Dubbo日志链路追踪TraceId选型
链路追踪ID
一、目的
开发排查系统问题用得最多的手段就是查看系统日志,但是在分布式环境下使用日志定位问题还是比较麻烦,需要借助 全链路追踪ID
把上下文串联起来,本文主要分享基于 Spring Boot
+ Dubbo
框架下 日志链路追踪ID
的实现方案选型思路。
目前大多数分布式追踪系统的思想模型都来自 Google's Dapper 论文
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
跨线程丢失问题
file
由于 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 |
中 |
中 |
业务无入侵 |
中 |
自研 |
高 |
高 |
业务无入侵 |
高 |
- Hadoop(十四)MapReduce原理分析
- dubbox 增加google-gprc/protobuf支持
- 统计02:怎样描绘数据
- ActiveMQ笔记(1):编译、安装、示例代码
- centos ssh终端下高亮显示git分支名
- Django ORM模型:想说爱你不容易
- IE7下元素的 'padding-top' 遇到 'clear' 特性在某些情况下复制到 'padding-bottom'
- IE7下元素的 'padding-top' 遇到 'clear' 特性在某些情况下复制到 'padding-bottom'
- ARM处理器:开放者的逆袭
- 从5个方面对比微信小程序和App
- ActiveMQ笔记(7):如何清理无效的延时消息?
- JS魔法堂:再识Bitwise Operation & Bitwise Shift
- Hadoop(十三)分析MapReduce程序
- mac机上搭建php56/nginx 1.8.x/thinkphp 3.2.x/gearman扩展/seaslog扩展/redis扩展环境
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- TiKV 源码解析系列文章(二十)Region Split 源码解析
- 轻松构建Tomcat源码
- Flutter中Contrainer 组件的宽高限制分析
- 10张图带你深入理解Docker容器和镜像
- 手摸手教你撸一个微服务框架-关于服务端的处理
- 聊聊claudb的string command
- windows下安装nodejs
- 【Java面试总结】Java集合
- 《JavaScript 模式》读书笔记(8)— DOM和浏览器模式1
- 《JavaScript 模式》读书笔记(8)— DOM和浏览器模式2
- 5000字 | 24张图带你彻底理解21种并发锁
- JavaScript-变量
- Android应用安装卸载监控
- 细数这些年被困扰过的 TS 问题
- 将WordPress插件Elementor标签插入到WordPress模板文件以使用Elementor编辑