背景:
一天线上出一个bug,三方商品详情获取失败,于是进行线上日志排查,排查定位到报错方法在一个三方商品服务中,但是这个方法被很多地方调用,所以找不到调用的入口在哪,想要溯源简直是太难。这个时候项目Leader说之前有项目使用了sluenth结合zipkin记录traceId但是这里用这样的技术有点杀鸡用牛刀,于是他说你试试MDC看能不能实现这样的功能,当时想赶快排查完问题,之后再去搞搞这个MDC。
处理后续问题:
空闲下来查询资料了解MDC是什么,了解完了之后决定自己动手捣鼓,当时在AOP中直接拦截了所有的Controller在MDC中放入一个全局traceId,只要是此次请求访问线程内都能取到此traceId,并且能结合日志配置文件将此traceId输出到日志文件。虽然感觉还不错,但是还是只能用于单个服务,对于分布式服务这样还是不行,现在大多数服务之间的调用都是使用的feign,于是想的是将traceId放入feign的请求头中实现traceId的传递,然后在被调用的服务的拦截器或者aop中取到此traceId,然后再放入MDC中,这样就实现了traceId的多服务传递。在实践的过程遇到个问题如何判断traceId的生成是在哪里,于是马上想到如果从请求头中没有获取到traceId就一定是分布式链路调用的入口,此时生成一个全局唯一的traceId,可以用雪花算法也可以用UUID生成,我用的是UUID,毕竟雪花算法在并发特别大的情况下有可能id生成会重复。此时一个粗糙的MDC实现分布式traceId记录追踪并打印到日志中已经完成,但最后还发现一个问题,在子线程或线程池中此traceId获取不到,结合相关资料,这里我猜想是MDC是使用ThreadLocal实现的但是ThreadLocal不能实现父子线程的副本传递,所以子线程中获取不到父线程中的traceId。项目中大部分用的都是线程池,我们只需要自己重写一个线程池,在创建线程池时获取到当前线程的MDC上下文,然后将MDC上下文赋值给自定义线程池的成员变量,然后调用新线程的前后做相应的赋值,清空处理就可以实现子线程的traceId获取。
感悟:弄懂一个技术点不要只是知道,一定要自己敲一遍代码,有自己的足迹才值得回忆。