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个

  1. 网关(http或者rpc)

  2. 各类定时任务

  3. 消息队列消费者(本项目暂时不涉及)

传递

主要几种

  1. http header头

  2. rpc header或者扩展字段(本项目不涉及)

  3. 消息队列传递

实现

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即可

日志配置

最后更新于