【链路追踪】Java多线程之间日志traceId传递

前言

在生产环境中,由于处在并发环境,即使是同一个线程的日志输出也是不连续的,从而导致排查日志时不方便,可通过traceId就能够快速定位到同一个线程的多行日志输出,可以很方便地跟踪请求并定位问题。但是,如果在代码中使用了多线程,那么就会发现,新开的线程不会携带父线程traceId。于是,通过继承父线程的MDC上下文信息,使得新开的线程与父线程保持一致的traceId

MDC说明:

MDC(Mapped Diagnostic Context)是一种常用的日志记录技术,MDC可以将关键信息存储在线程上下文中,并在需要时将其传递到调用链的不同组件中。

使用MDC传递日志的好处:

  1. 方便跟踪请求:通过 MDC,可以在整个请求生命周期中记录和传递关键信息,例如请求 ID、用户 ID 等,这样可以方便地跟踪请求并定位问题。
  2. 提高调试效率:MDC 可以存储调用链中各个组件的上下文信息,从而使得在调试时可以更快速地诊断问题,缩短故障排除时间。
  3. 支持分布式系统:在分布式系统中,MDC 可以在不同节点之间传递关键信息,使得在跨节点调用时可以快速定位问题。
  4. 提高代码可读性:MDC 记录的上下文信息可以被日志输出格式化为易于阅读的形式,提升代码可读性。

实现代码:

/**
 * 继承ThreadPoolTaskExecutor,实现多线程处理任务时传递日志traceId
 */
public class ThreadPoolTaskExecutorMdcUtil extends ThreadPoolTaskExecutor {

    @Override
    public void execute(Runnable task) {
        super.execute(wrap(task));
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(wrap(task));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(wrap(task));
    }

    private <T> Callable<T> wrap(final Callable<T> callable) {
        // 获取当前线程的MDC上下文信息
        Map<String, String> context = MDC.getCopyOfContextMap();
        return () -> {
            if (context != null) {
                // 传递给子线程
                MDC.setContextMap(context);
            }
            try {
                return callable.call();
            } finally {
                // 清除MDC上下文信息,避免造成内存泄漏
                MDC.clear();
            }
        };
    }

    private Runnable wrap(final Runnable runnable) {
        Map<String, String> context = MDC.getCopyOfContextMap();
        return () -> {
            if (context != null) {
                MDC.setContextMap(context);
            }
            try {
                runnable.run();
            } finally {
                // 清除MDC上下文信息,避免造成内存泄漏
                MDC.clear();
            }
        };
    }
}

之后只要像正常的使用线程池一样使用ThreadPoolTaskExecutorMdcUtil类即可。

例如,注入一个线程池Bean代码示例:

@Bean("thread-pool-receive")
public ThreadPoolTaskExecutor receiveThreadPoolExecutor() {
    // new的是自定义的线程池
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorMdcUtil();
    executor.setCorePoolSize(1);
    executor.setMaxPoolSize(10);
    // 缓存队列
    executor.setQueueCapacity(10000);
    // 允许线程的空闲时间60秒:
    executor.setKeepAliveSeconds(60);
    // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
    executor.setThreadNamePrefix("test-");
    // 拒绝策略为调用者执行
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丶只有影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值