我们知道在Java技术体系中,链路跟踪严重依赖ThreadLocal;因此在多线程的场景下会导致链路跟踪失效.
起因
前几天一位开发同学反馈了一个问题,在链路跟踪UI上看到某个链路Rpc的span数量比实际调用少了很多;我听完第一反映是我们最近升级的SDK出问题了? 根据该同学反馈该接口是一个新接口,上线后一直没关注过链路;我们随即在链路跟踪UI上将多个系统的常用接口都检查了一遍发现一切正常,基本排除了SDK可能引起的问题。接着开始检查开发同学的代码,顺着该请求发现代码里面用到了线程池 ,该同学解释该接口内部需要多次调用多个Rpc接口,为了提升效率所以采用了多线程。
分析
分析opentracing的源码发现tracing相关的的信息保存在io.opentracing.util.ThreadLocalScopeManager
的ThreadLocal变量里:
1 | public class ThreadLocalScopeManager implements ScopeManager { |
这就可以解释为什么多线程中的Rpc请求没有将相关Tracing信息传递下去;
解决方法
多线程中ThreadLocal变量的传递:
关于多线程中ThreadLocal变量的传递我们可以用阿里巴巴的transmittable-thread-local ,所以整个思路就是自己实现一遍ThreadLocalScopeManager
用TransmittableThreadLocal
替换ThreadLocal
,以及跟它关联的类ThreadLocalScope
(它里面申明了ThreadLocalScopeManager
变量);修改Opentrcing里面默认的
ThreadLocalScopeManager
为用户自定义ThreadLocalScopeManager
检查Jaeger的Springboot自动配置文件io.opentracing.contrib.java.spring.jaeger.starter.JaegerAutoConfiguration
源码, 在构造Beanio.opentracing.Tracer
时有用户自定义方法:tracerCustomizers.forEach(c -> c.customize(builder))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class JaegerAutoConfiguration {
private List<TracerBuilderCustomizer> tracerCustomizers = Collections.emptyList();
public io.opentracing.Tracer tracer(Sampler sampler,
Reporter reporter,
Metrics metrics,
JaegerConfigurationProperties properties) {
final JaegerTracer.Builder builder =
new JaegerTracer.Builder(properties.getServiceName())
.withReporter(reporter)
.withSampler(sampler)
.withTags(properties.determineTags())
.withMetrics(metrics);
tracerCustomizers.forEach(c -> c.customize(builder));
return builder.build();
}
.....于是,我们只需要自定义
TracerBuilderCustomizer
调用builder.withScopeManager
方法即可1
2
3
4
5
6public class TtlTracerBuilderCustomizer implements TracerBuilderCustomizer {
public void customize(JaegerTracer.Builder builder) {
builder.withScopeManager(new TracingScopeManager());
}
}在SpringBoot自动配置文件中构造Bean:
1
2
3
4
public List<TracerBuilderCustomizer> tracerCustomizers(){
return Arrays.asList(new TtlTracerBuilderCustomizer());
}至此,问题解决; 代码参考request-tracing
关于多线程
我不建议在互联网高并发请求接口内部采用多线程;以Java技术体系为例,请求到达系统后一般会有容器(Tomcat之类)或者Rpc框架先接收,然而这些框架本来就是多线程在运行,如果系统本来已经到瓶颈了,即使增加线程也不会提升效率;如果系统需要增加线程,首先我们应该增加容器或者Rpc框架的线程数量;另外如果接口性能差,我们首先应该考虑是Sql的问题还是代码逻辑的问题;或者系统达到了瓶颈是否可以通过增加机器提升性能;如果接口逻辑本身太复杂,可能是我们的方案或者设计有问题,或许可以考虑按照离线请求的模式设计接口。
最后
这个问题虽然没有对业务造成影响,但收集到链路肯定是有问题的;这也暴露出了我们开发过程中的一些问题,对非功能性验证做的不到位,这方面还需要加强。