37DATA

天算底层实现之链路实现

结论先行

基于springboot的拦截器HandlerInterceptor + MDC + 线程池,轻松实现日志链路和数据流链路

知识点

springboot的HandlerInterceptor拦截器

1. 什么是拦截器

在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略

2. 如何使用

类需要实现HandlerInterceptor接口、或者继承一个实现了HandlerInterceptor接口的类

3. 实现哪些方法
  •  preHandle:预处理,在业务处理器处理请求之前被调用(执行业务逻辑之前)。可以进行编码、安全控制、权限校验等处理

  •  postHandle:后处理,在执行Handler成功(执行业务逻辑成功)之后,根据拦截器链倒序执行,如果前面的流程中抛出异常或者请求被拦截则不会执行

  • afterCompletion:返回处理,在DispatcherServlet完全处理完请求后被调用,可用于清理资源等

MDC

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能

看一下MDC的put方法源码,MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问

Image

天算日志链路与数据流链路

日志链路

springboot处理api请求都是对应到一个线程,结合HandlerInterceptor拦截器的preHandle方法将traceId写入MDC中,然后在日志配置文件中加上对应的KEY,打印日志就不用每次都需要写traceId了。天算中的具体实现如下:

TraceId:用于跟踪单次请求

SpanId:用于应用层多次关联请求的二级跟踪

天算会优先使用请求Header里的Metis-Trace-Id和Metis-Span-Id,如果没有的话,天算会自动生成一个TraceId,Span默认为空。这里配置了traceId的key为TRACE_ID

Image

日志配置文件

Image

打印日志时就不需要写traceId了

Image

可以看到日志中已经有了traceId

Image

那么日志链路就已经搞定了,非常简单优雅~

数据流链路

天算会将一些关键步骤的处理日志写入hologres,用于生成报表查询的数据流和问题排查。最终的样子是这样的

Image

要实现这样一个数据流,我们只需要把想要的日志数据写入到holo的一个trace_log的表里即可,先建一个trace_log表,结构如下:

Image

比如在接收请求这里记录请求参数(如下图)

Image

我们需要考虑这种写入不能影响报表查询效率,需要使用异步写入。那么我们只需要启一个线程池去处理就好,springboot可以非常方便的让方法能够异步执行,只需要加上@Async注解。

Image

但是这里遇到一个问题,MDC是线程隔离的,新启的线程是拿不到MDC里的信息的。这里就需要使用MDC的getCopyOfContextMap方法复制主线程的MDC。使用方法如下:

Image

这样我们就可以往里面写入各种想要的数据流记录了,一个完整trace数据流的记录如下,这样就能轻松扩展数据流以及排查问题了。

Image

springboot的初学者,如有不对的地方,还望指正!