Spring Cloud Hystrix 源码系列:HystrixRequestContext

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/itsoftchenfei/article/details/87989995

在讲HystrixRequestContext之前,先了解下Hystrix 跨线程传递数据的知识,我们都知道ThreadLocal、InheritableThreadLocal跨父子线程传递数据。

需求描述

服务A 通过 Feign + Hystrix 调用服务B,服务间调用时需传递 JWT Token,希望在Feign发起的所有请求中都自动加上token以在各服务中传递环境信息,如何将token从tomcat工作线程传递到hystrix线程池线程?

注:JWT Token 一是用于确保访问的安全,二是存储用户context信息。

我们都知道,当 Hystrix 隔离策略为线程池时(复习Hystrix线程池的知识),Hystrix 会专门创建线程池用于执行对 Service-B的调用。

这时候你会想到,java中父子线程之间传递数据是这样的:

每个Thread都有个有两个属性,

  •  threadLocals(ThreadLocalMap类型,它用于存储线程私有数据,ThreadLocal对象只是充当检索数据的Key,本身不存储任何信息。
  •  inheritableThreadLocals (ThreadLocalMap类型,当使用 InheritableThreadLocal时,数据存储在它中,否则存储在threadLocals。在创建子线程时,会将父线程的inheritableThreadLocals拷贝到子线程,从而达到跨线程传递数据的目的。
private void init(ThreadGroup g, Runnable target, String name ...) {
    ...
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ...
}

不急,先看Hystrix 的实例代码吧,下面就实现了从 main线程传递到子subThread线程。

@Test
public void test() throws InterruptedException {
    // 在当前线程下创建一个HystrixRequestContext对象
    HystrixRequestContext.initializeContext();
    // 创建HystrixRequestVariableDefault作为检索数据的key
    final HystrixRequestVariableDefault<String> variableDefault = new HystrixRequestVariableDefault<>();
    // 将<HystrixRequestVariableDefault,kitty>存储到当前线程的HystrixRequestContext中
    variableDefault.set("kitty");
    // HystrixContextRunnable 是核心, 下面将分析源码:
    HystrixContextRunnable runnable = new HystrixContextRunnable(() -> System.out.println(variableDefault.get()));
    // 用子线程执行任务
    new Thread(runnable, "subThread").start();
}
//输出: kitty

难道就是用的InheritableThreadLocal?那你就错了!接下来看源码吧!

// request level的属性
public interface HystrixRequestVariable<T> extends HystrixRequestVariableLifecycle<T> {
    T get();
}
// HystrixRequestVariableDefault提供了get和set
public class HystrixRequestVariableDefault<T> implements HystrixRequestVariable<T> {
    public T get() {
        // 拿到当前线程的存储结构, 以自己作为key, 来检索数据
        if (HystrixRequestContext.getContextForCurrentThread() == null) {
            throw new IllegalStateException(HystrixRequestContext.class.getSimpleName() + ".initializeContext() must be called at the beginning of each request before RequestVariable functionality can be used.");
        } else {
            ConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>> variableMap = HystrixRequestContext.getContextForCurrentThread().state;
            HystrixRequestVariableDefault.LazyInitializer<?> v = (HystrixRequestVariableDefault.LazyInitializer)variableMap.get(this);
            if (v != null) {
                return v.get();
            } else {
                HystrixRequestVariableDefault.LazyInitializer<T> l = new HystrixRequestVariableDefault.LazyInitializer(this);
                HystrixRequestVariableDefault.LazyInitializer<?> existing = (HystrixRequestVariableDefault.LazyInitializer)variableMap.putIfAbsent(this, l);
                return existing == null ? l.get() : existing.get();
            }
        }
    }
    // 拿到当前线程的存储结构, 用自己作为key, 存储实际的数据
    public void set(T value) {
        HystrixRequestContext.getContextForCurrentThread().state.put(this, new HystrixRequestVariableDefault.LazyInitializer(this, value));
    }
}
// HystrixRequestContext是真正存储数据的
public class HystrixRequestContext implements Closeable {
    // 每个线程各有一份HystrixRequestContext,当然,前提是调用了initializeContext()进行初始化
    private static ThreadLocal<HystrixRequestContext> requestVariables = new ThreadLocal();
    // 实际的存储结构(每个线程),key就是HystrixRequestVariableDefault
    ConcurrentHashMap<HystrixRequestVariableDefault<?>, LazyInitializer<?>> state = new ConcurrentHashMap();
    //当前线程是否有数据
    public static boolean isCurrentThreadInitialized() {
        HystrixRequestContext context = (HystrixRequestContext)requestVariables.get();
        return context != null && context.state != null;
    }
    // 获取当前线程的HystrixRequestContext
    public static HystrixRequestContext getContextForCurrentThread() {
        HystrixRequestContext context = (HystrixRequestContext)requestVariables.get();
        return context != null && context.state != null ? context : null;
    }
    // 覆盖当前线程的HystrixRequestContext
    public static void setContextOnCurrentThread(HystrixRequestContext state) {
        requestVariables.set(state);
    }
    // 当前线程创建一个HystrixRequestContext
    public static HystrixRequestContext initializeContext() {
        HystrixRequestContext state = new HystrixRequestContext();
        requestVariables.set(state);
        return state;
    }
    private HystrixRequestContext() {
    }
    public void shutdown() {
        if (this.state != null) {
            Iterator i$ = this.state.keySet().iterator();

            while(i$.hasNext()) {
                HystrixRequestVariableDefault v = (HystrixRequestVariableDefault)i$.next();

                try {
                    HystrixRequestVariableDefault.remove(this, v);
                } catch (Throwable var4) {
                    HystrixRequestVariableDefault.logger.error("Error in shutdown, will continue with shutdown of other variables", var4);
                }
            }
            this.state = null;
        }
    }
    public void close() {
        this.shutdown();
    }
}

这时你会问为何HystrixRequestContext 中requestVariables 的key要设计成HystrixRequestVariableDefault?

感觉这样是普通码农和高级的区别,优秀的设计就是这样的,这样对使用者的而言代码更简洁更友好!接下来感受下吧

实例化v1,v2并set值相应的值,
HystrixRequestVariableDefault<String> v1 = new HystrixRequestVariableDefault<>();
v1.set("1")
HystrixRequestVariableDefault<String> v2 = new HystrixRequestVariableDefault<>();
v2.set("2");

那当前线程的HystrixRequestContext存储的数据为:
<v1, "1">
<v2, "2">

那Hystrix 如何实现request level context?

//一个可执行的任务
public class HystrixContextRunnable implements Runnable {
    private final Callable<Void> actual;
    private final HystrixRequestContext parentThreadState;

    public HystrixContextRunnable(Runnable actual) {       
        this(HystrixPlugins.getInstance().getConcurrencyStrategy(), actual);
    }
    
    public HystrixContextRunnable(HystrixConcurrencyStrategy concurrencyStrategy, final Runnable actual) {
        // 将原始任务Runnable包装成Callable
        this.actual = concurrencyStrategy.wrapCallable(new Callable<Void>() {
            public Void call() throws Exception {
                actual.run();
                return null;
            }
        });
        // 获取并存储当前线程的HystrixRequestContext(如示例的main线程)
        this.parentThreadState = HystrixRequestContext.getContextForCurrentThread();
    }
    public void run() {
        //先保存子线程的HystrixRequestContext
        HystrixRequestContext existingState = HystrixRequestContext.getContextForCurrentThread();
        try {
            //设置子线程的HystrixRequestContext为来自上一级线程HystrixRequestContext
            HystrixRequestContext.setContextOnCurrentThread(this.parentThreadState);
            try {
                this.actual.call();
            } catch (Exception var6) {
                throw new RuntimeException(var6);
            }
        } finally {
            // 还原子线程的HystrixRequestContext
            HystrixRequestContext.setContextOnCurrentThread(existingState);
        }
    }
}
public class HystrixContextCallable<K> implements Callable<K> {
    private final Callable<K> actual;
    private final HystrixRequestContext parentThreadState;
    public HystrixContextCallable(Callable<K> actual) {
        this(HystrixPlugins.getInstance().getConcurrencyStrategy(), actual);
    }
    public HystrixContextCallable(HystrixConcurrencyStrategy concurrencyStrategy, Callable<K> actual) {
        this.actual = concurrencyStrategy.wrapCallable(actual);
        this.parentThreadState = HystrixRequestContext.getContextForCurrentThread();
    }
    public K call() throws Exception {
        HystrixRequestContext existingState = HystrixRequestContext.getContextForCurrentThread();

        Object var2;
        try {
            HystrixRequestContext.setContextOnCurrentThread(this.parentThreadState);
            var2 = this.actual.call();
        } finally {
            HystrixRequestContext.setContextOnCurrentThread(existingState);
        }
        return var2;
    }
}

重点关注下HystrixContextRunnable,而HystrixContextCallable与它原理类似,不难理解,在不同线程之间传递信息的载体是HystrixContextRunnable,也就是任务本身。

HystrixRequestVariableDefault 和ThreadLocal的区别

  • 它使用前需要用 HystrixRequestContext.initializeContext() 进行初始化
  • 它结束时需使用 HystrixRequestContext.shutdown() 进行清理
  • 它有一个生命周期方法 shutdown()用来清理资源
  • 它会以传引用的方式(线程之间使用的是相同的HystrixRequestVariables)拷贝到下一个线程,主要通过HystrixRequestContext.getContextForCurrentThread()HystrixRequestContext.setContextOnCurrentThread()两个方法
  • 父线程调用shutdown时,子线程的HystrixRequestVariables也会被清理(因为就是一个对象,传的是引用)

Thread的 threadLocals在创建线程时会初始化,HystrixRequestContext并不是每个线程都需要的,因此需要根据需要自行进行初始化。

总结,Hystrix 传递信息的思路是非常值得借鉴的,也许在某些场景下,我们也需要设计一个在特定范围内传递信息的模型。

猜你喜欢

转载自blog.csdn.net/itsoftchenfei/article/details/87989995