前言
本文将从零搭建分布式链路追踪客户端工具包的Starter ,并将在后续文章中逐步丰富支持的场景。这里首先将搭建一个最基础的Starter ,能提供的功能和1. 看完这篇文章我奶奶都懂Opentracing了一文中的示例demo类似。
相关版本依赖如下。
opentracing-api 版本:0.33.0
opentracing-spring-web 版本:4.1.0
jaeger-client 版本:1.8.1
Springboot 版本:2.7.6
正文
一. 基础Starter实现
在基础Starter 中,主要是提供Servlet 的过滤器 和RestTemplate 的拦截器 ,我们后续的复杂功能,就将在这个基础Starter上一点一点的丰富。
首先下图是Starter的工程结构。
在基础Starter 中,Servlet的链路过滤器实现如下。
java
public class HoneyTracingFilter implements Filter {
private final Tracer tracer;
public HoneyTracingFilter(Tracer tracer) {
this.tracer = tracer;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
SpanContext extractedSpanContext = tracer.extract(Format.Builtin.HTTP_HEADERS,
new HttpServletRequestExtractAdapter(request));
Span span = tracer.buildSpan(request.getMethod())
.asChildOf(extractedSpanContext)
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
.start();
try (Scope scope = tracer.activateSpan(span)) {
filterChain.doFilter(servletRequest, servletResponse);
} catch (IOException | ServletException e) {
Tags.ERROR.set(span, Boolean.TRUE);
throw e;
} finally {
span.finish();
}
}
}
RestTemplate的链路拦截器实现如下。
java
/**
* RestTemplate客户端的分布式链路追踪拦截器。
*/
public class HoneyRestTemplateTracingInterceptor implements ClientHttpRequestInterceptor {
private final Tracer tracer;
public HoneyRestTemplateTracingInterceptor(Tracer tracer) {
this.tracer = tracer;
}
@NotNull
public ClientHttpResponse intercept(@NotNull HttpRequest request, @NotNull byte[] body,
ClientHttpRequestExecution execution) throws IOException {
Span span = tracer.buildSpan(HONEY_REST_TEMPLATE_NAME)
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
.start();
tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS, new HttpHeadersCarrier(request.getHeaders()));
try (Scope scope = tracer.activateSpan(span)) {
return execution.execute(request, body);
} catch (IOException e) {
Tags.ERROR.set(span, Boolean.TRUE);
throw e;
} finally {
span.finish();
}
}
}
打印链路日志的HoneySpanReporter目前不打印任何日志,后面再添加打印逻辑,实现如下。
java
public class HoneySpanReporter implements Reporter {
public void report(JaegerSpan span) {
// todo 打印链路日志
// todo 暂不做任何事情
}
public void close() {
}
}
我们使用HoneyTracingProperties 来读取链路相关的配置,目前仅读取一个开关enabled,如下所示。
java
/**
* 分布式链路追踪配置属性类。
*/
@ConfigurationProperties("honey.tracing")
public class HoneyTracingProperties {
private boolean enabled;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
使用到的一些常量,存放在CommonConstants中,如下所示。
java
public class CommonConstants {
public static final double DEFAULT_SAMPLE_RATE = 1.0;
public static final String HONEY_TRACER_NAME = "HoneyTracer";
public static final String HONEY_REST_TEMPLATE_NAME = "HoneyRestTemplate";
public static final String ALL_URL_PATTERN_STR = "/*";
}
再接下来就是三个自动装配类,其中HoneyRestTemplateTracingConfig 负责注册HoneyRestTemplateTracingInterceptor 给容器中所有的RestTemplate ,HoneyTracingConfig 负责注册Tracer 到Spring 容器,HoneyTracingFilterConfig 负责注册HoneyTracingFilter,实现如下所示。
java
/**
* RestTemplate分布式链路追踪配置类。
*/
@ConditionalOnBean(RestTemplate.class)
@Configuration
@AutoConfigureAfter(HoneyTracingConfig.class)
public class HoneyRestTemplateTracingConfig {
public HoneyRestTemplateTracingConfig(List<RestTemplate> restTemplates, Tracer tracer) {
for (RestTemplate restTemplate : restTemplates) {
// todo 还要判断RestTemplate里是否已经添加了HoneyRestTemplateTracingInterceptor
restTemplate.getInterceptors().add(new HoneyRestTemplateTracingInterceptor(tracer));
}
}
}
java
/**
* 分布式链路追踪配置类。
*/
@Configuration
@EnableConfigurationProperties({HoneyTracingProperties.class})
@ConditionalOnProperty(prefix = "honey.tracing", name = "enabled", havingValue = "true", matchIfMissing = true)
public class HoneyTracingConfig {
@Bean
@ConditionalOnMissingBean(Sampler.class)
public Sampler sampler() {
return new ProbabilisticSampler(DEFAULT_SAMPLE_RATE);
}
@Bean
@ConditionalOnMissingBean(Reporter.class)
public Reporter reporter() {
return new HoneySpanReporter();
}
@Bean
@ConditionalOnMissingBean(Tracer.class)
public Tracer tracer(Sampler sampler, Reporter reporter) {
return new JaegerTracer.Builder(HONEY_TRACER_NAME)
.withTraceId128Bit()
.withZipkinSharedRpcSpan()
.withSampler(sampler)
.withReporter(reporter)
.build();
}
}
java
/**
* Servlet过滤器配置类。
*/
@Configuration
@AutoConfigureAfter(HoneyTracingConfig.class)
public class HoneyTracingFilterConfig {
@Bean
public FilterRegistrationBean<HoneyTracingFilter> honeyTracingFilter(Tracer tracer) {
HoneyTracingFilter honeyTracingFilter = new HoneyTracingFilter(tracer);
FilterRegistrationBean<HoneyTracingFilter> filterFilterRegistrationBean
= new FilterRegistrationBean<>(honeyTracingFilter);
filterFilterRegistrationBean.addUrlPatterns(ALL_URL_PATTERN_STR);
return filterFilterRegistrationBean;
}
}
最后就是spring.factories 和pom文件,内容如下所示。
yaml
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.honey.tracing.config.HoneyTracingConfig,\
com.honey.tracing.config.HoneyTracingFilterConfig,\
com.honey.tracing.config.HoneyRestTemplateTracingConfig
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>honey-tracing</artifactId>
<groupId>com.honey</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>honey-starter-tracing</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.jaegertracing</groupId>
<artifactId>jaeger-client</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
还有一点要补充,父工程的pom文件如下所示。
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
</parent>
<groupId>com.honey</groupId>
<artifactId>honey-tracing</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-api</artifactId>
<version>0.33.0</version>
</dependency>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-web</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>io.jaegertracing</groupId>
<artifactId>jaeger-client</artifactId>
<version>1.8.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>honey-starter-tracing</module>
<module>honey-tracing-example</module>
</modules>
</project>
二. 测试demo搭建
我们需要一个demo 来测试我们的基础Starter ,并且随着后续Starter 支持的功能的增多,我们的demo也要相应的扩展更多的场景。
首先是demo的目录结构,如下所示。
1. example-service-1
RestTemplateConfig 简单的向Spring 容器注册了一个RestTemplate 的bean,如下所示。
java
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
RestTemplateController 提供了发送接口,如下所示。
java
@RestController
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/send")
public void send(String url) {
restTemplate.getForEntity(url, Void.class);
}
}
最后是application.yml 和pom文件,如下所示。
yaml
server:
port: 8080
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>honey-tracing-example</artifactId>
<groupId>com.honey</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>example-service-1</artifactId>
<dependencies>
<dependency>
<groupId>com.honey</groupId>
<artifactId>honey-starter-tracing</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-api</artifactId>
<version>0.33.0</version>
</dependency>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-web</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>io.jaegertracing</groupId>
<artifactId>jaeger-client</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
2. example-service-2
RestTemplateController 提供了接收接口,如下所示。
java
@RestController
public class RestTemplateController {
@GetMapping("/receive")
public void receive() {
System.out.println();
}
}
最后是application.yml 和pom文件,如下所示。
yaml
server:
port: 8081
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>honey-tracing-example</artifactId>
<groupId>com.honey</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>example-service-2</artifactId>
<dependencies>
<dependency>
<groupId>com.honey</groupId>
<artifactId>honey-starter-tracing</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-api</artifactId>
<version>0.33.0</version>
</dependency>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-web</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>io.jaegertracing</groupId>
<artifactId>jaeger-client</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
总结
在本文,首先实现了一个基础的Starter 包,为分布式链路追踪提供了Servlet 过滤器和RestTemplate 拦截器,其次实现了一个demo ,后续分布式链路追踪Starter 的功能,将用该demo完成测试。