之前在前司的时候研究过一些链路日志相关的,有基于pinpoint,skywalking等中间件的,也有一些基于rpc框架的自研的,不过其中很多相通的逻辑应该是基于MDC来实现的,MDC 全称是 Mapped Diagnostic Context , 可以比较粗略的想成是一个存放诊断日志的工具
可以基于简单的代码来看一下
1 | public static void main(String[] args) { |
另外再配置下
1 | <?xml version="1.0" encoding="UTF-8" ?> |
一般springboot这种框架都是自带logback了的,没有的话可以引一下
打印下看
1 | [main] [d8610b0d-7db8-4926-8de0-0e14e8342018] - 开始业务逻辑处理 |
那么MDC里究竟是啥呢
我们可以顺着 org.slf4j.MDC#put 的逻辑看下去,里面的是调用了个接口的
1 | public static void put(String key, String val) throws IllegalArgumentException { |
这个 org.slf4j.spi.MDCAdapter 接口我们再看下实现类
我们用的是logback,就看下这个实现 ch.qos.logback.classic.util.LogbackMDCAdapter
这里的put呢,就是基于ThreadLocal的实现
1 | public void put(String key, String val) throws IllegalArgumentException { |
至于这些map的操作主要是为了父子线程之间来传递MDC信息用的
主要还是基于
1 | final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>(); |
的操作,这样也就能保持线程安全
基于这个逻辑,我们在做链路日志串联的时候就有基础功能了
比如web请求,就在拦截器里可以先给MDC里设置好当前请求的请求traceId
当然这个traceId的唯一性就需要进行一步讨论比如雪花算法这种,不过一般这种情况带上机器id就可以简化点
在微服务之间进行rpc调用的时候可以基于类似于Dubbo的filter机制,在RpcContext中写入traceId,保证服务之间调用能够带上这个traceId
接下去就是更复杂的,比如定时任务,消息队列,
还有涉及到其他中间件,以及最重要的数据库的操作,需要研究类似于数据库连接池或者分库分表中间件的能力
只是总结下来,对于这类基础设施的实现,还是离不开MDC这个日志的基础能力
否则比如我们在整个应用写入日志的时候都需要处理这个traceId
有的说可以用切面,切面其实也是要基于MDC这种存储,另外就是整个应用的切面其实用起来也没那么方便
再说回来,traceId来串联日志的重要性在一般业务排查中还是起到非常大作用的,这个还有业界的dapper这种示范理念
再细化的还会在这个traceId中再叠加树形结构,去细化一个链路中的每次子调用
后面可以再对这些内容进行展开