Implement Distributed Tracing Using Zipkin in the given Microservices Architecture

To implement distributed tracing using Zipkin in the given microservices architecture, you need to instrument each microservice to send trace data to Zipkin. Below are the steps and code snippets to integrate Zipkin with Microservice A, Microservice B, and Microservice C.

Prerequisites

  1. Zipkin Server: Ensure you have a running Zipkin server. You can run it using Docker:

    docker run -d -p 9411:9411 openzipkin/zipkin
    
  2. Dependencies: Add the necessary dependencies to your microservices. For a Spring Boot application, you can use the Spring Cloud Sleuth and Zipkin dependencies.

Step-by-Step Implementation

1. Add Dependencies

Add the following dependencies to your pom.xml for each microservice:

<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>

2. Configure Zipkin

Add the following configuration to your application.properties or application.yml file for each microservice:

spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.probability=1.0

3. Instrument Microservices

Spring Cloud Sleuth automatically instruments your Spring Boot applications. You just need to ensure that the trace context is propagated across HTTP calls.

Microservice A
@RestController
public class ServiceAController {

    @Autowired
    private ServiceBClient serviceBClient;

    @GetMapping("/serviceA")
    public ResponseEntity<String> callServiceB() {
        try {
            return serviceBClient.callServiceB();
        } catch (Exception e) {
            log.error("Exception in Service A", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in Service A");
        }
    }
}

@FeignClient(name = "serviceB", url = "http://localhost:8081")
public interface ServiceBClient {
    @GetMapping("/serviceB")
    ResponseEntity<String> callServiceB();
}
Microservice B
@RestController
public class ServiceBController {

    @Autowired
    private ServiceCClient serviceCClient;

    @GetMapping("/serviceB")
    public ResponseEntity<String> callServiceC() {
        try {
            return serviceCClient.callServiceC();
        } catch (Exception e) {
            log.error("Exception in Service B", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in Service B");
        }
    }
}

@FeignClient(name = "serviceC", url = "http://localhost:8082")
public interface ServiceCClient {
    @GetMapping("/serviceC")
    ResponseEntity<String> callServiceC();
}
Microservice C
@RestController
public class ServiceCController {

    @GetMapping("/serviceC")
    public ResponseEntity<String> performAction() {
        try {
            // Some logic
            if (errorCondition) {
                throw new CustomException("Error in Service C");
            }
            return ResponseEntity.ok("Success in Service C");
        } catch (CustomException e) {
            log.error("Exception in Service C", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in Service C");
        }
    }
}

4. Run and Test

  1. Start Zipkin Server: Ensure the Zipkin server is running.
  2. Start Microservices: Start Microservice A, B, and C.
  3. Make a Request: Call the endpoint of Microservice A (e.g., http://localhost:8080/serviceA).
  4. View Traces: Open the Zipkin UI (e.g., http://localhost:9411) to view the traces and see how the request flows through the microservices.

Spring Cloud Sleuth, which integrates with Zipkin, automatically captures and propagates trace and span information, including errors.

However, you can enhance the error handling by explicitly adding error tags to the spans. This can be done by using the Tracer API provided by Spring Cloud Sleuth.

Step-by-Step Implementation with Error Tagging

Instrument Microservices with Error Tagging

Microservice A
@RestController
public class ServiceAController {

    @Autowired
    private ServiceBClient serviceBClient;

    @Autowired
    private Tracer tracer;

    @GetMapping("/serviceA")
    public ResponseEntity<String> callServiceB() {
        try {
            return serviceBClient.callServiceB();
        } catch (Exception e) {
            log.error("Exception in Service A", e);
            tracer.currentSpan().tag("error", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in Service A");
        }
    }
}

@FeignClient(name = "serviceB", url = "http://localhost:8081")
public interface ServiceBClient {
    @GetMapping("/serviceB")
    ResponseEntity<String> callServiceB();
}
Microservice B
@RestController
public class ServiceBController {

    @Autowired
    private ServiceCClient serviceCClient;

    @Autowired
    private Tracer tracer;

    @GetMapping("/serviceB")
    public ResponseEntity<String> callServiceC() {
        try {
            return serviceCClient.callServiceC();
        } catch (Exception e) {
            log.error("Exception in Service B", e);
            tracer.currentSpan().tag("error", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in Service B");
        }
    }
}

@FeignClient(name = "serviceC", url = "http://localhost:8082")
public interface ServiceCClient {
    @GetMapping("/serviceC")
    ResponseEntity<String> callServiceC();
}
Microservice C
@RestController
public class ServiceCController {

    @Autowired
    private Tracer tracer;

    @GetMapping("/serviceC")
    public ResponseEntity<String> performAction() {
        try {
            // Some logic
            if (errorCondition) {
                throw new CustomException("Error in Service C");
            }
            return ResponseEntity.ok("Success in Service C");
        } catch (CustomException e) {
            log.error("Exception in Service C", e);
            tracer.currentSpan().tag("error", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in Service C");
        }
    }
}

Open the Zipkin UI (e.g., http://localhost:9411) to view the traces and see how the request flows through the microservices. You should see error tags in the spans where exceptions occurred.

Conclusion

By following these steps, you can integrate Zipkin for distributed tracing in your microservices architecture. This setup will help you track the flow of requests and identify where exceptions occur, making it easier to debug and monitor your microservices.

Post a Comment

Previous Post Next Post