背景
分页查询单据时,由于单据表数据量很大,单月达到了千万级别,所以,查询先走ES,当ES不可用时,降级走mysql,降级使用了 Hystrix,并且是线程池策略,在实际测试过程中,发现前端提供相同查询参数时,后端会返回不同的响应结果,十分怪异,经排查,组装查询条件时,从上下文里获取了参数,而这些参数是放在 ThreadLocal 里的,ThreadLocal 在不同线程之间的数据隔离是通过每个线程都有一个独立的 ThreadLocal 存储来实现的。然而,Hystrix 使用线程池来实现隔离和限流,这意味着请求可能会在线程池中的不同线程之间切换。这可能导致 ThreadLocal 的数据被意外共享或者丢失。
解决方案
自定义一个 HystrixConcurrencyStrategy
,在每次任务执行时正确传递 ThreadLocal
数据。
HystrixConcurrencyStrategy
是 Hystrix 提供的一个扩展点,用于自定义并发执行的行为。通过自定义该策略,可以在 Hystrix 的执行上下文中正确管理 ThreadLocal
。
java
public class MyConcurrencyStrategy extends HystrixConcurrencyStrategy {
private HystrixConcurrencyStrategy existingConcurrencyStrategy;
public MyConcurrencyStrategy(HystrixConcurrencyStrategy existingConcurrencyStrategy) {
this.existingConcurrencyStrategy = existingConcurrencyStrategy == null
? HystrixConcurrencyStrategyDefault.getInstance() : existingConcurrencyStrategy;
}
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
return new WrappedCallable<>(callable);
}
private static class WrappedCallable<T> implements Callable<T> {
private final Callable<T> actual;
private final Map<ThreadLocal<?>, Object> threadLocals;
public WrappedCallable(Callable<T> actual) {
this.actual = actual;
this.threadLocals = captureThreadLocals();
}
@Override
public T call() throws Exception {
Map<ThreadLocal<?>, Object> originalThreadLocals = captureThreadLocals();
restoreThreadLocals(threadLocals);
try {
return actual.call();
} finally {
restoreThreadLocals(originalThreadLocals);
}
}
private Map<ThreadLocal<?>, Object> captureThreadLocals() {
Map<ThreadLocal<?>, Object> threadLocals = new HashMap<>();
// Capture current ThreadLocal values
return threadLocals;
}
private void restoreThreadLocals(Map<ThreadLocal<?>, Object> threadLocals) {
// Restore ThreadLocal values
}
}
}