Spring Cloud + Sleuth

Spring Cloud + Sleuth

2021, Nov 20    

Hướng dẫn tích hợp Spring Cloud Sleuth trong ứng dụng Spring Boot

Example

  • Trong ví dụ này bao gồm 3 service
    • ServiceA: một service public 2 api và gọi vào ServiceB, ServiceC
    • ServiceB: http service
    • ServiceC: grpc service
  • Thêm dependency spring-cloud-starter-sleuth trong mỗi service
<properties>
    <spring-cloud.version>2020.0.4</spring-cloud.version>
</properties>

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

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Service A

<properties>
    <net.devh.grpc.starter.version>2.12.0.RELEASE</net.devh.grpc.starter.version>
</properties>
<dependencies>
    <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-spring-boot-starter</artifactId>
        <version>${net.devh.grpc.starter.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>io.zipkin.brave</groupId>
        <artifactId>brave-instrumentation-grpc</artifactId>
    </dependency>
</dependencies>

Mặc định OpenFeign tích hợp sẵn sleuth, còn với gRPC cần sử dụng thêm dependency brave-instrumentation-grpc

  • Cấu hình sleuth trong application.yml
spring:
  sleuth:
    propagation:
      type: B3 #,W3C
    sampler:
      probability: 1.0
  • Cấu hình service B và C trong application.yml
serviceb:
  url: http://localhost:8082
  path:
    greet: /greet/{id}
grpc:
  client:
    servicec:
      address: static://localhost:8183
      enableKeepAlive: true
      keepAliveWithoutCalls: true
      negotiationType: plaintext
  • Sử dụng FeignClient để tạo HTTP client gọi đến ServiceB
@FeignClient(value = "serviceb", url = "${serviceb.url}")
public interface ServiceBClient {

  @RequestMapping(method = RequestMethod.GET, value = "${serviceb.path.greet}")
  public MessageB greet(@PathVariable(name = "id") int userId);

}
  • Viết gRPC client gọi đến ServiceC
import net.devh.boot.grpc.client.inject.GrpcClient;
...
  @GrpcClient("servicec")
  private ServiceCBlockingStub serviceCBlockingStub;

  @Override
  public MessageC greet(int id) {
    GreetRequest request = GreetRequest.newBuilder()
        .setId(id)
        .build();

    GreetResponse response = this.serviceCBlockingStub.greet(request);
    return new MessageC(response.getMessage());
  }

Service B

  • Cấu hình port HTTP
server:
  port : 8082
  • Viết Rest controller đơn giản
@RestController
public class Controller {

  @GetMapping("/greet/{id}")
  public MessageB greet(@PathVariable(name = "id") int id) {
    log.info("ServiceB.greet");
    return new MessageB("MessageB greet " + id);
  }
}

ServiceC

  • Cấu hình port gRPC trong file application.yml
grpc:
  server:
    port: 8183
  • Viết gRPC controller
import net.devh.boot.grpc.server.service.GrpcService;
...
@GrpcService
public class GrpcController extends ServiceCGrpc.ServiceCImplBase {

  @Override
  public void greet(GreetRequest request, StreamObserver<GreetResponse> responseObserver) {
    log.info("ServiceC.greet " + request.getId());
    GreetResponse response = GreetResponse.newBuilder()
        .setMessage("Message C")
        .build();

    responseObserver.onNext(response);
    responseObserver.onCompleted();
  }
}

Test sleuth

  • Start 3 service A, B, C

  • Gửi request test đến ServiceA, trong ServiceA sẽ gọi đến ServiceB

$ curl http://localhost:8081/greetb/1

Log service A

2021-11-20 21:01:03.834  INFO [service-A,48c90d4d61668476,48c90d4d61668476] 2433 --- [nio-8081-exec-1] i.c.servicea.entrypoint.Controller       : ServiceA.greetB

và service B

2021-11-20 21:01:03.939  INFO [service-B,48c90d4d61668476,c73d7b8f5c34adc6] 2429 --- [nio-8082-exec-1] i.c.serviceb.entrypoint.Controller       : ServiceB.greet

chung TraceId 48c90d4d61668476

  • Gửi request test đến ServiceA, trong ServiceA sẽ gọi đến ServiceC
$ curl http://localhost:8081/greetc/1

Log service A:

2021-11-20 21:02:54.170  INFO [service-A,9b6f0e1e5e5ca2da,9b6f0e1e5e5ca2da] 2433 --- [nio-8081-exec-2] i.c.servicea.entrypoint.Controller       : ServiceA.greetC

và service C

2021-11-20 21:02:54.340  INFO [service-C,9b6f0e1e5e5ca2da,9cd977fdc2849bb4] 2100 --- [ault-executor-2] i.c.servicec.entrypoint.GrpcController   : ServiceC.greet 1

chung TraceId 9b6f0e1e5e5ca2da

Report lên Zipkin, Jaeger

  • Setup Zipkin bằng docker-compose
version: "3.4"
services:
  zipkin:
    image: openzipkin/zipkin
    ports:
      - 9411:9411

Run docker-compose

docker-compose up -d
  • Trong mỗi service, thêm thông tin dependency
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-sleuth-zipkin</artifactId>
    </dependency>
</dependencies>
  • Thêm cấu hình zipkin trong application.yml
spring:
  zipkin:
    base-url: http://localhost:9411
  • Ngoài ra, có thể sử dụng Jeager server để hiện thị report
    • Setup Jeager server bằng docker-compose và cho phép nhận report theo format của zipkin
version: "3.4"
services:
  jaeger:
    image: jaegertracing/all-in-one:1.17
    ports:
      - 5775:5775/udp
      - 6831:6831/udp
      - 6832:6832/udp
      - 5778:5778
      - 16686:16686
      - 14268:14268
      - 14250:14250
      - 9411:9411
    environment:
      - COLLECTOR_ZIPKIN_HTTP_PORT=9411

Test và xem kết quả trên Jeager UI tại địa chỉ http://localhost:16686/

Reference

Source code ở đây