spring cloud应用分布式链路跟踪简易实现
spring cloud应用分布式链路跟踪简易实现
背景
在一个项目中,一个请求通常会有调用各种上下游,最常见的有数据库、缓存、消息队列以及下游服务;在链路比较复杂的时,定位问题以及分析问题会非常麻烦,因此需要通过某种方式,定位一次请求的所有上下游链路
目标
实现单次请求的上下游链路跟踪
现状
1、服务基于spring cloud,网关使用spring cloud gateway
2、服务间调用使用open feign调用,没有rpc
3、使用mybatis-plus操作数据库
4、使用redis缓存
5、暂时没有使用消息队列
业界方案
spring cloud 通常spring slueth + zipkin/skywalking实现
1、slueth实现链路traceId + spanId生成
2、数据上报zipkin/skywalking实现数据展示
我们当前的主要问题在于,预算不够,暂时无法申请资源部署zipkin/skywalking;因此可以考虑只使用slueth实现traceId + spanId生成,待需要时再接入zipkin/skywalking或者其他日志收集系统(比如ELK,SLS等)
这里更多的是探讨一种简易实现,只生成traceId + spanId,结合本地日志系统,实现方案定位问题即可
简易方案
实现的重点在于如何生成traceId和spanId,并打印到日志系统
traceId:标识一次请求;因此同一请求的上下游链路,traceId必须相同,不同请求traceId必须不相同
spanId:标识一次请求中的不同调用链路,需要可以通过spanId还原出链路调用时序
traceId生成
这里直接使用uuid
spanId
我们通过x.y.z格式表示
相同长度的spanId标识双方处于同一层次;比如0.1和0.2表示一次请求的第1次和第2次子链路
相同前缀,长度较长者为子调用;比如0.1.1表示0.1链路的子链路
整体格式如下
spanId如何生成接下来会细说
入口
入口主要3个
网关(http或者rpc)
各类定时任务
消息队列消费者(本项目暂时不涉及)
传递
主要几种
http header头
rpc header或者扩展字段(本项目不涉及)
消息队列传递
实现
traceId、spanId生成
入口传递
网关
定时任务
定时任务主要使用spring task通过@Scheduled定义任务
通过@EnableScheduling注解开始代码跟踪,发现并没有提供可以针对task扩展的部分;因此此处使用AOP实现
传递
http调用通过open feign,因此针对feign做拦截,在调用时将traceId和spanId放入header即可
针对数据库、缓存的传递,自定义实现暂时也没有什么好的方法,要么通过AOP,要么通过Java agent做增强,或者手动埋点(代码侵入性较强,一般不这么做);这里暂时先忽略不做
针对消息队列,通常可以放在Message扩展字段里面传递,这里不做探讨
接收
http接收比较简单,通过springmvc interceptor即可
消息队列同理,在Message扩展字段中去取即可
发送(生产者)
消费(消费者)
异步执行处理
使用自定义线程池
通常两种方式,一种是提交任务时,使用封装过的callable或者runnable
代码如下
callable同理
还有一种是针对线程池做封装
@Async注解
一种是使用了自定义线程池,参考上面
还有一种使用默认线程池,通过@EnableAsync注解个跟踪,可以通过实现自定义的AsyncConfigurer实现
默认是AsyncConfigurerSupport
TaskExecutorBuilder是spring系统内部实现,可以自动感知到对任务的增强,参考:org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration

所以只需要自定一个TaskDecorator即可
日志配置
最后更新于