异步调用中链路信息TRACE丢失问题

1、问题描述

链路框架底层为jaegertracing,行内的北斗链路是对这个jaegertracing进行了一层包装

框架中使用自定义注解@RvcAsync来执行异步任务,RvcAsync注解核心逻辑为使用CompletableFuture.runAsync()方法执行多线程任务,传入的第二个参数asyncTaskExecutor为自定义线程池。

1  CompletableFuture.runAsync(() -> {
2             try {
3                 TimeUnit.SECONDS.sleep(1);
4                 this.proceed(point);
5             } catch (Exception ex) {
6                 userLogUtils.logError(ex.getMessage() + ExceptionUtils.getStackTrace(ex));
7             }
8         }, asyncTaskExecutor);

在与主机方进行Http请求的时候,链路信息莫名其妙消失了,通常情况下链路信息会由北斗链路框架中的一个httpInterceptor拦截器对Http请求拦截,然后在请求头中塞入链路信息,使得在调用方和主机方追溯调用链路的时候,主机方找不到渠道端给出的链路ID的日志信息,导致问题追踪困难。

2、问题分析

通过查看源码,链路信息保存在JaegerTracer对象和JaegerSpan中,再继续看JaegerTracer对象的属性,发现内部有一个叫scopeManager的变量,它的内部存放了当前链路中所有的活跃span信息,经过调试发现这个属性注入的具体依赖为为ThreadLocalScopeManager这个类,它内部使用了ThreadLocal类型来保存tlsScope这个变量,这个变量存储的是Span信息,ThreadLocal只在当前线程中生效,当进入子线程当中,httpInterceptor拦截器拦截http,并去获取当前活跃span信息的时候,获取到的span对象为null,这时候作为客户端调用方,则直接放过合格拦截也就是请求头中不再带有链路信息。ThreadLocalScopeManager的代码如下:

 1 public class ThreadLocalScopeManager implements ScopeManager {
 2     final ThreadLocal<ThreadLocalScope> tlsScope = new ThreadLocal<ThreadLocalScope>();
 3 
 4     @Override
 5     public Scope activate(Span span) {
 6         return new ThreadLocalScope(this, span);
 7     }
 8 
 9     @Override
10     public Span activeSpan() {
11         ThreadLocalScope scope = tlsScope.get();
12         return scope == null ? null : scope.span();
13     }
14 }

服务端的链路信息是怎么生成的呢,继续研究发现是一个叫做TracingFilter的拦截器实现的,这个拦截器作用于整个服务,也就是当有请求到controller的时候,会首先被这个filter拦截,这个拦截器的作用就是调用jaegertracing的start方法去拿到Span信息,在这个start方法中,如果有存活的Span,则添加当前的服务的这一跳的Span信息到childrenSpan中,如果没有Span信息,则判断当前为第一跳,生成链路ID,SpanID等链路信息。

3、问题修复

由于我们之前分析,链路丢失的根本原始是scopeManager这个属性,在注入依赖的时候所注入的那个对象ThreadLocalScopeManager,他的内部使用了ThreadLocal来保存span信息,那么我们可以自己定义一个MyScopeManager对象,实现ScopeManager接口,然后指定这个对象为Primary,也就是接口再有多个实现类的情况下,指定当前类为默认bean,在这个MyScopeManager中指定存储Span对象的这个变量为InheribleThreadLocal,并且重写activate和activeSpan方法,这样,在项目启动的时候,加载jaegerTracing这个bean的时候,会注入我们自己定义的ScopeManager,这样就可以实现父子线程之间Span信息的共享了。

待解决问题:由此可见,在jaegerTracing这个bean内部注入ScopeManager的时候,并没有指定注入具体哪一个实现类,因为如果指定了具体实现类,那我们自定义的MyScopeManager,就算加上Primary注解指定为默认bean,但是Spring会优先根据beanName去注入对应的实现类,根绝beanName没找到,才会去注入默认的这个实现类,那么为什么会注入ThreadLocalScopeManager呢,推测可能是bean的加载顺序有关。

posted @ 2024-02-16 14:55  leviH  阅读(30)  评论(0编辑  收藏  举报