ThreadLocal Context Propagation with Hystrix
The article explains how to propagate ThreadLocal data such as a traceId across Hystrix’s thread‑pool isolation by implementing a custom HystrixConcurrencyStrategy or, more robustly, a HystrixCommandExecutionHook that uses HystrixRequestContext, ensuring the context is correctly transferred even to fallback methods.
This article shares a solution for passing ThreadLocal context information when using Hystrix.
Background : In business development, ThreadLocal is used to store key information such as traceId. When Hystrix is applied for circuit breaking, the ThreadLocal value may not be correctly propagated to the Hystrix thread.
ThreadLocal : Explanation and example code for storing traceId.
public class ThreadLocalUtil {
private static final ThreadLocal
TRACE_ID = new ThreadLocal<>();
public static void setTraceId(String traceId) { TRACE_ID.set(traceId); }
public static String getTraceId() { return TRACE_ID.get(); }
public static void clearTraceId() { TRACE_ID.remove(); }
}Hystrix : Overview of Hystrix circuit breaker and its two isolation strategies (semaphore and thread‑pool). The default is thread‑pool, which creates a separate thread pool for command execution.
Problem : In thread‑pool mode, ThreadLocal values are not automatically transferred to the Hystrix thread, leading to mismatched traceId.
Solution 1 – InheritableThreadLocal : Not suitable because Hystrix reuses threads from its pool, causing stale values.
Solution 2 – HystrixConcurrencyStrategy : Implement a custom concurrency strategy that copies the ThreadLocal value before the command runs and clears it afterwards.
public class MyHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
@Override
public
Callable
wrapCallable(Callable
callable) {
String traceId = ThreadLocalUtil.getTraceId();
return () -> {
ThreadLocalUtil.setTraceId(traceId);
try {
return callable.call();
} finally {
ThreadLocalUtil.clearTraceId();
}
};
}
}
// Register
HystrixPlugins.getInstance().registerConcurrencyStrategy(new MyHystrixConcurrencyStrategy());This approach works for normal command execution but does not cover fallback methods.
Solution 3 – HystrixCommandExecutionHook : Override hook methods to copy and paste the traceId for onStart, onExecutionStart, onFallbackStart, and clean up after success or error.
public class MyHystrixHook extends HystrixCommandExecutionHook {
private String traceId;
@Override
public
void onStart(HystrixInvokable
commandInstance) { copyTraceId(); }
@Override
public
void onExecutionStart(HystrixInvokable
commandInstance) { pasteTraceId(); }
@Override
public
void onFallbackStart(HystrixInvokable
commandInstance) { pasteTraceId(); }
// option1
@Override
public
void onExecutionSuccess(HystrixInvokable
commandInstance) {
ThreadLocalUtil.clearTraceId();
super.onExecutionSuccess(commandInstance);
}
@Override
public
Exception onExecutionError(HystrixInvokable
commandInstance, Exception e) {
ThreadLocalUtil.clearTraceId();
return super.onExecutionError(commandInstance, e);
}
// option2 …
private void copyTraceId() { this.traceId = ThreadLocalUtil.getTraceId(); }
private void pasteTraceId() { ThreadLocalUtil.setTraceId(traceId); }
}
// Register
HystrixPlugins.getInstance().registerCommandExecutionHook(new MyHystrixHook());Potential race condition: multiple caller threads may overwrite the shared traceId in the hook. To avoid this, Hystrix provides HystrixRequestContext and HystrixRequestVariableDefault.
HystrixRequestContext holds a ThreadLocal of request‑scoped data; HystrixRequestVariableDefault works like a ThreadLocal with get/set backed by the context’s ConcurrentHashMap.
public class MyHystrixHook extends HystrixCommandExecutionHook {
private HystrixRequestVariableDefault
requestVariable = new HystrixRequestVariableDefault<>();
@Override
public
void onStart(HystrixInvokable
commandInstance) {
HystrixRequestContext.initializeContext();
copyTraceId();
}
@Override
public
void onExecutionStart(HystrixInvokable
commandInstance) { pasteTraceId(); }
@Override
public
void onFallbackStart(HystrixInvokable
commandInstance) { pasteTraceId(); }
@Override
public
void onSuccess(HystrixInvokable
commandInstance) {
HystrixRequestContext.getContextForCurrentThread().shutdown();
super.onSuccess(commandInstance);
}
@Override
public
Exception onError(HystrixInvokable
commandInstance,
HystrixRuntimeException.FailureType failureType, Exception e) {
HystrixRequestContext.getContextForCurrentThread().shutdown();
return super.onError(commandInstance, failureType, e);
}
private void copyTraceId() { requestVariable.set(ThreadLocalUtil.getTraceId()); }
private void pasteTraceId() { ThreadLocalUtil.setTraceId(requestVariable.get()); }
}
// Register
HystrixPlugins.getInstance().registerCommandExecutionHook(new MyHystrixHook());Hystrix’s internal wrapper copies the caller’s HystrixRequestContext to the execution thread, ensuring the traceId is correctly transferred.
Summary : Choose between HystrixConcurrencyStrategy (simpler, works when fallback does not need context) and HystrixCommandExecutionHook with HystrixRequestContext (more robust, covers fallback). Both plugins must be registered before use.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.