Sleuth MDC分布式链路追踪
在分布式系统下,存在多链路的微服务调用,在排查问题时需要去追踪各个服务的调用链过程是比较麻烦的,所以需要引入一个分布式链路追踪的机制 https://mp.weixin.qq.com/s/cyGEDNYMDAKwQgNEFQV4cw 其中调
背景
在分布式系统下,存在多链路的微服务调用,在排查问题时需要去追踪各个服务的调用链过程是比较麻烦的,所以需要引入一个分布式链路追踪的机制
引用: https://segmentfault.com/a/1190000041480530
https://mp.weixin.qq.com/s/cyGEDNYMDAKwQgNEFQV4cw
基础知识
- service: 服务名,它默认就取spring.application.name。
- span : 直接翻译就是跨度,比如service1调用service2,这一个来回可以算一个span。每个span都由一个唯一的spanId标识。
- trace: 直接翻译就是追踪的意思。它由一系列的span组成。通常在第一个服务接收到请求时开启一个trace,生成一个唯一的traceId,后续的所有追踪都可以沿用这同一个traceId,这样就可以来唯一的标识这一整个调用链路。
其中调用的开始的spanId == traceId 也称为 root span
原理
一般来说都是引入 Sleuth + MDC的使用来进行链路追踪
Spring Cloud Sleuth的追踪信息中的traceId、spanId会自动的添加到Slf4j的MDC中。所以如果你使用的日志框架是走的SLF4j规范的,则可以在日志中使用traceId、spanId变量。
spring 默认日志框架是 logback,这里就以 logback-spring.xml 为例,在 pattern 中添加变量 %X{traceId}、%X{spanId} 就能读到 MDC 中设置的 traceId、spanId 值。
X-B3-******* 已被移除**
要注意的是,网上一些示例用的是 %X{X-B3-TraceId}、%X{X-B3-SpanId} 来代替上面的值。经亲自踩坑,发现在 spring cloud sleuth 3.0.0 版本发布后,也可对应 spring cloud 2020.0.0 之后,X-B3- 这种写法已经被移除了。可能在其他地方还能见到,但在 MDC 中已经找不到了。
LOG**_LEVEL_**PATTERN
还有人要说了,我在引入了 sleuth之后,并没有创建 logback-spring.xml,也没有配置 traceId、spanId 这类值。或者创建了 logback-spring.xml,但 pattern 中没配置 traceId、spanId 的变量,为什么日志中也会打印出来呢,就像这样:
2022-02-20 23:01:28.662 INFO [a-service,d0a7a4be9e9f3ab4,cb4cb09a83e0c553] 20570 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
原因在于,当项目内没创建 logback-spring.xml 时,默认 pattern 中会包含 LOG_LEVEL_PATTERN 变量,类似于下面这个 logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<property name="CONSOLE_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(${LOG_LEVEL_PATTERN:-%5p}) %magenta(${PID:- }) --- [%15.15t] %cyan(%-40.40logger{39}) : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
</root>
</configuration>
而在 sleuth 框架里面有一个拓展处理类,会将 LOG_LEVEL_PATTERN 变量替换成 %5p [${spring.zipkin.service.name:${spring.application.name:}},%X{traceId:-},%X{spanId:-}] 值。因此就算没设置 %X{traceId}等值,依然会显示。
TraceEnvironmentPostProcessor.java
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Map<String, Object> map = new HashMap();
if (Boolean.parseBoolean(environment.getProperty("spring.sleuth.enabled", "true"))) {
map.put("logging.pattern.level", "%5p [${spring.zipkin.service.name:${spring.application.name:}},%X{traceId:-},%X{spanId:-}]");
}
this.addOrReplace(environment.getPropertySources(), map);
}
另一种方式
不引入Sleuth , 使用AOP 在每个请求进来时生成Trace,然后放入MDC 中,然后可以放入请求头
什么是MDC
MDC(Mapped Diagnostic Contexts) ,翻译过来就是:映射的诊断上下文 。意思是:在日志中 (映射的) 请求ID(requestId),可以作为我们定位 (诊断) 问题的关键字 (上下文)
有了MDC工具,只要在接口或切面植入 put 和 remove 代码,就可以在定位问题时,根据映射的唯一 requestID 快速过滤出某次请求的所有日志。另外,当客户端请求到来时,可以将客户端id,ip,请求参数等信息保存在MDC中,同时在logback.xml中配置,那么会自动在每个日志条目中包含这些信息。
MDC类基本原理其实非常简单,其内部持有一个ThreadLocal实例,用于保存context数据,MDC提供了put/get/clear等几个核心接口,用于操作ThreadLocal中的数据;ThreadLocal中的K-V,可以在logback.xml中声明,即在layout中通过声明 %X{Key} 来打印MDC中保存的此key对应的value在日志中。