异步线程中链路追踪方案

本文涉及的产品
可观测链路 OpenTelemetry 版,每月50GB免费额度
简介: 异步线程中链路追踪方案

1. 解决的问题

当一个线程执行过程中开启了新的异步线程,会导致异步线程与当前线程的traceId不一致的问题。

在线程池中,traceId可能在线程创建那一刻就已经固定了,不会跟着使用场景上下文traceId变动,在后面的线程复用环节中一直都是这个traceId,会带来traceId混乱在一起的情况,同样也会带来异步线程与当前线程的traceId不一致的问题。

最终,导致异步线程在日志上无法准确追踪到整个调用链路。

2. 环境

总的来说基于 Zipkin server + Brave library,这一块基本使用需要参考一下文档:

https://zipkin.io/pages/instrumenting.html

https://github.com/openzipkin/brave

3. Brave的currentTraceContext

以MDCCurrentTraceContext作为上下文容器进行初始化方式为例:

var Tracing = Tracing.newBuilder().endpoint(endpoint)

  .spanReporter(spanReporter()).currentTraceContext(MDCCurrentTraceContext.create()).build();

当调用tracing#Tracer#nextSpan()来开启一个新的执行片段(Span)时:

/**

  * Returns a new child span if there's a {@link #currentSpan()} or a new trace if there isn't.

  *

  * <p>Prefer {@link #startScopedSpan(String)} if you are tracing a synchronous function or code

  * block.

  */

 public Span nextSpan() {

   TraceContext parent = currentTraceContext.get();

   return parent != null ? newChild(parent) : newTrace();

 }

本质上是从currentTraceContext中取出TraceContext再进行后续操作,而TraceContext中又有我们需要的traceId:TraceContext#traceIdString

因此,认为只要将这个currentTraceContext从当前线程“传递”到异步线程中就可以满足需求。

我这里追了下代码,MDCCurrentTraceContext是通过ThreadLocal(准确的说是InheritableThreadLocal)绑定到线程中的,TraceContext有多种实现,也有可能有其他的方式。猜想作为框架的Brave其实应该提供一个方法来统一处理这种需求。

4. 用Brave提供的API实现

Brave提供了以下API来装饰线程(池),帮我们做这个“传递”的动作:

  • brave.propagation.CurrentTraceContext#wrap(java.lang.Runnable)
  • brave.propagation.CurrentTraceContext#wrap(java.util.concurrent.Callable)
  • brave.propagation.CurrentTraceContext#executor
  • brave.propagation.CurrentTraceContext#executorService


下文是几个Demo代码。

4.1. Runnable使用

// Ignore DI Tracing
// Ignore DI THREAD_POOL
CompletableFuture<Void> smartFuture = CompletableFuture.runAsync(tracing.currentTraceContext().wrap(() -> {
    var tracer = tracing.tracer();
    var span = getNextSpan(tracer, "spanName");
    try (var ignored = tracer.withSpanInScope(span)) {
        // biz code
    } catch (Exception e) {
        span.error(e);
        throw e;
    } finally {
        span.finish();
    }
}), THREAD_POOL);



4.2. Spring线程池ThreadPoolTaskExecutor中使用

// Ignore DI Tracing
@Bean
public ThreadPoolTaskExecutor getThreadPoolTaskExecutor() {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setCorePoolSize(20);
    threadPoolTaskExecutor.setMaxPoolSize(100);
    threadPoolTaskExecutor.setQueueCapacity(100);
    threadPoolTaskExecutor.setKeepAliveSeconds(60);
    threadPoolTaskExecutor.setThreadNamePrefix("thread-prefix");
    threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    // decorate runnable used in thread pool
    threadPoolTaskExecutor.setTaskDecorator(tracing.currentTraceContext()::wrap);
    return threadPoolTaskExecutor;
}


Java自带的ThreadPoolExecutor也是一样的,重写即可,方法同Spring的ThreadPoolTaskExecutor类中的setTaskDecorator方法

相关实践学习
分布式链路追踪Skywalking
Skywalking是一个基于分布式跟踪的应用程序性能监控系统,用于从服务和云原生等基础设施中收集、分析、聚合以及可视化数据,提供了一种简便的方式来清晰地观测分布式系统,具有分布式追踪、性能指标分析、应用和服务依赖分析等功能。 分布式追踪系统发展很快,种类繁多,给我们带来很大的方便。但在数据采集过程中,有时需要侵入用户代码,并且不同系统的 API 并不兼容,这就导致了如果希望切换追踪系统,往往会带来较大改动。OpenTracing为了解决不同的分布式追踪系统 API 不兼容的问题,诞生了 OpenTracing 规范。OpenTracing 是一个轻量级的标准化层,它位于应用程序/类库和追踪或日志分析程序之间。Skywalking基于OpenTracing规范开发,具有性能好,支持多语言探针,无侵入性等优势,可以帮助我们准确快速的定位到线上故障和性能瓶颈。 在本套课程中,我们将全面的讲解Skywalking相关的知识。从APM系统、分布式调用链等基础概念的学习加深对Skywalking的理解,从0开始搭建一套完整的Skywalking环境,学会对各类应用进行监控,学习Skywalking常用插件。Skywalking原理章节中,将会对Skywalking使用的agent探针技术进行深度剖析,除此之外还会对OpenTracing规范作整体上的介绍。通过对本套课程的学习,不止能学会如何使用Skywalking,还将对其底层原理和分布式架构有更深的理解。本课程由黑马程序员提供。
相关文章
|
2月前
|
Python
Python学习之路 02 之分支结构
Python学习之路 02 之分支结构
467 0
Python学习之路 02 之分支结构
|
6天前
|
安全 Java 数据库连接
Spring Boot 优雅关机时异步线程安全优化
Spring Boot 优雅关机时异步线程安全优化
8 1
|
2天前
|
安全 NoSQL Java
网络安全-----Redis12的Java客户端----客户端对比12,Jedis介绍,使用简单安全性不足,lettuce(官方默认)是基于Netty,支持同步,异步和响应式,并且线程是安全的,支持R
网络安全-----Redis12的Java客户端----客户端对比12,Jedis介绍,使用简单安全性不足,lettuce(官方默认)是基于Netty,支持同步,异步和响应式,并且线程是安全的,支持R
|
6天前
|
安全 Java
解决Java中多线程同步问题的方案
解决Java中多线程同步问题的方案
|
12天前
|
Java
java线程之异步回调
java线程之异步回调
9 0
|
2月前
|
前端开发 JavaScript UED
由于JavaScript是单线程的,因此在处理大量异步操作时,需要确保不会阻塞UI线程
【5月更文挑战第13天】JavaScript中的Promise和async/await常用于处理游戏开发中的异步操作,如加载资源、网络请求和动画帧更新。Promise表示异步操作的结果,通过.then()和.catch()处理回调。async/await作为Promise的语法糖,使异步代码更简洁,类似同步代码。在游戏循环中,使用async/await可清晰管理资源加载和更新,但需注意避免阻塞UI线程,并妥善处理加载顺序、错误和资源管理,以保证游戏性能和稳定性。
39 3
|
2月前
|
存储 消息中间件 Java
Java多线程实战-异步操作日志记录解决方案(AOP+注解+多线程)
Java多线程实战-异步操作日志记录解决方案(AOP+注解+多线程)
|
2月前
|
监控 安全 Java
一文讲明白Java中线程与进程、并发与并行、同步与异步
一文讲明白Java中线程与进程、并发与并行、同步与异步
14 1
|
2月前
|
JavaScript 前端开发
JS 单线程还是多线程,如何显示异步操作
JS 单线程还是多线程,如何显示异步操作
34 2
|
2月前
|
Java
JAVA线程&线程池&异步编排
JAVA线程&线程池&异步编排
33 0