自实现分布式链路追踪方案ampampamp实践
前言:
排查问题是程序员的基本能力也是必须要会的,在开发环境,我们可以debug,但是一旦到了服务器上,就很难debug了,最有效的方式就是通过日志揪出bug,而一次请求的日志如果没有一个唯一的链路标识(我们下边称他为traceId),单靠程序员人工分析的话,费时费力,尤其是请求量高频的接口,更是雪上加霜,排查问题效率大打折扣,作为程序员,低效的方式是忍不了的!!! 本文我将用一次实战演练,来演示常用框架/中间件/多服务 之间如何传递traceId
本文大概有如下内容:链路追踪简述和自实现思路单服务内如何实现链路id的输出垮服务调用时,实现链路id传递的各种方式 ( 包含http(openFeign,httpClient restTemplate)、rpc(motan、 dubbo)、mq(RocketMq) )异步调用时,如何解决log4j2自带的ThreadLocal丢失链路id问题起4个服务,进行调用,观察链路追踪的效果1、链路追踪实现简述
所谓链路追踪,就是为了 把整个请求链路从头到尾串起来,不管调用链路有多深,多复杂,只要将一次链路完整无误的串联起来,就是合格的链路追踪功能。
业界不乏skywalking zipkin 等等链路追踪方面牛逼的框架,但是我们为了更轻量更灵活可控同时也是抱着学习心态,所以自己来实现链路追踪。
首先想实现链路追踪,有两点是核心,实现了这两点,问题也就不大了traceId 如何在本地 (或者说单服务内) 传递?在分布式环境中,traceId如何跨服务/中间件 传递?2、单体服务 的链路追踪
首先我们先讲下单服务内的链路传递
作为java开发,最常用的就是slf4j来实现打印日志的功能(但是slf4j并不没有实现逻辑,因为 slf4j整个的定义是一个日志门面,该包中并无具体的实现,实现都是在 比如:logback log4j2等等日志实现框架中)
slf4j的门面不仅给我们提供了打印日志的功能,还提供了 org.slf4j.MDC 类, 该类的作用大概如下:
映射诊断上下文(Mapped Diagnostic Context,简称MDC)是一种工具,用于区分不同来源的交错日志输出。当服务器几乎同时处理多个客户机时,日志输出通常是交错的。 MDC是基于每个线程进行管理的 。
上边这个官方解释,最重要的一句话就是 MDC是基于每个线程进行管理的
上边这个太官方,说下我个人对MDC的理解:他是一个日志的扩展,扩展的目的就是给 每个线程 输出的日志打上一个标记(一个线程只有一个标记且不能重复一般使用uuid即可),这样我们在查看日志时候,就可以根据这个标记来区分调用链路了
ps: 当然了, 光往MDC中设置当前线程的链路id也是不行的你还得在log4j2.xml文件中,设置占位符,这样最终输出的日志才会带链路信息。如何设置会在 2.1节 有讲。
从代码层面看下 MDC做了啥:MDC类中通过一个 MDCAdapter实例调用MDCAdapter的put get remove clear等方法。而put get remove clear 具体的实现是不同厂商来做的 比如我常用的log4j2包中就实现了 MDCAdapter 接口,实现在 org.apache.logging.slf4j.Log4jMDCAdapter类中Log4jMDCAdapter类中使用了一个 ThreadContext来执行put get remove clear逻辑,而ThreadContext 中又是一个 ThreadContextMap ThreadContextMap是一个接口 ,有不同的实现其中默认的是 DefaultThreadContextMap 该类中维护了一个 ThreadLocal