Spring Cloud 源码学习之 HystrixRequestContext

欢迎访问陈同学博客原文

前面写过 Spring Cloud 之 Hystrix 跨线程传递数据,写的是跨线程传递数据的表象,其实就是调试了一把,记录一下。今天写下核心:HystrixRequestContext。

HystrixRequestContext 表示 request level的context,用于存储 request level 的数据。与此相对的是 thread level的数据,一般用ThreadLocal来处理。

例举一个场景:Tomcat 工作线程拿到request后,将任务(例如Runnable)交给线程池处理,或创建一个子线程来处理。Tomcat工作线程自然可以获取request数据(利用RequestContextHolder存储在当前线程中呢),但如何在线程池线程中或子线程中获取到request数据呢?

这就是request level context的意义,无论是哪个线程在处理,只要在当前请求的生命周期内,都可以拿到request的数据。

小例子

先看个Demo,使用方式和ThreadLocal基本一模一样。

@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。上面代码并没有显示的将 kitty 从 main线程传递到子线程,也没有利用InheritableThreadLocal,原理且看下文。

HystrixRequestVariable

《Spring Cloud 源码学习之 HystrixRequestContext》

HystrixRequestVariable接口表示request level的属性,仅提供了get()来获取属性。

public interface HystrixRequestVariable<T> extends HystrixRequestVariableLifecycle<T> {
    public T get();
}

HystrixRequestVariableDefault是实现类,和ThreadLocal一样,提供了 T get()set(T value) 两个工具方法,在使用时HystrixRequestVariableDefault都是 static final 类型(方便使用),充当一个检索数据的 key

下面是它的get/set 方法:

// 拿到当前线程的存储结构, 用自己作为key, 存储实际的数据。
public void set(T value) {
    HystrixRequestContext.getContextForCurrentThread().state.put(this, new LazyInitializer<T>(this, value));
}

public T get() {
    // 拿到当前线程的存储结构, 以自己作为key, 来检索数据
    ConcurrentHashMap<HystrixRequestVariableDefault<?>, LazyInitializer<?>> variableMap = HystrixRequestContext.getContextForCurrentThread().state;

    LazyInitializer<?> v = variableMap.get(this);
    if (v != null) {
        return (T) v.get();
    }
	...
}

HystrixRequestContext

真正存储数据的是HystrixRequestContext,和ThreadLocal一样,存储数据的不是ThreadLocal,而是Thread本身的数据结构。下面看看它的实现:

public class HystrixRequestContext implements Closeable {
	// 利用ThreadLocal, 每个线程各有一份HystrixRequestContext,当然,前提是调用了initializeContext()进行初始化
    private static ThreadLocal<HystrixRequestContext> requestVariables = new ThreadLocal<HystrixRequestContext>();
    
    // 创建一个HystrixRequestContext,并与当前线程关联
    public static HystrixRequestContext initializeContext() {
        HystrixRequestContext state = new HystrixRequestContext();
        requestVariables.set(state);
        return state;
    }

    // 获取当前线程关联的HystrixRequestContext, 用的是ThreadLocal
    public static HystrixRequestContext getContextForCurrentThread() {
        HystrixRequestContext context = requestVariables.get();
        if (context != null && context.state != null) {
            return context;
        } else {
            return null;
        }
    }

    // 为当前线程设置一个已存在的HystrixRequestContext
    public static void setContextOnCurrentThread(HystrixRequestContext state) {
        requestVariables.set(state);
    }

	// 这句单独说 (注意:实际类型不是T,我简化了)
    ConcurrentHashMap<HystrixRequestVariableDefault<?>, T value> state = new ...
}

ConcurrentHashMap<HystrixRequestVariableDefault<?>, T value> state

这是实际的存储结构,每个线程关联一个HystrixRequestContext,每个HystrixRequestContext有个Map结构存储数据,key就是HystrixRequestVariableDefault。

因此, 初始化 HystrixRequestVariableDefault v1, v2 后,在当前线程执行:

v1.set("1");
v2.set("2");

那当前线程对应的HystrixRequestContext存储的数据为:

<v1, "1">
<v2, "2">

如何实现request level context?

实现的秘密就在HystrixContextRunnable和HystrixContextCallable中,这里以前者为例:

// HystrixContextRunnable是个Runnable,一个可用于执行的任务
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) {
        // 获取当前线程的HystrixRequestContext(如文首的main线程)
        this(concurrencyStrategy, HystrixRequestContext.getContextForCurrentThread(), actual);
    }

    // 关键的构造器
    public HystrixContextRunnable(final HystrixConcurrencyStrategy concurrencyStrategy, final HystrixRequestContext hystrixRequestContext, final Runnable actual) {
        
        // 将原始任务Runnable包装成Callable, 创建了一个新的callable
        this.actual = concurrencyStrategy.wrapCallable(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                actual.run();
                return null;
            }
        });
        // 存储当前线程的hystrixRequestContext
        this.parentThreadState = hystrixRequestContext;
    }

    @Override
    public void run() {
        // 运行实际的Runnable之前先保存当前线程已有的HystrixRequestContext
        HystrixRequestContext existingState = HystrixRequestContext.getContextForCurrentThread();
        try {
            // 设置当前线程的HystrixRequestContext,来自上一级线程,因此两个线程是同一个HystrixRequestContext
            HystrixRequestContext.setContextOnCurrentThread(parentThreadState);
            try {
                actual.call();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } finally {
            // 还原当前线程的HystrixRequestContext
            HystrixRequestContext.setContextOnCurrentThread(existingState);
        }
    }
}

so,Hystrix 的思路是包装Runnable,在执行实际的任务之前,先拿当前线程的HystrixRequestContext初始化实际执行任务的线程的HystrixRequestContext。

因此,回顾下文首的小例子,伪代码变成:

1.main 线程创建了HystrixRequestContext context, HystrixRequestVariableDefault key
2.main 线程在context的Map中存储<key, "kitty">
3.main 线程 HystrixContextRunnable task, 并将context作为它的属性
4.main 线程创建 subThread, 用它来执行task (将Task交给线程池的效果是一样的)
5.subThread 执行HystrixContextRunnable.run(), 先用task的context来初始化subThread
此时,main线程和subThread线程拥有的是同一个context, 因此在两个线程中执行 key.get() 拿到的都是"kitty"

因此,在不同线程之间传递信息的载体是HystrixContextRunnable,也就是任务本身。独立个体之间需要建立联系,总得有个桥梁,现实中的推荐信和HystrixContextRunnable的作用是一样的。

HystrixRequestVariableDefault 和ThreadLocal的一些区别

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

因Thread中的存储结构是:ThreadLocal.ThreadLocalMap threadLocals,threadLocals在创建线程时会初始化。

而HystrixRequestContext并不是每个线程都需要的,因此需要根据需要自行进行初始化。

小结

本文抽象出来就是 跨主体信息传递,跨主体算是我YY的名词。Hystrix 传递信息的思路是值得借鉴的,也许在某些场景下,我们需要设计一个在特定范围内传递信息的模型。

YY个无聊的例子:一场神奇运动会,N个运动员在田径场进行接力赛,每分钟换一个运动员,比赛开始时点燃一柱香,这根香是接力棒,香上贴着一个数字,运动员拿到香后要记住数字。

需求是:运动员之间不能交流,在香燃尽时,参与接力赛的运动员都要知道这个数字。

这个例子就是在N个主体之间传递信息:

  • 生命周期(scope):香开始点着到燃尽的时间内
  • 主体:N个运动员
  • 信息载体:香本身。如果运行员将香传给下一个人是Task,那香就属于Task属性的一部分。

现实生活中的信息载体又有哪些呢?文字?语言?空气?…

附录

HystrixContextCallable

HystrixContextCallable做的事情和HystrixContextRunnable是一样的,只不过它实现了Callable。

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();
    }

    @Override
    public K call() throws Exception {
        HystrixRequestContext existingState = HystrixRequestContext.getContextForCurrentThread();
        try {
            // set the state of this thread to that of its parent
            HystrixRequestContext.setContextOnCurrentThread(parentThreadState);
            // execute actual Callable with the state of the parent
            return actual.call();
        } finally {
            // restore this thread back to its original state
            HystrixRequestContext.setContextOnCurrentThread(existingState);
        }
    }

}

欢迎关注陈同学的公众号,一起学习,一起成长

《Spring Cloud 源码学习之 HystrixRequestContext》

    原文作者:Spring Cloud
    原文地址: https://blog.csdn.net/myle69/article/details/85013561
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞