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