Today we'll arm ourselves with Sleuth and Zipkin to implement request tracing between multiple microservices. We'll have two simple microservices:
- Service A — sends an HTTP request to Service B.
- Service B — processes the request and responds.
In the end we'll be able to visualize each request trace in the Zipkin UI. Let's get started!
Adding dependencies
We'll need:
Spring BootSpring Cloud SleuthSpring Web- Connection to Zipkin (either locally or via Docker).
Setting up pom.xml for Service A and B
Add the necessary dependencies to the projects' pom.xml. Let's start by adding Sleuth and Spring Web:
<dependencies>
<!-- Spring Web (for handling HTTP requests) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Sleuth (for request tracing) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- Zipkin (for sending tracing data to Zipkin) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
</dependencies>
Update maven with mvn install if your favorite IDE hasn't already.
Setting up Zipkin
If you don't have Zipkin yet, the easiest way is to run it via Docker:
docker run -d -p 9411:9411 openzipkin/zipkin
After starting Zipkin it will be available at: http://localhost:9411. Open it in your browser — a nice trace visualization awaits us.
Creating the microservices
Service A will be responsible for sending requests to Service B. We'll add one REST endpoint that makes an HTTP request to the other service.
In the src/main/java folder create the controller class:
@RestController
@RequestMapping("/serviceA")
public class ServiceAController {
private final RestTemplate restTemplate;
public ServiceAController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@GetMapping("/callB")
public String callServiceB() {
// Sending GET request to Service B
String response = restTemplate.getForObject("http://localhost:8081/serviceB/hello", String.class);
return "Response from Service B: " + response;
}
}
Note that we use RestTemplate to send the request. Sleuth will automatically "trace" this request.
Now add a RestTemplate bean so Spring Boot can use it:
@Configuration
public class ServiceAConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Service B will handle the HTTP request from Service A and return a response.
In the src/main/java folder create the controller for Service B:
@RestController
@RequestMapping("/serviceB")
public class ServiceBController {
@GetMapping("/hello")
public String sayHello() {
return "Hello from Service B!";
}
}
Tracing configuration
Sleuth automatically adds the necessary "tags" (traceId, spanId) to logs and requests. However, we need to configure it to send data to Zipkin.
Add the following settings to application.yml files for both services:
spring:
application:
name: service-a # or service-b, change for each service
sleuth:
sampler:
probability: 1.0 # 100% of requests will be traced
zipkin:
base-url: http://localhost:9411 # Zipkin URL
enabled: true
Note that sampler.probability determines what percentage of requests will be traced. We set it to 1.0 for debugging (that's 100%).
Running and testing
Step 1: Start both services
- Start Service B (it should listen on port
8081). - Start Service A (it should listen on port
8080).
Step 2: Test tracing
- Send a request to Service A using your browser or any HTTP client (for example, Postman):
GET http://localhost:8080/serviceA/callB - You should get the response:
Response from Service B: Hello from Service B!
Check the trace in the Zipkin UI
Go to http://localhost:9411 to see the trace. Look at how the request travels from Service A to Service B and back. The UI will show:
- Trace ID — the unique identifier of the request.
- Span ID — individual parts of the trace (for example, HTTP requests).
Notes and gotchas
- Typical errors: if the trace doesn't appear in Zipkin, check the
base-urlsetting and make sure Zipkin is running. - Port issues: make sure Service A and B are running on different ports.
- Missing traces: make sure
spring.sleuth.sampler.probabilityis set to1.0while debugging.
Real-world use of tracing
These skills apply to real projects. For example, a temperamental microservice architecture can start acting up if a call fails somewhere. With tracing you can quickly find the "bad" service neighbor and fix the issue.
Congrats — you're now ready to successfully find and fix bugs in distributed systems!
GO TO FULL VERSION