
一、目的
開發排查系統問題用得最多的手段就是查看系統日志,但是在分布式環境下使用日志定位問題還是比較麻煩,需要借助 全鏈路追蹤ID 把上下文串聯起來,本文主要分享基於 Spring Boot + Dubbo 框架下 日志鏈路追蹤ID 的實現方案選型思路。
目前大多數分布式追蹤系統的思想模型都來自 Google's Dapper 論文

全鏈路追蹤的核心思想:
- 為每條請求都單獨分配一個唯一的
traceId用來標識一條請求鏈路,該traceId會貫穿整個請求處理過程的所有服務 - 每個服務/線程都擁有自己的
spanId標識,代表請求的其中一段處理步驟 - 一個請求包含一個
traceId和一個或多個spanId
日志全鏈路追蹤 就是在每條系統日志里都添加顯示
traceId和spanId信息

二、方案選型
2.1. 方案一(apm-toolkit)
這是 SkyWalking 的一個日志插件,通過這個插件可以在日志中輸出
traceId
2.1.1. 使用方式
配置依賴,在 pom 文件中添加以下內容
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.1.0</version>
</dependency>
配置日志模板,修改 logback-spring.xml 文件中 Appender 元素的 encoder 為以下內容
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%tid] [%thread] %-5level %logger{35} - %msg%n</pattern>
</layout>
</encoder>
ps: pattern 中的內容按需修改,其中的 %tid 就是相當於 traceId,默認 TID:N/A,當有請求調用時會生成並顯示 traceId
2.1.2. 總結
-
優點:無需編碼,業務無入侵,可與
SkyWalking的圖形化界面中使用該ID快速定位各種接口的調用關系。 -
缺點:強耦合
SkyWalking才能生效- 必須添加sk的
javaagent - 必須部署
SkyWalking服務端
- 必須添加sk的
2.2. 方案二(sleuth)
Sleuth 是 Spring Cloud 的組件之一,它為 Spring Cloud 實現了一種分布式追蹤解決方案,兼容Zipkin,HTrace與其他日志追蹤系統
2.2.1. 使用方式
配置父依賴,在 pom 文件中添加以下內容管理版本號
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth</artifactId>
<version>2.2.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
配置依賴,在 pom 文件中添加以下內容
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
適配dubbo,要讓 sleuth 支持 dubbo 框架,需要增加以下兩個步驟:
首先添加 dubbo 的插件依賴
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-dubbo-rpc</artifactId>
<version>5.12.6</version>
</dependency>
配置 dubbo 過濾器
dubbo:
provider:
filter: tracing
consumer:
filter: tracing
配置日志模板,修改 logback-spring.xml 文件中 Appender 元素的 encoder 為以下內容
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{X-B3-TraceId},%X{X-B3-SpanId}] [%thread] %-5level %logger{35} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
ps: pattern 中的內容按需修改,其中的 %X{X-B3-TraceId} 為 traceId,%X{X-B3-SpanId} 為 spanId
2.2.2. 總結
-
優點:業務無入侵,有豐富的插件進行擴展包括定時任務、MQ等。
-
缺點:
brave-instrumentation-dubbo-rpc不支持dubbo 2.7.x需要自行開發插件。
2.3. 方案三(自研)
2.3.1. 無入侵增加 traceId
使用 Logback 的 MDC 機制,在日志模板中加入 traceId 標識,取值方式為 %X{traceId}
- 系統入口(api網關)創建
traceId的值 - 使用
MDC保存traceId - 修改
logback配置文件模板格式添加標識%X{traceId}
MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能。
2.3.2. 跨線程傳遞
解決 traceId 跨線程丟失問題

由於 MDC 內部使用的是 ThreadLocal 所以只有本線程才有效,子線程和下游的服務 MDC 里的值會丟失;
需要解決 Spring 的各種線程池與異步方法的父子線程間傳遞。
解決思路:重寫一個 MDCAdapter 使用阿里的 TransmittableThreadLocal 替換原來的 ThreadLocal 對象,解決各種線程池(ExecutorService / ForkJoinPool / TimerTask)父子進程傳值問題。
需要使用
TtlRunnable和TtlCallable來修飾傳入線程池的Runnable和Callable
2.3.3. 跨進程傳遞
解決 traceId 跨進程丟失問題
dubbo服務 使用 org.apache.dubbo.rpc.Filter 創建一個過濾器進行 traceId 傳遞
- 服務消費者:負責傳遞鏈路追蹤 ID
- 服務提供者:負責接收 ID 並保存到
MDC中
2.3.4. 總結
-
優點:業務無入侵,最小依賴,擴展靈活,適配性強。
-
缺點:需要自行實現,有大量的開發工作量。
三、方案總結
| 方案 | 開發工作量 | 可維護性 | 入侵性 | 性能 |
|---|---|---|---|---|
| apm-toolkit | 無 | 低 | 業務無入侵 | 中 |
| sleuth | 中 | 中 | 業務無入侵 | 中 |
| 自研 | 高 | 高 | 業務無入侵 | 高 |
掃碼關注有驚喜!

