自定义线程池策略,解决使用Hystrix丢失ThreadLocal信息问题

609 阅读1分钟

背景

在微服务中,我们通常使用open-feign调用其他依赖的服务,我们会传递一些上下文信息,如traceId,token等。正常情况下我们可以直接从当前线程上下文。当使用了Hystrix之后,如果使用了线程池模式进行线程隔离,上下文就会丢失。

HystrixPlugins

自定义线程策略

Hystrix提供了插件机制,我们可以在提交任务的时候拦截处理上下文信息。 自定义一个线程池策略即可

@Slf4j
@Component
public class FeignHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {

    public FeignHystrixConcurrencyStrategy() {
        try {
            HystrixConcurrencyStrategy delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (delegate instanceof FeignHystrixConcurrencyStrategy) {
                return;
            }

            HystrixCommandExecutionHook commandExecutionHook =
                    HystrixPlugins.getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy =
                    HystrixPlugins.getInstance().getPropertiesStrategy();

            // 打印日志
            if (log.isDebugEnabled()) {
                log.debug(
                        "Current Hystrix plugins configuration is [concurrencyStrategy [{}], eventNotifier [{}], metricPublisher [{}], propertiesStrategy [{}]]",
                        delegate,
                        eventNotifier,
                        metricsPublisher,
                        propertiesStrategy);
                log.debug("Registering FeignWithHystrix Concurrency Strategy.");
            }

            HystrixPlugins.reset();
            HystrixPlugins instance = HystrixPlugins.getInstance();
            instance.registerConcurrencyStrategy(this);
            instance.registerCommandExecutionHook(commandExecutionHook);
            instance.registerEventNotifier(eventNotifier);
            instance.registerMetricsPublisher(metricsPublisher);
            instance.registerPropertiesStrategy(propertiesStrategy);
        } catch (Exception e) {
            log.error("Failed to register FeignWithHystrix Concurrency Strategy", e);
        }
    }

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        // 读取上下文信息
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 获取当前线程的traceId , 这个线程是tomcat的线程
        String traceId = MDC.get("traceId");
        log.info(" traceId info -> traceId : {} ", traceId);
        return () -> {
            try {
                // 在子线程(hystrix线程)中添加request上下文信息
                RequestContextHolder.setRequestAttributes(requestAttributes);
                // 添加traceId
                MDC.put("traceId",traceId);
                return callable.call();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }


}

在拦截器中获取上下文信息

feign发起请求之前做拦截,把要traceId,token放入header中,传到下一个服务。

@Slf4j
public class FeignAuthRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        //
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        assert requestAttributes != null;
        // 可以获取获取 HttpServletRequest
        HttpServletRequest request = requestAttributes.getRequest();
        // 链路追踪的TraceId
        String traceId = MDC.get("traceId");

        log.info("拦截器添加请求头 traceId : {} ",traceId);
        // 业务逻辑  模拟认证逻辑
        String access_token = UUID.randomUUID().toString();
        requestTemplate.header("Authorization",access_token);
        requestTemplate.header("traceId",traceId);
    }
    
}