如何为基于Spring Boot的Thrift微服务集成Zipkin链路追踪?
嘿,你的思路方向是对的——确实需要拦截承载Thrift的HTTP请求,在出入站链路中注入Zipkin的追踪上下文,从而打通整个调用链路。下面我给你梳理一套可落地的实现方案,基于Spring Cloud Sleuth(负责链路追踪上下文管理)和Zipkin(负责链路数据存储与展示)来做:
不管是Thrift服务端还是客户端的Spring Boot应用,都需要引入Spring Cloud Sleuth和Zipkin的依赖。如果用Maven的话,在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>
如果是Gradle,就加:
implementation 'org.springframework.cloud:spring-cloud-starter-sleuth' implementation 'org.springframework.cloud:spring-cloud-starter-zipkin'
因为你的Thrift是通过HTTP传输的,请求类型是x-thrift,所以可以用Spring的HandlerInterceptor来拦截这类请求,创建并管理Zipkin的Span:
首先写一个拦截器类:
import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class ThriftHttpTraceInterceptor implements HandlerInterceptor { private final Tracer tracer; public ThriftHttpTraceInterceptor(Tracer tracer) { this.tracer = tracer; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 只拦截x-thrift类型的请求 if ("x-thrift".equals(request.getHeader("Content-Type"))) { // 从请求头中提取B3追踪信息(如果客户端已经传递),没有的话创建新的Span Span span = tracer.nextSpan().name("thrift-http-server-handle"); // 将Span设置到当前上下文 try (Tracer.SpanInScope ws = tracer.withSpan(span.start())) { // 可以把TraceId放到MDC,方便日志关联 request.setAttribute("zipkin-trace-id", span.context().traceId()); } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 结束Span if ("x-thrift".equals(request.getHeader("Content-Type"))) { Span currentSpan = tracer.currentSpan(); if (currentSpan != null) { currentSpan.end(); } } } }
然后注册这个拦截器到Spring MVC的配置里:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { private final ThriftHttpTraceInterceptor thriftTraceInterceptor; public WebMvcConfig(ThriftHttpTraceInterceptor thriftTraceInterceptor) { this.thriftTraceInterceptor = thriftTraceInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(thriftTraceInterceptor) // 这里配置你的Thrift API的请求路径,比如"/thrift/**" .addPathPatterns("/thrift/**"); } }
客户端在发送Thrift HTTP请求时,需要把当前的Zipkin追踪信息(B3协议的头:X-B3-TraceId、X-B3-SpanId、X-B3-Sampled等)放到请求头里,这样服务端就能接续同一个链路。
如果你的客户端是用RestTemplate来发送HTTP请求的,可以给RestTemplate添加一个客户端拦截器:
import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import java.io.IOException; public class ThriftClientTraceInterceptor implements ClientHttpRequestInterceptor { private final Tracer tracer; public ThriftClientTraceInterceptor(Tracer tracer) { this.tracer = tracer; } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 只处理Thrift请求 if ("x-thrift".equals(request.getHeaders().getFirst("Content-Type"))) { Span currentSpan = tracer.currentSpan(); if (currentSpan != null) { // 添加B3协议的追踪头 HttpHeaders headers = request.getHeaders(); headers.add("X-B3-TraceId", currentSpan.context().traceId()); headers.add("X-B3-SpanId", currentSpan.context().spanId()); headers.add("X-B3-Sampled", currentSpan.context().sampled() ? "1" : "0"); } } return execution.execute(request, body); } }
然后把这个拦截器注册到RestTemplate:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; import java.util.Collections; @Configuration public class RestTemplateConfig { private final Tracer tracer; public RestTemplateConfig(Tracer tracer) { this.tracer = tracer; } @Bean public RestTemplate thriftRestTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setInterceptors(Collections.singletonList(new ThriftClientTraceInterceptor(tracer))); return restTemplate; } }
如果你的客户端是自定义的Thrift HTTP客户端,逻辑也是一样的:在构建HTTP请求时,从Tracer中获取当前Span的上下文,把B3头添加到请求里就行。
在服务端和客户端的application.yml(或application.properties)里添加Zipkin的基础配置:
spring: zipkin: base-url: http://localhost:9411/ # 你的Zipkin服务地址 sleuth: sampler: probability: 1.0 # 采样率,开发环境设为1.0,生产环境可以调整
- 启动Zipkin服务(可以用Docker快速启动:
docker run -d -p 9411:9411 openzipkin/zipkin) - 启动Thrift服务端和客户端应用
- 调用客户端的接口,让它发起Thrift HTTP请求到服务端
- 访问
http://localhost:9411,在Zipkin UI里就能看到完整的调用链路,包含客户端和服务端的Span信息
另外有个小提示:如果你的Thrift服务端是用Thrift官方的Servlet来处理请求的(不是Spring MVC的Controller),那可能需要用Filter来代替HandlerInterceptor,拦截逻辑和上面差不多,只是换成Filter的doFilter方法而已。
内容的提问来源于stack exchange,提问作者Manuel Jain

