此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。
本文第一部分为调用链、OpenTracing、Zipkin和Jeager的简述;第二部分为Spring Boot及Dubbo zipkin 链路追踪组件埋点
本系列文章中所使用的框架版本为Spring Boot 2.0.3-RELEASE,Spring 5.0.7-RELEASE,Dubbo 2.6.2。
调用链
在广义上,一个调用链代表一个事务或者流程在(分布式)系统中的执行过程。在 OpenTracing 标准中,调用链是多个 Span 组成的一个有向无环图(Directed Acyclic Graph,简称 DAG),每一个 Span代表调用链中被命名并计时的连续性执行片段。
下图是一个分布式调用的例子:客户端发起请求,请求首先到达负载均衡器,接着经过认证服务、计费服务,然后请求资源,最后返回结果。

数据被采集存储后,分布式追踪系统一般会选择使用包含时间轴的时序图来呈现这个调用链。

OpenTracing
为了解决不同的分布式追踪系统 API 不兼容的问题,诞生了 OpenTracing 规范。 OpenTracing 是一个轻量级的标准化层,它位于应用程序/类库和追踪或日志分析程序之间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+-------------+  +---------+  +----------+  +------------+
| Application |  | Library |  |   OSS    |  |  RPC/IPC   |
|    Code     |  |  Code   |  | Services |  | Frameworks |
+-------------+  +---------+  +----------+  +------------+
       |              |             |             |
       |              |             |             |
       v              v             v             v
  +------------------------------------------------------+
  |                     OpenTracing                      |
  +------------------------------------------------------+
     |                |                |               |
     |                |                |               |
     v                v                v               v
+-----------+  +-------------+  +-------------+  +-----------+
|  Tracing  |  |   Logging   |  |   Metrics   |  |  Tracing  |
| System A  |  | Framework B |  | Framework C |  | System D  |
+-----------+  +-------------+  +-------------+  +-----------+
OpenTracing 的优势
- OpenTracing 已进入 CNCF,正在为全球的分布式追踪,提供统一的概念和数据标准。
- OpenTracing 通过提供平台无关、厂商无关的 API,使得开发人员能够方便的添加(或更换)追踪系统的实现。
Zipkin
Zipkin是一种分布式跟踪系统。它有助于收集解决微服务架构中的延迟问题所需的时序数据。它管理这些数据的收集和查找。Zipkin的设计基于 Google Dapper论文。

Jeager
Jaeger受Dapper和OpenZipkin的启发,是Uber Technologies公开发布的分布式跟踪系统。它用于监视和排除基于微服务的分布式系统,包括:
- 分布式上下文传播
- 分布式事务监控
- 根本原因分析
- 服务依赖分析
- 性能/延迟优化
Uber发表了一篇博客文章Evolving Distributed Tracing at Uber,在那里他们解释了Jaeger所做的架构性选择的历史和原因

依赖
brave依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<properties>
     <brave.version>5.4.2</brave.version>
     <zipkin-reporter.version>2.7.9</zipkin-reporter.version>
 </properties>
 <dependencyManagement>
     <dependencies>
         <!-- 引入 zipkin brave 的 BOM 文件 -->
         <dependency>
             <groupId>io.zipkin.brave</groupId>
             <artifactId>brave-bom</artifactId>
             <version>${brave.version}</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>
         <!-- 引入 zipkin repoter 的 BOM 文件 -->
         <dependency>
             <groupId>io.zipkin.reporter2</groupId>
             <artifactId>zipkin-reporter-bom</artifactId>
             <version>${zipkin-reporter.version}</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>
     </dependencies>
 </dependencyManagement>
 <dependencies>
     <!-- 1. brave 的 spring bean 支持 -->
     <dependency>
         <groupId>io.zipkin.brave</groupId>
         <artifactId>brave-spring-beans</artifactId>
         <version>${brave.version}</version>
     </dependency>
     <!-- 2. 在 SLF4J 的 MDC (Mapped Diagnostic Context) 中支持 traceId 和 spanId -->
     <dependency>
         <groupId>io.zipkin.brave</groupId>
         <artifactId>brave-context-slf4j</artifactId>
         <version>${brave.version}</version>
     </dependency>
     <!-- 3. 使用 okhttp3 作为 reporter -->
     <dependency>
         <groupId>io.zipkin.reporter2</groupId>
         <artifactId>zipkin-sender-okhttp3</artifactId>
         <version>${zipkin-reporter.version}</version>
     </dependency>
 </dependencies>
Dubbo & brave
1
2
3
4
5
6
<!-- 1. brave 对 dubbo 的集成 -->
<dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave-instrumentation-dubbo-rpc</artifactId>
    <version>${brave.version}</version>
</dependency>
Spring web & brave
1
2
3
4
5
6
7
8
9
10
11
<!-- brave 对 spring web 的集成 -->
<dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave-instrumentation-spring-web</artifactId>
    <version>${brave.version}</version>
</dependency>
<dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave-instrumentation-spring-webmvc</artifactId>
    <version>${brave.version}</version>
</dependency>
使用方法
zipkin
1
zipkin.url = http://<domain>/api/v2/spans?userName=<username>&userKey=<userkey>
Spring bean 注入
- @ComponentScan
- 配置 autoconfigure(spring.factories)。1 org.springframework.boot.autoconfigure.EnableAutoConfiguration=brave.webmvc.TracingConfiguration 
服务端
1
dubbo.provider.filter=tracing
客户端
1
dubbo.consumer.filter=tracing
tracing为链路追踪filter组件启用
插件埋点
RPC插件埋点
通过brave.dubbo.rpc的TracingFilter实现Dubbo Filter接口,注入对应的spring bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Configuration
public class TracingConfiguration {
    @Value("${zipkin.url}")
    private String url;
    /**
     * Configuration for how to send spans to Zipkin
     */
    @Bean
    Sender sender() {
        return OkHttpSender.create(url);
    }
    /**
     * Configuration for how to buffer spans into messages for Zipkin
     */
    @Bean
    AsyncReporter<Span> spanReporter() {
        return AsyncReporter.create(sender());
    }
    /**
     * Controls aspects of tracing such as the name that shows up in the UI
     */
    @Bean
    Tracing tracing(@Value("${spring.application.name}") String serviceName) {
        return Tracing.newBuilder()
                .localServiceName(serviceName)
                .propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "user-name"))
                .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder()
                        .addScopeDecorator(MDCScopeDecorator.create())
                        .build()
                )
                .spanReporter(spanReporter()).build();
    }
}
HTTP插件埋点
通过brave.servlet的TracingFilter实现Servlet Filter接口,注入对应的spring bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@Configuration
@Import(SpanCustomizingAsyncHandlerInterceptor.class)
public class HttpTracingConfiguration {
    @Value("${zipkin.url}")
    private String url;
    /**
     * Configuration for how to send spans to Zipkin
     */
    @Bean
    Sender sender() {
        return OkHttpSender.create(url);
    }
    /**
     * Configuration for how to buffer spans into messages for Zipkin
     */
    @Bean
    AsyncReporter<Span> spanReporter() {
        return AsyncReporter.create(sender());
    }
    /**
     * Controls aspects of tracing such as the name that shows up in the UI
     */
    @Bean
    Tracing tracing(@Value("${spring.application.name}") String serviceName) {
        return Tracing.newBuilder()
                .localServiceName(serviceName)
                .propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "user-name"))
                .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder()
                        .addScopeDecorator(MDCScopeDecorator.create())
                        .build()
                )
                .spanReporter(spanReporter()).build();
    }
    /**
     * decides how to name and tag spans. By default they are named the same as the http method.
     */
    @Bean
    HttpTracing httpTracing(Tracing tracing) {
        return HttpTracing.create(tracing);
    }
    /**
     * Creates client spans for http requests
     * <p>
     * We are using a BPP as the Frontend supplies a RestTemplate bean prior to this configuration
     */
    @Bean
    BeanPostProcessor connectionFactoryDecorator(final BeanFactory beanFactory) {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) {
                return bean;
            }
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) {
                if (!(bean instanceof RestTemplate)) {
                    return bean;
                }
                RestTemplate restTemplate = (RestTemplate) bean;
                List<ClientHttpRequestInterceptor> interceptors =
                        new ArrayList<>(restTemplate.getInterceptors());
                interceptors.add(0, getTracingInterceptor());
                restTemplate.setInterceptors(interceptors);
                return bean;
            }
            // Lazy lookup so that the BPP doesn't end up needing to proxy anything.
            ClientHttpRequestInterceptor getTracingInterceptor() {
                return TracingClientHttpRequestInterceptor.create(beanFactory.getBean(HttpTracing.class));
            }
        };
    }
    /**
     * Creates server spans for http requests
     */
    @Bean
    Filter tracingFilter(HttpTracing httpTracing) {
        return TracingFilter.create(httpTracing);
    }
}
参考资料:
