前言
在分布式服务架构下,一个 Web 请求从网关流入,有可能会调用多个服务对请求进行处理,拿到最终结果。这个过程中每个服务之间的通信又是单独的网络请求,无论请求经过的哪个服务出了故障或者处理过慢都会对前端造成影响。
处理一个Web请求要调用的多个服务,为了能更方便的查询哪个环节的服务出现了问题,现在常用的解决方案是为整个系统引入分布式链路跟踪。
一、原理
在分布式链路跟踪中有两个重要的概念:跟踪(trace)和 跨度( span)。trace 是请求在分布式系统中的整个链路视图,span 则代表整个链路中不同服务内部的视图,span 组合在一起就是整个 trace 的视图。
在整个请求的调用链中,请求会一直携带 traceid 往下游服务传递,每个服务内部也会生成自己的 spanid 用于生成自己的内部调用视图,并和traceid一起传递给下游服务。
traceid 在请求的整个调用链中始终保持不变,所以在日志中可以通过 traceid 查询到整个请求期间系统记录下来的所有日志。请求到达每个服务后,服务都会为请求生成spanid,而随请求一起从上游传过来的上游服务的 spanid 会被记录成parent-spanid或者叫 pspanid。当前服务生成的 spanid 随着请求一起再传到下游服务时,这个spanid 又会被下游服务当做 pspanid 记录。
分布式链路跟踪中的trace和span 通过在访问日志和业务日志里记录的traceid、spanid 和 pspanid 能完整的还原出整个请求的调用链路视图,对错误排查能起到很大的帮助。
上面就是分布式链路跟踪的原理,我们可以自己实现,也可以依赖 opentracing 这种开源的解决方案。一般是在请求到达网关的开始,生成本次请求的traceid 和 在网关服务内的spanid ,将他们放在HTTP 请求头或者RPC调用的元数据里,在调用下游服务时继续向下传递。下游的RESTful API服务的全局路由中间件和RPC服务的拦截器里会接收请求携带的traceid 和生成当次请求在服务内部的spanid,从上游接收到的 spanid 在这里会被转换成 pspanid。除此之外我们甚至可以把 traceid 和 spanid 注入到一些数据库连接池应用里,让记录的慢SQL日志里同样能打上 traceid 和 spanid 信息,为请求的响应过慢提供有效的分析数据。
二、应用日志打印 traceId 和 spanId
SLF4J 提供了 MDC (Mapped Diagnostic Contexts)功能,可以支持用户定义和修改日志的输出格式以及内容。本文将介绍 SOFATracer 集成的 SLF4J MDC功能,方便用户在只简单修改日志配置文件的前提下输出当前 SOFATracer 上下文 TraceId 以及 SpanId 。
2.1 使用前提
为了在应用中的日志正确打印 TraceId 和 SpanId 参数,我们的日志编程接口需要面向 SLF4J 进行编程,即打印日志的编程接口不要依赖具体的日志实现。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
2.2 引入依赖
如果是 SOFABoot 或者 Spring Boot 的应用具体的日志实现需要大家去引入,我们推荐的日志打印实现是 Logback 和 Log4j2,不推荐 Log4j,同时日志实现建议只使用一个而不要使用多个实现。
- Logback 实现引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
- Log4j2 实现引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<!--SOFABoot 没有管控 log4j2 版本 -->
<version>1.4.2.RELEASE</version>
</dependency>
2.3 配置方法
我们基于 SLF4J MDC 的原理打印对应的 TraceId 和 SpanId,首先我们的应用中的日志编程接口应该面向 SLF4J,如通过如下的方式:
//引入接口
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//构造日志打印实例
private static final Logger logger = LoggerFactory.getLogger(XXX.class);
其次,我们为了正确打印 TraceId 和 SpanId 参数,我们还需要在日志的配置文件中配置 PatternLayout 的额外参数,这两个参数是 %X{SOFA-TraceId} 和 %X{SOFA-SpanId},参数值我们均是从 MDC 中获取的值。
以 Logback 为例配置的 pattern 参数:
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %5p [%X{SOFA-TraceId},
%X{SOFA-SpanId}]
---- %m%n</pattern>
- 关键配置项目:[%X{SOFA-TraceId},%X{SOFA-SpanId}] 作为 Logback pattern 的一部分,在对应的 appender 被调用的时候,会根据 pattern 中的占位符替换为当前线程上下文中 TraceId 和 SpanId 的具体值,当前线程中没有对应的 TraceId 和 SpanId 值时,会用“空字符串”替代。
Log4j2 配置 PatternLayout 样例:
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p
[%X{SOFA-TraceId},%X{SOFA-SpanId}] ---- %m%n " />
Log4j 配置 PatternLayout 样例:
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p %-32t
[%X{SOFA-TraceId},%X{SOFA-SpanId}] - %m%n"/>
</layout>
需要注意的是:[%X{SOFA-TraceId},%X{SOFA-SpanId}] 使我们推荐的打印格式,用户可以根据自己的实际需求场景进行定制。
参考: https://blog.csdn.net/kevin_tech/article/details/117203420
https://www.sofastack.tech/projects/sofa-tracer/print-traceid-spanid/
https://blog.csdn.net/daobuxinzi/article/details/122140045