4. 分布式链路追踪客户端工具包Starter设计

前言

本文将从零搭建分布式链路追踪客户端工具包的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 给容器中所有的RestTemplateHoneyTracingConfig 负责注册TracerSpring 容器,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.factoriespom文件,内容如下所示。

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 容器注册了一个RestTemplatebean,如下所示。

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.ymlpom文件,如下所示。

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.ymlpom文件,如下所示。

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完成测试。

相关推荐
向前看-3 小时前
验证码机制
前端·后端
xlsw_3 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹4 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭4 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
Data跳动4 小时前
Spark内存都消耗在哪里了?
大数据·分布式·spark
暮湫5 小时前
泛型(2)
java
超爱吃士力架5 小时前
邀请逻辑
java·linux·后端
南宫生5 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石5 小时前
12/21java基础
java
李小白665 小时前
Spring MVC(上)
java·spring·mvc