问题描述
我在使用日志链路追踪的时候(基于SLF4J MDC机制实现日志的链路追踪),我发现使用Hystrix线程池隔离的时候,我不能将子线程没有复制主线程的MDC上下文(Slf4j MDC机制),导致日志链路断掉。
问题分析
Hystrix的线程池隔离是使用HystrixThreadPool来实现的。而获取HystrixThreadPool是在HystrixConcurrencyStrategy。在这里我们可以看到类的描述:
大致意思是HystrixConcurrencyStrategy提供了一套默认的并发策略实现。我们可以根据我们自己不同需求通过装饰去扩展它。如每次执行HystrixCommand的时候都会去调用wrapCallable(Callable) 方法,这里我们就可以通过装饰Callable使它提供一些额外的功能(如ThreadLocal上下文传递)。还附上了插件的文档:https://github.com/Netflix/Hystrix/wiki/Plugins。
打开文档,将会教我们如何去扩展HystrixConcurrencyStrategy并使它生效。
实现
扩展HystrixConcurrencyStrategy
/**
* Hystrix线程池隔离支持日志链路跟踪
*
* @author yuhao.wang3
*/
public class MdcHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
return new MdcAwareCallable(callable, MDC.getCopyOfContextMap());
}
private class MdcAwareCallable<T> implements Callable<T> {
private final Callable<T> delegate;
private final Map<String, String> contextMap;
public MdcAwareCallable(Callable<T> callable, Map<String, String> contextMap) {
this.delegate = callable;
this.contextMap = contextMap != null ? contextMap : new HashMap();
}
@Override
public T call() throws Exception {
try {
MDC.setContextMap(contextMap);
return delegate.call();
} finally {
MDC.clear();
}
}
}
}
通过装饰Callable类,我们在MdcAwareCallable#call()方法中先将MDC上下文复制到子线程。
注册插件
@Configuration
public class HystrixConfig {
//用来拦截处理HystrixCommand注解
@Bean
public HystrixCommandAspect hystrixAspect() {
return new HystrixCommandAspect();
}
@PostConstruct
public void init() {
HystrixPlugins.getInstance().registerConcurrencyStrategy(new MdcHystrixConcurrencyStrategy());
}
}
参考: https://github.com/Netflix/Hystrix/wiki/Plugins#concurrencystrategy
源码
https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases
spring-boot-student-hystrix 工程
为监控而生的多级缓存框架 layering-cache这是我开源的一个多级缓存框架的实现,如果有兴趣可以看一下