分布式链路跟踪技术(五):跨线程传输和上下文传播

本文探讨了在分布式链路跟踪系统中如何保证 Trace 信息在跨线程操作时不丢失。介绍了阿里巴巴开源的 transmittable-thread-local 实现,通过 TtlRunnable 和 ExecutorService 的包装实现上下文传播,以及使用 java agent 进行字节码修改的自动化方法。此外,还提到跨线程传输 Trace 数据在全链路压测中的应用,以保持压测标识在整个调用链路中的一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在分布式链路跟踪系统中,同一条请求处理链路要用一个全局唯一的 traceId 来标识,那就需要把 traceId 传输到该请求涉及的各个系统中。Trace 信息要在系统之间传输时,是通过各种 RPC 中间件里埋点,把 Trace 信息放在 HTTP Header、RPC Context 里进行传输的。

实际中,同一个系统内部业务处理出现多线程操作时,如果不做显式处理,也容易丢失 Trace 信息。那么遇到跨线程操作时,怎么做才能保证 Trace 信息不丢失呢?可以参考阿里开源的 transmittable-thread-local,基本原理是在创建 Runnable 或 Callabke 的时候,把父线程的 Trace 数据存起来传递给子线程,子线程执行之前先获取 Trace 数据设置 Context,再执行业务代码,具体可以看 TtlRunnable 类:

public final class TtlRunnable implements Runnable, TtlEnhanced, TtlAttachments {
   
    private final AtomicReference<Object> capturedRef;	// 需要跨线程传输的数据
    private final Runnable runnable;					// 待执行的 Runnable

    private TtlRunnable(@NonNull Runnable runnable) {
   
        this.capturedRef = new AtomicReference<Object>(capture());	// 获取主线程的 Context 信息
        this.runnable = runnable;
    }

    /**
     * wrap method {@link Runnable#run()}.
     */
    @Override
    public void run() {
   
        Object captured = capturedRef.get();

        Object backup = replay(captured);	// 设置 Context,并返回线程中原先已有的 Context 数据
        try {
   
            runnable.run();					// 执行业务代码
        } finally {
   
            restore(
### 已知问题分析 在分布式微服务架构中,`MyBatis-Plus` 的租户插件功能可能因 `Feign` 调用机制的不同而导致失效。具体表现为,在通过 `Feign` 进行远程调用时,未能正确传递当前线程中的租户上下文信息[^1]。 --- ### 问题原因剖析 #### Feign 调用特性 `Feign` 是一种声明式的 Web Service 客户端工具,其核心原理是基于动态代理实现 HTTP 请求封装。然而,默认情况下,`Feign` 并不会自动继承主线程的上下文环境(如 Token 或租户 ID),这可能导致在跨服务调用过程中丢失必要的上下文信息[^4]。 #### MyBatis-Plus 租户插件的工作方式 `MyBatis-Plus` 提供了内置的租户隔离能力,通常依赖于全局拦截器来动态修改 SQL 查询语句并附加租户条件。这种机制的前提是能够获取到当前线程绑定的租户 ID。如果该值未被正确传播至目标服务,则会导致查询逻辑异常或数据泄露风险[^3]。 --- ### 解决方案 以下是针对此场景的具体优化措施: #### 方法一:手动设置租户上下文 可以在每次发起 `Feign` 调用前显式指定所需的租户参数,并将其作为请求头的一部分发送给下游服务。接收方需解析这些头部字段并将对应值重新注入到本地线程变量中以便后续操作正常执行。 ```java // 发送方配置示例 @FeignClient(name = "example-service", configuration = TenantAwareFeignConfiguration.class) public interface ExampleServiceClient { @GetMapping("/data") String getData(); } @Configuration public class TenantAwareFeignConfiguration { @Bean public RequestInterceptor tenantRequestInterceptor() { return requestTemplate -> { // 获取当前线程内的租户ID String currentTenantId = TenantContext.getTenantId(); if (currentTenantId != null && !currentTenantId.isEmpty()) { requestTemplate.header("X-Tenant-ID", currentTenantId); } }; } } ``` 上述代码片段展示了如何自定义一个 `RequestInterceptor` 来捕获现有会话里的租户标识符并通过标准协议传输过去。 #### 方法二:利用Spring Cloud Sleuth或其他链路追踪框架同步上下文 借助像 **Spring Cloud Sleuth** 那样的分布式跟踪库可以更方便地完成跨进程间共享敏感元数据的任务。它不仅负责记录日志关联关系还允许开发者扩展额外属性比如我们的tenant id等业务专属标记项。 启用步骤如下所示: 1. 添加依赖项 ```xml <!-- pom.xml --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> ``` 2. 自定义Span处理器以携带更多细节 ```java import brave.propagation.CurrentTraceContext; import org.springframework.stereotype.Component; @Component public class CustomCurrentTraceContextHolder { private final CurrentTraceContext traceContext; public CustomCurrentTraceContextHolder(CurrentTraceContext traceContext) { this.traceContext = traceContext; } public void setCustomTag(String key, String value){ traceContext.mandatory().context().put(key,value); } } ``` 之后只需确保所有涉及的服务均已开启相同的功能模块即可无缝衔接整个流程链条上的各个节点之间的联系状态[^2]. --- ### 总结说明 无论是采用直接编码手段还是引入第三方中间件辅助处理的方式都能够有效缓解甚至彻底消除由feign引起的mybatis plus多租户模式下产生的兼容性难题。实际项目选型还需综合考量团队技术水平以及长远维护成本等因素做出最佳决策. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值