Spring boot 3 Monitoring performance of RESTful API with Zipkin.

Zipkin is a distributed tracing system that helps you collect, visualize, and troubleshoot latency issues in microservice architectures. It is beneficial in scenarios where a request may pass through multiple services, and you want to understand the performance characteristics, bottlenecks, or failures that occur along the way.

Introduction

Software development design RESTful API using a micrometre collects and sends trace data (spans) to the Zipkin server to monitor RESTful API performance. This makes it easy to track which processes are slow and easy to maintain. The developer can choose which method in the workflow should be traced and view the process time in each traced process.

How Zipkin helps the development team improve the performance of the trace process to find a problem.

When the client informs the development team that the RESTful API is slow or has a problem, the team can directly trace the slow process in the workflow on the Zipkin server. Otherwise, the development team can view the log file and get a trace ID to identify the slow process in the workflow. The trace ID can also be used on the Zipkin server.

Create a Zipkin server on Docker.

1. Directory structure.

/zipkin-project
├── docker-compose.yml

2. Create a “docker-compose.yml” file.

version: '3.7'
services:
  zipkin:
    image: openzipkin/zipkin
    ports:
      - "9411:9411"
    networks:
      - monitoring
networks:
  monitoring:
    driver: bridge

3. Pull the image and start the Zipkin server with the Docker command.

docker-compose up -d

The -d flag in the docker-compose up -d Command stands for “detached mode.” When you run Docker Compose with the -d flag, the containers start in the background, allowing you to continue using the terminal.

4. Check the image of the Zipkin server on Docker.

>docker images
REPOSITORY                                      TAG       IMAGE ID       CREATED        SIZE
openzipkin/zipkin                               latest    88de2581a6c0   9 days ago     183MB

5. Check the container of the Zipkin server on Docker.

>docker container ls
CONTAINER ID   IMAGE               COMMAND                  CREATED             STATUS                       PORTS                              NAMES
ea30e88839aa   openzipkin/zipkin   "start-zipkin"           About an hour ago   Up About an hour (healthy)   9410/tcp, 0.0.0.0:9411->9411/tcp   zipkin-zipkin-1

Conclusion

The Zipkin server is ready to receive trace data (spans) from the RESTful API.

Access the Zipkin server.

The developer can access the Zipkin server through the browser with the URL http://localhost:9411/zipkin/.

Zipkin home

The RESTful API connects to the Zipkin server.

The developer can connect the RESTful API Zipkin server through the application properties file.

1. Add the required Maven dependencies.

 <dependencies>
    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Boot Starter AOP (Optional, if not already included) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
     <groupId>io.micrometer</groupId>
     <artifactId>micrometer-tracing-bridge-brave</artifactId>
    </dependency>
    <dependency>
     <groupId>io.zipkin.reporter2</groupId>
     <artifactId>zipkin-reporter-brave</artifactId>
    </dependency>
    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-devtools</artifactId>
     <scope>runtime</scope>
     <optional>true</optional>
    </dependency>
    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.34</version>
        <scope>provided</scope>
    </dependency>
 </dependencies>

2. Using application.propertie

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

# ===============================
# = MICROMETER
# ===============================
management.tracing.sampling.probability=1.0

Alternatively, Using application.yml

spring:
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      probability: 1.0
  reactor:
    context-propagation: AUTO
management:
  tracing:
    sampling:
      probability: 1.0

3. Create a user profile bean.

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class UserProfileBean {

    private String username;
    private String email;
    private String traceId;

    public UserProfileBean(String username, String email, String traceId) {
        this.username = username;
        this.email = email;
        this.traceId = traceId;
    }

}

4. Create a REST controller.

import io.micrometer.tracing.Tracer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserProfileController {

    private final Tracer tracer;

    @Autowired
    public UserProfileController(Tracer tracer) {
        this.tracer = tracer;
    }

    @GetMapping(path = "/user-profile")
    public ResponseEntity<UserProfileBean> getUserProfile(){
        String traceId = tracer.currentSpan().context().traceId();
        UserProfileBean bean = new UserProfileBean("John","demo@example.com",traceId);
        return ResponseEntity.ok().body(bean);
    }

}

5. Request RESTful API using the GET method.

>curl http://localhost:8080/user-profile
{"username":"John","email":"demo@example.com","traceId":"66bb1527e06b187ac42f6ba7397a4f9e"}

6. RESTful API returns a response user profile with a trace ID.

Monitor requests in the Zipkin server.

The request appears in a table containing the root, start time, span, and duration information. The developer can view the details by clicking the “SHOW” button.

In the root detail, show the trace ID that matches the trace ID in the response.

An error message will appear in the panel when the application causes an exception in the Zipkin server.

Conclusion

RESTful API collects and sends trace data (spans) to the Zipkin server. The developer can use any detail to identify the performance of each process in the RESTful API, making the application easy to maintain.

Monitoring multiple processes in the workflow.

The developer can trace the process using the trace ID. Each process has a span ID. If the process is in the same workflow, the Zipkin server will show all span IDs in the workflow. The developer can trace all the processes in the workflow and identify which process may impact the workflow’s performance.

For example, when a client requests a user profile path, the controller calls the user profile service to return its data.

1. Create a user profile service implementation.

import io.micrometer.tracing.Tracer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserProfileServiceImpl implements UserProfileServiceInf  {

    private final Tracer tracer;

    @Autowired
    public UserProfileServiceImpl(Tracer tracer) {
        this.tracer = tracer;
    }

    public UserProfileBean inquiryUserProfile(){
        String traceId = tracer.currentSpan().context().traceId();
        return new UserProfileBean("John","demo@example.com",traceId);
    }

}

2. Create a user profile service interface.

public interface UserProfileServiceInf {
    public UserProfileBean inquiryUserProfile();
}

3. Modify the user profile controller to call the service to get user profile data.

@RestController
public class UserProfileController {

    private  UserProfileServiceInf userProfileServiceInf;

    @Autowired
    public UserProfileController(UserProfileServiceInf userProfileServiceInf) {
        this.userProfileServiceInf = userProfileServiceInf;
    }

    @GetMapping(path = "/user-profile")
    public ResponseEntity<UserProfileBean> getUserProfile(){
        UserProfileBean bean = userProfileServiceInf.inquiryUserProfile();
        return ResponseEntity.ok().body(bean);
    }

}

4. Request RESTful API using the GET method.

>curl http://localhost:8080/user-profile
{"username":"John","email":"demo@example.com","traceId":"66bb1cf12637559e45207ee00b6f756f"}

5. Monitor requests in the Zipkin server.

6. The Zipkin server shows only a single trace ID and span ID.

Conclusion

The root should have two span IDs, but the other span ID in the workflow is not showing, and the developer can’t trace each process in the workflow. To solve the problem, the developer must use the Observed annotation.

Observed annotation.

The @Observed An annotation is part of Micrometer’s observation API, which was introduced to simplify and standardize metrics collection. This annotation allows developers to easily instrument methods and automatically capture metrics such as execution time, counts, exceptions, etc.

1. Add Maven dependency.

<dependency>
   <groupId>io.micrometer</groupId>
   <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!-- Spring Boot Starter AOP (Optional, if not already included) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. Modify the user profile service by adding an observed annotation.

import io.micrometer.observation.annotation.Observed;
import io.micrometer.tracing.Tracer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserProfileServiceImpl implements UserProfileServiceInf  {

    private final Tracer tracer;

    @Autowired
    public UserProfileServiceImpl(Tracer tracer) {
        this.tracer = tracer;
    }

    @Observed(name = "userProfile.inquiry", contextualName = "inquiryUserProfile")
    public UserProfileBean inquiryUserProfile(){
        String traceId = tracer.currentSpan().context().traceId();
        return new UserProfileBean("John","demo@example.com",traceId);
    }

}

3. Request RESTful API using the GET method.

>curl http://localhost:8080/user-profile
{"username":"John","email":"demo@example.com","traceId":"66bb241b65012e324e22b480dc6bc1ab"}

4. Monitor requests in the Zipkin server.

5. The root’s details show information for two spans. The developer can click on a span to view its details.

6. The second span shows the parent ID. The tag panel shows the class name and method name.

Conclusion

The developer can monitor each process’s time using observed annotations that help the developer trace specific processes.

Slf4j annotation to trace the Zipkin server.

The developer can use the log from the SLF4J annotation to trace the process in the Zipkin server. The trace ID is generated from the micrometre that collects and sends data to the Zipkin server.

1. Using application.propertie

# ===============================
# = LOGGER
# ===============================
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]

Alternatively, Using application.yml

logging:
  pattern:
    level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"

2. Modify the user service to log informational details.

@Observed(name = "userProfile.inquiry", contextualName = "inquiryUserProfile")
public UserProfileBean inquiryUserProfile(){
    String traceId = getTraceId();
    log.info("userProfile.inquiry");
    return new UserProfileBean("John","demo@example.com",traceId);
}

3. Request RESTful API using the GET method.

>curl http://localhost:8080/user-profile
{"username":"John","email":"demo@example.com","traceId":"66bb301c0b45318163e3af5a61b86293"}

4. In the console, show trace ID.

2024-08-13T17:06:20.783+07:00  INFO [app-trace-zipkin,66bb301c0b45318163e3af5a61b86293,480a30c4f7def724] 54784 --- [app-trace-zipkin] [nio-8080-exec-2] [66bb301c0b45318163e3af5a61b86293-480a30c4f7def724] c.e.a.services.UserProfileServiceImpl    : userProfile.inquiry

Monitor requests in the Zipkin server.

1. The developer can search the process by using a trace ID.

2. When the developer clicks on the “SPAN TABLE” button.

Conclusion

The trace ID from the Slf4j annotation matches the trace ID inside the Zipkin server, so the developer can use the trace ID from the log to search the Zipkin server because it is generated from the micrometer.

Finally

Zipkin benefits the development team by improving performance monitoring through a RESTful API. The developer should learn how to configure a micrometer that helps the developer provide information to the Zipkin server.
When a client sends a request, the developer can instantly trace process performance and the problem, reducing the time it takes to solve it.

Leave a Comment

Your email address will not be published. Required fields are marked *