Spring Cloud Sleuth | Microservices Distributed Tracing Sinhala Tutorial

Spring Cloud Sleuth | Microservices Distributed Tracing Sinhala Tutorial

නමස්තේ! කොහොමද යාලුවනේ? අද අපි කතා කරන්න යන්නේ නූතන Software Engineering ලෝකයේදී, විශේෂයෙන්ම Microservices Architecture භාවිතා කරනකොට අතිශයින්ම වැදගත් වෙන, සමහරවිට ඔයාගේ හිසරදයට හොඳම විසඳුම වෙන්න පුළුවන් මාතෘකාවක් ගැන. ඒ තමයි Distributed Tracing. ඒ වගේම, Spring Boot Developersලාට මේ සංකීර්ණ කාර්යය ඉතාමත් පහසු කරන, Spring Cloud Sleuth කියන සුපිරි Tool එක ගැනත් අපි විස්තරාත්මකව බලමු.

දැනට අවුරුදු කිහිපයක ඉඳන් Microservices කියන්නේ Software Development වල Trending Topic එකක්. සරලවම කිව්වොත්, විශාල Application එකක්, එකිනෙකට ස්වාධීන කුඩා Services ගොඩක් විදියට කඩලා හදන එක තමයි Microservices කියන්නේ. මේකෙන් Development Speed එක වැඩි වෙනවා, Scalability එක දියුණු වෙනවා, ඒ වගේම Technology Stack එක තෝරගන්න පුළුවන් නිදහස ලැබෙනවා. ඒත්, මේකෙන් එන වාසි වගේම, අභියෝගත් තියෙනවා. ඒ අභියෝග අතරින් ප්‍රධාන එකක් තමයි Debugging හා Troubleshooting. හිතන්න, Customer කෙනෙක්ගේ Request එකක් Services 5ක් 6ක් හරහා ගිහින්, එක Service එකකදී Fail වෙනවා. දැන් මේකේ Bug එක හොයන එක Monolithic Application එකක වගේ ලේසි වැඩක් නෙවෙයි. මොකද, Log Files ගොඩක් තියෙනවා, ඒ හැම එකක්ම Correlate කරන එකයි, සිදුවීම් අනුපිළිවෙල තේරුම් ගන්න එකයි හරිම අමාරුයි.

අන්න ඒ වගේ අවස්ථාවකදී තමයි Distributed Tracing වල වැදගත්කම අපිට හොඳටම තේරෙන්නේ. මේකෙන් කරන්නේ, එක request එකක් මුල ඉඳන් අගට යනකම් Tracking කරන එකයි. හරියට Parcel එකක් Track කරනවා වගේ. මේ Article එක කියෙව්වට පස්සේ, ඔබට Spring Cloud Sleuth හා Zipkin යොදාගෙන ඔබේ Microservices Application වල requests Trace කරන හැටි ගැන පැහැදිලි අවබෝධයක් ලැබෙනවා නොඅනුමානයි. එහෙනම්, අපි පටන් ගමු!

Distributed Tracing කියන්නේ මොකක්ද? (Distributed Tracing Explained)

මුලින්ම, Distributed Tracing කියන්නේ මොකක්ද කියන Concept එක හොඳින් තේරුම් ගමු. අපි කලින් කිව්වා වගේ, Microservices Architecture එකකදී Application එකක Request එකක් කියන්නේ, Services කිහිපයක් අතර සිදුවන ක්‍රියාවලීන් මාලාවකට. උදාහරණයක් විදියට, e-commerce Application එකකදී Customer කෙනෙක් "Order Now" button එක Click කළාම, Request එක Order Service එකට යනවා, එතනින් Payment Service එකට, ඊට පස්සේ Inventory Service එකට, සමහරවිට Shipping Service එකටත් යන්න පුළුවන්. මේ සෑම Service එකක්ම තමන්ගේ Log File එකට තොරතුරු ලියනවා.

දැන් හිතන්න, මේ Request එක Payment Service එකේදී Fail වුණා කියලා. අපිට දැනගන්න ඕනේ:

  1. Request එකට අදාළව Order Service එකට මොකද වුණේ?
  2. Payment Service එකේදී Error එක එන්න කලින් මොනවද සිද්ධ වුණේ?
  3. Request එකේ මුළු ගමන් මගේ Performance එක කොහොමද? (කොයි Service එකද වැඩිපුර වෙලාවක් ගත්තේ?)

සාම්ප්‍රදායිකව, මේ ප්‍රශ්නවලට උත්තර හොයන්න නම්, අපි හැම Service එකකම Log Files Open කරලා, එකිනෙක ගැළපෙන Log Lines හොයාගන්න වෙයි. මේක හරිම වෙහෙසකර, කාලය නාස්ති වෙන වැඩක්. විශේෂයෙන්ම, Services දහයක් පහළොවක් තියෙනකොට මේක කළ නොහැකි දෙයක් වෙනවා.

අන්න මේ ගැටලුවට විසඳුම තමයි Distributed Tracing. මේකෙන් කරන්නේ, Request එකක් Microservices Network එක හරහා යනකොට, ඒකට unique ID එකක් (Trace ID) එකතු කරන එකයි. ඊට පස්සේ, ඒ Request එක යන හැම Service එකක්ම, ඒ Service එක ඇතුළේ සිදුවන හැම Operation එකක්ම (Database Call එකක්, External API Call එකක් වගේ) මේ Trace ID එකට අදාළව Record කරනවා. මේ එක් එක් Operation එකට Span ID කියලා තවත් unique ID එකක් ලැබෙනවා. මේ Span ID එක, ඒ Operation එකේ Parent Operation එකේ Span ID එකත් එක්ක සම්බන්ධ වෙනවා (Parent Span ID). මේ විදියට Trace ID එකෙන් මුළු Request එකම Track කරන්න පුළුවන් වෙනවා වගේම, Span ID වලින් ඒ Request එක ඇතුළේ තියෙන Individual Operations ටිකත් (කාලයත් එක්ක) බලාගන්න පුළුවන් වෙනවා.

සරලව කියනවා නම්, Distributed Tracing කියන්නේ Application එකක් ඇතුළේ Request එකක මුළු ගමන් මග, ඒකට ගත වුණු කාලය, හා ඒකේ එක් එක් පියවර (Service Calls, Database Queries, etc.) මොනවාද කියලා පැහැදිලිව දකින්න පුළුවන් Mapping එකක් වගේ දෙයක්. මේකෙන් අපිට System එකේ Performance Bottlenecks, Error Points, හා Overall Flow එක ඉතාමත් පහසුවෙන් තේරුම් ගන්න පුළුවන්.

Spring Cloud Sleuth මොකටද? (Why Spring Cloud Sleuth?)

දැන් Distributed Tracing කියන Concept එක ගැන හොඳ අවබෝධයක් ආවනේ. හැබැයි, මේ Trace ID, Span ID Manage කරන එකයි, ඒවා Services අතර Propagate කරන එකයි, ඒ data ටික Log කරන එකයි, ඒ හැමදේම Manualy කරන්න ගියොත් හරිම අමාරුයි, වැරදි වෙන්නත් ඉඩ තියෙනවා. අන්න ඒකට තමයි Spring Cloud Sleuth කියන නියම Library එක තියෙන්නේ.

Spring Cloud Sleuth කියන්නේ Spring Boot Application වලට Distributed Tracing capability එක 자동으로ම එකතු කරන්න උදව් කරන Library එකක්. මේක Brave කියන Tracing Library එක මත තමයි Build වෙලා තියෙන්නේ. Sleuth වල ප්‍රධානම දේ තමයි, ඔබගේ Application එකට එන හැම Request එකකටම, හා Application එකෙන් පිටට යන හැම Request එකකටම (External API calls, Database queries, Message Queue interactions) 자동으로ම Trace ID හා Span ID generate කරලා, ඒවා ඔබගේ Log Messages වලට එකතු කරන එක.

ඒ විතරක් නෙවෙයි, මේ IDs ටික HTTP Headers (විශේෂයෙන්ම X-B3-TraceId, X-B3-SpanId වගේ Headers), Messaging Queues (Kafka, RabbitMQ වගේ) හරහා ඊළඟ Service එකට Propagate කරන්නත් Sleuth උදව් කරනවා. ඒ නිසා, එක් Request එකක් Services ගොඩක් හරහා ගියත්, අපිට ඒක එකම Trace ID එකෙන් follow කරන්න පුළුවන්. මේ Context Propagation එක තමයි Distributed Tracing වල හදවත.

Sleuth වල තවත් වැදගත් විශේෂත්වයක් තමයි ඒක OpenTracing හා OpenTelemetry වගේ Industry Standards වලට Support කරන එක. ඒ වගේම, Zipkin වගේ Tracing Server වලට Tracing data යවන්නත් ඒකට පුළුවන්. Zipkin කියන්නේ මේ Tracing data ටික Store කරලා, Analyze කරලා, ලස්සනට Graph එකකින්, Time-based Diagrams වලින් පෙන්නන Open Source Tool එකක්. ඒ ගැන අපි ඊළඟ කොටසින් විස්තරාත්මකව කතා කරමු.

සරලව කියනවා නම්, Sleuth කියන්නේ ඔබේ Spring Boot Microservice Application එකට Trace කරන්න අවශ්‍ය කරන සියලුම Infrastructure එක 자동으로ම සපයන, කරදර අඩු කරන (low-overhead) Library එකක්. ඔබට Code එකේ ලොකු වෙනස්කම් කරන්නේ නැතුවම Tracing enable කරන්න පුළුවන් වීම තමයි මේකේ ලොකුම වාසිය.

Sleuth වල ප්‍රධාන වාසි:

  • සරල බව (Simplicity): අවශ්‍ය Dependencies එකතු කරලා, පොඩි Configuration එකක් දැම්මම Distributed Tracing Enable වෙනවා. කිසිම Code Change එකක් අවශ්‍ය වෙන්නේ නැහැ.
  • Automatic Instrumentation: HTTP Requests, JDBC Calls, Messaging (Kafka, RabbitMQ), Async Operations වගේ දේවල් Sleuth 자동으로 Trace කරනවා. Developer කෙනෙක්ට මේ ගැන විශේෂයෙන් ලියන්න අවශ්‍ය වෙන්නේ නැහැ.
  • Context Propagation: Trace IDs හා Span IDs Services අතර, හා Thread Boundaries අතර (e.g., Spring's @Async methods) නිවැරදිව Propagate කරනවා.
  • Zipkin Integration: Zipkin වගේ ජනප්‍රිය Tracing Servers එක්ක පහසුවෙන් සම්බන්ධ වෙන්න පුළුවන්. මේකෙන් Tracing data Visualize කරන එක පහසු වෙනවා.
  • Performance Friendly: Minimal Overhead එකක් සහිතව Tracing සිදු කරනවා.

Microservices වලට Sleuth එකතු කරමු

හරි, දැන් අපි Theory ඇති. Practical වැඩේට බහිමු. අපි හදමු Microservices දෙකක්. එකක් UserService කියලා, අනිත් එක ProductService කියලා. UserService එකෙන් ProductService එකට request එකක් යවන විදිහට තමයි අපි මේ Application එක හදන්නේ.

මේ සඳහා ඔබ Spring Boot Project දෙකක් (user-service සහ product-service) වෙන වෙනම නිර්මාණය කරගන්න. ඔබට Spring Initializr (start.spring.io) භාවිතා කරලා මේ Project හදාගන්න පුළුවන්.

පියවර 1: Maven Dependencies එකතු කිරීම

මුලින්ම, ඔබේ Spring Boot Project වල pom.xml file එකට පහත Dependencies එකතු කරන්න. මේක UserService හා ProductService දෙකටම පොදුයි.


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

මේ Dependencies ටිකේ Versions manage කරන්න, ඔබේ pom.xml එකේ dependencyManagement section එකේ Spring Cloud Dependencies add කරගන්න පුළුවන්. උදාහරණයක් විදියට:


<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2020.0.3</version> <!-- ඔබේ Spring Boot Version එකට ගැලපෙන Version එකක් යොදන්න -->
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

පියවර 2: application.properties Configure කිරීම

දැන් application.properties (හෝ application.yml) file එකේ එක් එක් service එකට අදාළව Configuration එකතු කරමු. මේකෙන් තමයි Service Name එකයි, Zipkin Server එකේ Address එකයි define කරන්නේ.

UserService (src/main/resources/application.properties)


server.port=8080
spring.application.name=user-service
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.probability=1.0 # සියලු requests trace කරන්න

ProductService (src/main/resources/application.properties)


server.port=8081
spring.application.name=product-service
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.probability=1.0

spring.sleuth.sampler.probability=1.0 කියන්නේ හැම request එකක්ම trace කරන්න කියලා. Production Environments වලදී මේක 0.1 වගේ අගයකට අඩු කරන්න පුළුවන්, මොකද හැම request එකක්ම trace කරන එකෙන් performance impact එකක් වෙන්න පුළුවන්. Tracing data විශාල ප්‍රමාණයක් generate වීම වළක්වා ගැනීමට මෙය උපකාරී වේ.

පියවර 3: Simple Controllers සහ Configuration නිර්මාණය

දැන් අපි Controller දෙක හදමු. UserService එකේ Endpoint එකක් ProductService එකට Call කරන විදිහට. RestTemplate එක Sleuth සමඟ 자동으로ම Trace Context එක propagate කරනවා, ඒ නිසා ඔබට ඒ ගැන විශේෂයෙන් කිසිවක් කරන්න අවශ්‍ය නැහැ.

UserService (com.example.userservice.config.AppConfig.java)

RestTemplate Bean එක define කරන්න මේ විදිහට Configuration Class එකක් හදාගන්න:


package com.example.userservice.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class AppConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

UserService (com.example.userservice.controller.UserServiceController.java)


package com.example.userservice.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class UserServiceController {

    private static final Logger logger = LoggerFactory.getLogger(UserServiceController.class);

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/users")
    public String getUsers() {
        logger.info("In user-service: getUsers endpoint called.");
        String productInfo = restTemplate.getForObject("http://localhost:8081/products", String.class);
        logger.info("Received product info from ProductService: {}", productInfo);
        return "Users fetched! Also called Product Service: " + productInfo;
    }
}

ProductService (com.example.productservice.controller.ProductServiceController.java)


package com.example.productservice.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductServiceController {

    private static final Logger logger = LoggerFactory.getLogger(ProductServiceController.class);

    @GetMapping("/products")
    public String getProducts() {
        logger.info("In product-service: getProducts endpoint called.");
        return "All Products";
    }
}

දැන් මේ Application දෙකම Run කරන්න. UserService එක 8080 port එකෙනුත්, ProductService එක 8081 port එකෙනුත් Run වෙන්න ඕනේ. ඔබට ඔබේ IDE (IntelliJ IDEA, Eclipse) භාවිතා කරලා මේ Project දෙක වෙන වෙනම Run කරන්න පුළුවන්.

ඉන්පසු, Browser එකේ හෝ Postman වැනි Tool එකකින් http://localhost:8080/users යන URL එකට Request එකක් යවන්න. දැන්, මේ Services දෙකේම Console එකේ Log Messages ටික බලන්න.

ඔබට පෙනේවි, Log Message වල [application-name, trace-id, span-id, parent-span-id, export-or-not] වගේ Format එකකට අලුත් information එකතු වෙලා තියෙනවා කියලා. උදාහරණයක් විදියට [user-service, 5a7b8c9d0e1f2a3b, 5a7b8c9d0e1f2a3b, 5a7b8c9d0e1f2a3b, true] වගේ දෙයක් දැකගන්න පුළුවන්. මේකෙන් කියවෙන්නේ මේ Log line එක මේ අදාළ Trace ID හා Span ID එකට අදාළයි කියලා. මේ ID එක user-service එකේ ඉඳන් product-service එකටත් automatically Propagate වෙලා තියෙනවා ඔබට දැකගන්න පුළුවන්.

Zipkin එක්ක Trace කරන හැටි

දැන් අපේ Microservices ටික Sleuth එක්ක Configure කරලා ඉවරයි. ඒත් මේ IDs ටික Log එකෙන් කියවලා Trace එකක් තේරුම් ගන්න එක ටිකක් අමාරුයිනේ. ඒ වගේම, System එකේ flow එක දෘශ්‍යමය වශයෙන් තේරුම් ගන්න එකත් අමාරුයි. අන්න ඒකට තමයි Zipkin කියන Open Source Tool එක තියෙන්නේ.

Zipkin කියන්නේ Distributed Tracing Data එක Store කරලා, Analyze කරලා, Visualize කරන Server එකක්. අපේ Sleuth Configure කරපු Services වලින් එවන Tracing data ටික Zipkin Server එකට යනවා. Zipkin Server එකේ Web UI එක හරහා අපිට මේ Traces ටික බලන්න පුළුවන්.

Zipkin Run කරන හැටි:

Zipkin Run කරන්න ලේසිම ක්‍රමය තමයි Docker භාවිතා කරන එක. ඔබගේ System එකේ Docker install කරලා නැත්නම්, මුලින්ම ඒක කරගන්න. ඊට පස්සේ, Command Prompt එකේ පහත Command එක Run කරන්න:


docker run -d -p 9411:9411 openzipkin/zipkin

මේ Command එකෙන් Zipkin Server එක Docker Container එකක් විදියට Run වෙනවා. 9411 Port එකෙන් තමයි ඒකට access කරන්න පුළුවන්. අපේ application.properties වල spring.zipkin.base-url එකට 9411 Port එක දුන්නේ ඒකයි.

Traces බලන හැටි:

Zipkin Server එක Run වුණාට පස්සේ, Browser එකේ http://localhost:9411 URL එකට යන්න. ඔබට Zipkin Web UI එක පෙනේවි. දැන් අපේ UserService එකේ http://localhost:8080/users endpoint එකට Request එකක් දෙන්න. Request එක දීපු ගමන්, Zipkin UI එකේ “Find Traces” button එක Click කරලා බලන්න. ඔබට අලුත් Trace එකක් පෙනේවි. ඒක Click කරලා, ඒ request එක user-service එකේ ඉඳන් product-service එකට ගිය විදිහ පැහැදිලිව Diagrams එක්ක බලන්න පුළුවන්. ඒ වගේම, එක් එක් Span එකට ගත වුණු වෙලාව, ඒකේ Log data, tags වගේ ගොඩක් information බලාගන්නත් පුළුවන්.

Zipkin UI displaying a trace with services and spans

(සටහන: මේ රූපය ඔබට Zipkin UI එකේ trace එකක් පෙනෙන ආකාරය පිළිබඳ උදාහරණයකි. ඔබට ඔබේම Zipkin UI screenshot එකක් මෙතනට ඇතුළත් කළ හැක.)

නිගමනය

ඉතින් යාලුවනේ, අද අපි Spring Cloud Sleuth සහ Zipkin යොදාගෙන Distributed Tracing කියන සංකීර්ණ මාතෘකාව කොච්චර ලේසියෙන් තේරුම් අරගෙන, Practical විදියට implement කරන්න පුළුවන්ද කියලා බැලුවා. Microservices Architecture එකේදී System එකක flow එක තේරුම් ගන්න, Bug Fix කරන්න, Performance Issues හොයන්න මේ Distributed Tracing කියන එක නැතුවම බැරි Tool එකක්. මේක ඔබේ Development Workflows වලට විශාල වටිනාකමක් එකතු කරනවා නොඅනුමානයි.

මතක තියාගන්න, හොඳ Software Engineer කෙනෙක් වෙන්න නම්, තියෙන Tools ගැන දැනගන්න එක විතරක් නෙවෙයි, ඒවා මොන වගේ ගැටලුවලටද පාවිච්චි කරන්නේ කියලා තේරුම් ගන්න එකත් වැදගත්. ඒ වගේම, මේ Article එකෙන් මම කිව්වේ Basic Setup එකක් විතරයි. Production Environments වලට යනකොට Sampling Strategies, Context Propagation for Asynchronous calls, Error Handling, හා Metrics integration වගේ ගොඩක් දේවල් ගැන හිතන්න සිද්ධ වෙනවා. ඒ වගේ ගැඹුරු දේවල් ගැනත් අපි ඉස්සරහට කතා කරමු.

ඔබ මේ Sleuth Implementation එක ඔබේ Project එකක උත්සාහ කරලා බැලුවාද? ඔබට මොන වගේ අත්දැකීමක්ද ලැබුණේ? පහලින් Comment එකක් දාලා කියන්න. ඔබේ අත්දැකීම් අනිත් අයටත් ගොඩක් වටිනවා. ඒ වගේම මේ Article එක ඔබේ යාලුවෝ එක්කත් Share කරන්න අමතක කරන්න එපා. අලුත් Article එකකින් නැවත හමුවෙමු! සුභ දවසක්!