一餐屎、目的
開發(fā)排查系統(tǒng)問題用得最多的手段就是查看系統(tǒng)日志场靴,但是在分布式環(huán)境下使用日志定位問題還是比較麻煩,需要借助 全鏈路追蹤ID
把上下文串聯(lián)起來忠怖,本文主要分享基于 Spring Boot
+ Dubbo
框架下 日志鏈路追蹤ID
的實現方案選型思路呢堰。
?
目前大多數分布式追蹤系統(tǒng)的思想模型都來自 Google's Dapper 論文
全鏈路追蹤的核心思想:
- 為每條請求都單獨分配一個唯一的
traceId
用來標識一條請求鏈路,該traceId
會貫穿整個請求處理過程的所有服務 - 每個服務/線程都擁有自己的
spanId
標識凡泣,代表請求的其中一段處理步驟 - 一個請求包含一個
traceId
和一個或多個spanId
日志全鏈路追蹤 就是在每條系統(tǒng)日志里都添加顯示
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. 總結
優(yōu)點:無需編碼褪测,業(yè)務無入侵,可與
SkyWalking
的圖形化界面中使用該ID快速定位各種接口的調用關系潦刃。-
缺點:強耦合
SkyWalking
才能生效- 必須添加sk的
javaagent
- 必須部署
SkyWalking
服務端
- 必須添加sk的
?
2.2. 方案二(sleuth)
Sleuth
是 Spring Cloud
的組件之一侮措,它為 Spring Cloud
實現了一種分布式追蹤解決方案,兼容Zipkin乖杠,HTrace與其他日志追蹤系統(tǒng)
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. 總結
優(yōu)點:業(yè)務無入侵,有豐富的插件進行擴展包括定時任務汛兜、MQ等巴粪。
缺點:
brave-instrumentation-dubbo-rpc
不支持dubbo 2.7.x
需要自行開發(fā)插件。
?
2.3. 方案三(自研)
2.3.1. 無入侵增加 traceId
使用 Logback
的 MDC
機制粥谬,在日志模板中加入 traceId
標識肛根,取值方式為 %X{traceId}
- 系統(tǒng)入口(api網關)創(chuàng)建
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
創(chuàng)建一個過濾器進行 traceId
傳遞
- 服務消費者:負責傳遞鏈路追蹤 ID
- 服務提供者:負責接收 ID 并保存到
MDC
中
?
2.3.4. 總結
優(yōu)點:業(yè)務無入侵感耙,最小依賴褂乍,擴展靈活,適配性強即硼。
缺點:需要自行實現逃片,有大量的開發(fā)工作量。
?
三只酥、方案總結
方案 | 開發(fā)工作量 | 可維護性 | 入侵性 | 性能 |
---|---|---|---|---|
apm-toolkit | 無 | 低 | 業(yè)務無入侵 | 中 |
sleuth | 中 | 中 | 業(yè)務無入侵 | 中 |
自研 | 高 | 高 | 業(yè)務無入侵 | 高 |