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的加载顺序有关。
标签:异步,拦截器,span,TRACE,信息,ThreadLocal,链路,Span From: https://www.cnblogs.com/zeevy/p/18017158