Java多线程之间日志traceId传递
前言
在生产环境中,由于处在并发环境,所以日志输出的顺序散落在各个不同行,通过traceId
就能够快速定位到同一个请求的多个不同的日志输出,可以很方便地跟踪请求并定位问题。但是,如果在代码中使用了多线程,那么就会发现,新开的线程不会携带父线程traceId
。于是,通过继承父线程的MDC
上下文信息,使得新开的线程与父线程保持一致的traceId
。
MDC说明:
MDC(Mapped Diagnostic Context)是一种常用的日志记录技术,MDC可以将关键信息存储在线程上下文中,并在需要时将其传递到调用链的不同组件中。
使用MDC传递日志的好处:
- 方便跟踪请求:通过 MDC,可以在整个请求生命周期中记录和传递关键信息,例如请求 ID、用户 ID 等,这样可以方便地跟踪请求并定位问题。
- 提高调试效率:MDC 可以存储调用链中各个组件的上下文信息,从而使得在调试时可以更快速地诊断问题,缩短故障排除时间。
- 支持分布式系统:在分布式系统中,MDC 可以在不同节点之间传递关键信息,使得在跨节点调用时可以快速定位问题。
- 提高代码可读性:MDC 记录的上下文信息可以被日志输出格式化为易于阅读的形式,提升代码可读性。
实现代码:
/* 继承ThreadPoolTaskExecutor,实现多线程处理任务时传递日志traceId*/
public class ThreadPoolTaskExecutorMdcUtil extends ThreadPoolTaskExecutor {@Overridepublic void execute(Runnable task) {super.execute(wrap(task));}@Overridepublic <T> Future<T> submit(Callable<T> task) {return super.submit(wrap(task));}@Overridepublic 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;
}