Distributed tracing với Spring Cloud Sleuth và Zipkin

1. Distributed tracing

Trong quá trình phát triển và vận hành một hệ thống, để giải quyết các vấn đề phát sinh, chúng ta thường phải theo dõi flow của các request trong hệ thống. Flow của một request được bắt đầu kể từ khi hệ thống tiếp nhận request và kết thúc khi hệ thống không còn xử lý request này nữa. Kỹ thuật này được gọi với thuật ngữ là tracing.

Khác với hệ thống monolithic, các request trong hệ thống microservice thường dàn trải trên nhiều service. Việc theo dõi flow của một request trên một hệ thống phân tán là công việc không hề dễ dàng chút nào và cũng mất tương đối thời gian. Lúc này, chúng ta có thể áp dụng kỹ thuật distributed tracing:

  • Service tiếp nhận request đầu tiên sẽ gán cho request một correlation ID - định danh để phân biệt các request.
  • Các downstream request tiếp tục lan truyền correlation ID này:
    • Nếu việc giao tiếp giữa các service thông qua HTTP request, correlation ID sẽ được gắn vào request header.
    • Nếu việc giao tiếp giữa các service thông qua các hệ thống messaging, correlation ID sẽ được nằm trong message.
  • Các log liên quan đến request đều sẽ chứa correlation ID.
  • Dữ liệu tracing sẽ được các hệ thống như Zipkin hoặc Jaeger thu thập và phân tích.

Thông qua distributed tracing, chúng ta có thể theo dõi được flow của một request dễ dàng hơn: xác định được tổng thời gian để hệ thống xử lý request đó, hoặc biết được request gặp vấn đề ở service nào.


2. Spring Cloud Sleuth

Spring Cloud Sleuth là một thư viện giúp chúng ta thực hiện distributed tracing cho các ứng dụng Spring Cloud. Spring Cloud Sleuth theo dõi request trên các servlet filter, REST template, scheduled action, message channel, Zuul filter, Feign client.

Spring Cloud Sleuth sử dụng 2 thuật ngữ spantrace được lấy từ Dapper của Google:

Span: đây là đơn vị cơ bản nhất để tracing, tương ứng với một thao tác trong hệ thống. Ví dụ, việc gửi đi một HTTP request và nhận về response của HTTP request đó đều cùng là một span. Các span được định danh bởi một unique 64-bit ID. Span còn chứa các dữ liệu khác như: descriptions, timestamped events, key-value annotations (tags), ID của span đứng trước, và ID của các process (thường là các địa chỉ IP).

Khi một span được start, span sẽ được gán tên và thời điểm start sẽ được lưu lại.

Khi một span được close, thời điểm close sẽ được lưu lại. Nếu span là sample thì dữ liệu của span sẽ được gửi tới các hệ thống như Zipkin. Việc xác định một span có là sample hay không sẽ dựa trên một tỉ lệ. Cơ chế này được gọi là sampling. Sở dĩ Spring Cloud Sleuth áp dụng cơ chế này vì trong các hệ thống lớn, việc gửi dữ liệu span liên tục tốn chi phí không nhỏ.

Trace: là tập hợp các span được tổ chức dưới dạng cây. Span đầu tiên của trace được gọi là root span. ID của trace chính là ID của root span.

Trace ID và span ID sẽ được Spring Cloud Sleuth thêm vào log thông qua Sl4J MDC, với format mặc định là: [appname,traceId,spanId,exportable]

Trong đó:

  • appname: tên của ứng dụng chứa log.
  • traceId: ID của trace chứa span
  • spanId: ID của span.
  • exportable: xác định xem log đã được gửi đến Zipkin hay chưa.

Ví dụ:

2016-02-02 15:31:01.936  INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...

3. Zipkin

Zipkin là một hệ thống distributed tracing open source. Zipkin được xây dựng dựa trên Dapper của Google. Zipkin được dùng để khắc phục các vấn đề liên quan đến latency trong hệ thống microservice.

Zipkin có kiến trúc hoạt động khá dễ hiểu:

  1. Reporter gửi dữ liệu tracing từ ứng dụng tới Zipkin thông qua HTTP hoặc message system (Kafka, RabbitMQ).
  2. Tại Zipkin, thành phần collector chịu trách nhiệm thu thập và validate dữ liệu.
  3. Dữ liệu sau đó được lưu trữ bên trong storage mặc định là Cassandra. Chúng ta có thể lựa chọn các storage khác như Elasticsearch hoặc MySQL.
  4. Zipkin cung cấp UI để user dễ dàng tìm kiếm và theo dõi dữ liệu. UI sẽ lấy dữ liệu từ API.

alt text


4. Grab all

Step 1: Start Zipkin

$ docker run --name=zipkin -d -p 9411:9411 openzipkin/zipkin

Sau khi Docker container run, chúng ta có thể truy cập Zipkin UI tại địa chỉ http://localhost:9411/zipkin/

alt text

Step 2: Cấu hình Spring Cloud Sleuth

Để sử dụng Spring Cloud Sleuth với Zipkin, chúng ta cần thêm 2 dependency sau:

pom.xml

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

Mặc định khi chúng ta thêm dependency spring-cloud-starter-zipkin, dữ liệu span sẽ được gửi tới Zipkin thông qua HTTP (http://localhost:9411). Để thay đổi URL này, chúng ta cấu hình thông qua thuộc tính spring.zipkin.base-url.

Bên cạnh đó, để thay đổi tỉ lệ sampling, chúng ta sẽ cấu hình thông qua thuộc tính spring.sleuth.sampler.probability. Giá trị của thuộc tính này là một số thập phân. Ví dụ: spring.sleuth.sampler.probability=1.0 => gửi dữ liệu của tất cả các span tới Zipkin.

...

Mình sẽ áp dụng Spring Cloud Sleuth vào 2 service trong một ứng dụng Microservice demogateway-serveraccount-service.

Thử gọi API http://localhost:8080/account-service/details/123. Khi tiếp nhận request, gateway-server sẽ gọi sang account-service.

alt text

alt text

alt text

alt text


Tài liệu tham khảo

  1. https://microservices.io/patterns/observability/distributed-tracing.html

  2. https://cloud.spring.io/spring-cloud-sleuth/reference/html/