使用Mdc解决链路追踪

使用Mdc解决链路追踪,第1张

用aop 拦截所有controller 的请求 生成全局traceId

@Pointcut("execution(* *.*.controller..*.*(..))")

public void cutOffPoint() {

}

@Before("cutOffPoint()")

public void doBefore(JoinPoint joinPoint)throws Throwable {

 MDC.put("traceId", "生成全局traceid") 

}

在logback.xml中配置 

<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">

    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %X{TRACEID}- %msg%n

</appender>

到此可以满足大部分的需求

需要注意的部分就是如果用到线程池需要特殊处理

可以参考

https://blog.csdn.net/yangcheng33/article/details/80796129

下面说下我的实现 

项目中主要使用ThreadPoolTaskExecutor因此对他进行改造

复制public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport

implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {

//内容为ThreadPoolTaskExecutor  类中的内容 我这里从新写了一下几个方法

initializeExecutor 方法中  super.execute(new MDCRunnable(decorated))

submitListenable方法中 

ListenableFutureTask future =new ListenableFutureTask<>(new MDCCallable<>(task))

//住所有执行的方法都要改为MDCRunnable MDCCallable

}

MDCRunnable类 为

public class MDCRunnable implements Runnable {

private final Runnable runnable

    private transient final Map_cm = MDC.getCopyOfContextMap()

    public MDCRunnable(Runnable runnable) {

        this.runnable = runnable

    }

@Override

    public void run() {

if (_cm !=null) {

             MDC.setContextMap(_cm)

        }

try {

runnable.run()

        }finally {

MDC.clear()

        }}}

public class MDCCallable implements Callable {

private final Callablecallable

    private transient final Map_cm = MDC.getCopyOfContextMap()

    public MDCCallable(Callable callable) {

this.callable = callable

    }

@Override

    public V call()throws Exception {

if (_cm !=null) {

MDC.setContextMap(_cm)

        }

try {

return callable.call()

        }finally {

MDC.clear()

        }}}

至此我们项目中就完美解决了traceId在本项目中传递

如果是在微服务中传递则 我们微服务之间的调用使用的restTemplate

所有增加一个拦截器

@Configuration

public class RestTemplateConfiguration {

@Bean

@LoadBalanced

    public RestTemplateloadbalancedRestTemplate() {

SimpleClientHttpRequestFactory requestFactory =new SimpleClientHttpRequestFactory()

        // 超时时间,单位毫秒

        requestFactory.setConnectTimeout(3 *1000)

        requestFactory.setReadTimeout(3 *60 *1000)

        RestTemplate restTemplate =new RestTemplate(requestFactory)

        restTemplate.getInterceptors().add(new TraceIdInterceptor())

        return restTemplate

    }

public class TraceIdInterceptorimplements ClientHttpRequestInterceptor {

@Override

    public ClientHttpResponseintercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)throws IOException {

HttpHeaders headers = request.getHeaders()

        traceId= MDC.get("traceId")

        if(StringUtils.isNotBlank(traceId)){

headers.add("traceId",traceId)

        }

return execution.execute(request,body)

    }}}

然后在下游服务器写一个拦截器就可在request head中可以获取traceidl 

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()

String traceId = request.getHeader("traceId")

至此就完成了所有的traceid 本项目 线程 项目之间 都可以用了

Spring Cloud系列之Eureka

Spring Cloud系列之配置中心Config

Spring Cloud系列之gateway

Spring Cloud系列之Feign

Spring Cloud系列之Hystrix

Spring Cloud系列之链路追踪

在微服务架构下,一次请求至少经过三四次服务调用完成,多则跨越七八个,那么问题接踵而来

基于上面得问题,就应运而生了分布式调用链路追踪技术

本质:记录日志,进行分析、排障

一次请求我们可以想象成一棵树,如下图:

Trace :一次完整的分布式调用跟踪链路,由一系列Span 组成的一个树状结构

TraceId :为了实现请求跟踪,当请求发送到分布式系统的入口时,我们为该请求创建一个唯一跟踪表示traceId,同时在分布式系统内部流转时,框架始终保持该唯一标识。

Span(跨度) : 追踪服务调基本结构,表示跨服务的一次调用; 多span形成树形结构,组合成一次Trace追踪记录。对于一个span节点必须有开始和结束节点,通过记录开始和结束的时间戳,就可以计算出调用该服务的一个耗时。每一个Span通过一个64位ID来进行唯一标识即spanId,并通过另一个64位ID对Span所在的Trace进行唯一标识。(注意:启动一个Trace的初始化Span被叫作 Root Span ,它的 Span ID 和 Trace Id 相同。)

span除了时间戳外,还可以包含一些其他元素数据,比如请求信息 parentId、traceId、spanId。

Annotation :用来及时记录一个事件的存在。通过引入 Brave 库,我们不用再去设置一系列的特别事件,从而让 Zipkin 能够知道客户端和服务器是谁、请求是从哪里开始的、又到哪里结束。出于学习的目的,还是把这些事件在这里列举一下:

Cs CLIENT_SEND,客户端发起请求

Cr CLIENT_RECIEVE,客户端收到响应

Sr SERVER_RECIEVE,服务端收到请求

Ss SERVER_SEND,服务端发送结果

下面我们就重点来了解下 sleuth + zipkin

追踪服务框架,Sleuth就是通过记录日志的方式来追踪数据的,我们可以通过记录的日志,进行:

我们往往把spring cloud sleuth和zipkin一起使用,把sleuth的数据信息发送给zipkin进行聚合,利用zipkin存储进行数据展示,具体实现流程如下:

在application.yml中添加日志级别

运行项目,发起请求,我们可以看到user-api打印结果:

运行项目,打开地址 http://localhost:10005/zipkin/ ,界面如下:

其中type方式有三种

还有 采样率 的问题

生产环境下,如果采用全部采集,那产生的踪迹数据量就是一个天量,对于网络和server端的压力就非常大了,而且没必要采集所有的数踪迹数据进行分析,我们只需要采集一部分进行分析就可以了,这里我们本地方便查看就设置为1,线上我们可以根据我们自己的需求,综合考量,设置一个合适的采样率

运行项目,访问接口,我们可以看到如下页面:

注意:spans的个数是按照入口和出口进行计算的比如第一条请求,请求入gateway算一个,gateway向user-api发起请求算一个,入user-api算一个,以此类推,第一个请求就有5个span

具体请求链路详情我们可以点击一个进入:

在这里我们简单介绍下,如果我们不持久化数据,数据是保存到内存中的,一旦服务重启,数据就丢失了,而且数据保存到内存中也存在很大的问题,一般我们持久化链路踪迹数据,Zipkin支持将追踪数据持久化到mysql或者es中,一般我们用的比较多的是es。

Spring Cloud系列之Eureka

Spring Cloud系列之配置中心Config

Spring Cloud系列之gateway

Spring Cloud系列之Feign

Spring Cloud系列之链路追踪


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/766074.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-08-19
下一篇2023-08-19

发表评论

登录后才能评论

评论列表(0条)

    保存