我是靠谱客的博主 认真小甜瓜,最近开发中收集的这篇文章主要介绍Spring Cloud 之 Hystrix 跨线程传递数据,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

欢迎访问陈同学博客原文

本文以一个技术场景来学习 Hystrix 跨线程传递数据的知识。将先简述ThreadLocal、InheritableThreadLocal跨父子线程传递数据,再进入主题。

基于Spring Boot 2.0.6.RELEASE, Spring Cloud Finchley.SR1

技术场景

服务A 通过 Feign + Hystrix 调用服务B,服务间调用时需传递 JWT Token,希望在Feign发起的所有请求中都自动加上token以在各服务中传递环境信息。JWT Token 一是用于确保访问的安全,二是存储用户context信息。

请求进入服务A时,首先通过 Filter 验证 JWT token,接着把 token 存到当前线程(tomcat工作线程)。调用 Service B时,tomcat工作线程将把任务交给 Hystrix 线程池处理,这是本文主题:如何将token从tomcat工作线程传递到hystrix线程池线程?

Hystrix 线程池

先提一下Hystrix线程池。当 Hystrix 隔离策略为线程池时,在上述场景中:

假设服务B的应用名为 Service-B,在服务A中,Hystrix 会专门创建线程池用于执行对 Service-B的调用,上图中的 hystrix-Service-B-n 就是其线程池中的线程。

通过这种方式,应用A对依赖进行了隔离。默认对每个依赖的服务分配10个线程。隔离的好处例举几个:

  • 请求太多,10个线程处理不过来?=> 那就拒绝请求,保护应用
  • 依赖服务调用出错?失败?=> 那就降级 或 快速失败
  • 依赖服务调用延时?=> 想耍流盲,占用资源不放,那就利用超时监控机制,干掉流氓任务

父子线程如何传递数据

跨线程传递数据的场景有:父子线程和其他任意线程之间。

先看下ThreadLocal,定义两个ThreadLocal类型的对象:TOKEN 和 USER,假定用1024、1025分别代表这两个对象。

static final ThreadLocal<String> TOKEN = new ThreadLocal<>(); // 1024
static final ThreadLocal<String> USER = new ThreadLocal<>();
// 1025

在Thread1中执行: TOKEN.set(“1”); USER.set(“1”)

在Thread2中执行: TOKEN.set(“2”); USER.set(“2”)

数据存储情况如下:

每个线程都有个ThreadLocalMap类型的属性 threadLocals,它用于存储线程私有数据,ThreadLocal对象只是充当检索数据的Key,本身不存储任何信息。

再看下 InheritableThreadLocal 怎么在父子线程之间传递数据,如下例子:

static final ThreadLocal<String> TOKEN = new InheritableThreadLocal<>();

Thread有两个属性,threadLocalsinheritableThreadLocals,结构完全一样。当使用 InheritableThreadLocal时,数据存储在inheritableThreadLocals中,否则存储在threadLocals。

ThreadLocalMap threadLocals = null;
ThreadLocalMap inheritableThreadLocals = null;

在创建子线程时,会将父线程的inheritableThreadLocals拷贝到子线程,从而达到跨线程传递数据的目的。

下面是Thread构造器上初始化的一段代码:

private void init(ThreadGroup g, Runnable target, String name ...) {
...
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}

Hystrix如何跨线程传递数据

上面的父子线程传递数据,子线程可以访问父线程数据。但从tomcat线程向hystrix线程池线程传递数据情况有所不同,并不清楚任务最终由hystrix线程池中的哪个线程执行,而且两种线程的关系八杆子打不着。

Hystrix提供了如下方案来解决上述问题(我将代码全写在了一起,看注释结合下面的图,否则容易晕):

// 用HystrixRequestVariableDefault做Key检索数据, 类比ThreadLocal
static final HystrixRequestVariableDefault<String> TOKEN = new HystrixRequestVariableDefault<>();

在某个 ServletFilter 进行处理:

// 在当前线程初始化HystrixRequestContext, 并设置token
HystrixRequestContext context = null;
if (!HystrixRequestContext.isCurrentThreadInitialized()) {
HystrixRequestContext.initializeContext();
}
try {
TOKEN.set("I am a token");
chain.doFilter(request, response);
} finally {
// 销毁当前线程HystrixRequestContext
if (HystrixRequestContext.isCurrentThreadInitialized()) {
HystrixRequestContext.getContextForCurrentThread().shutdown();
}
}

HystrixRequestContext 表示Request级别的context信息,在线程池环境下,用法和ThreadLocal差不多。

HystrixRequestContext中有两个非常重要的属性:

// requestVariables, 每个线程存储自己的 HystrixRequestContext
private static ThreadLocal<HystrixRequestContext> requestVariables = new ThreadLocal<HystrixRequestContext>();
// state属性,V是我自己简化的, 表示存储的值
ConcurrentHashMap<HystrixRequestVariableDefault<?>, V> state = new ...

画个图来翻译下上面的代码:

  • 先创建静态变量 HystrixRequestVariableDefault#1024 和 ThreadLocal#1025
static final HystrixRequestVariableDefault<String> TOKEN = new HystrixRequestVariableDefault<>();
private static ThreadLocal<HystrixRequestContext> requestVariables = new ThreadLocal<HystrixRequestContext>();
  • 在当前线程(tomcat的线程)通过1025这个Key找到对应的HystrixRequestContext,然后往HystrixRequestContext的state属性中put<1024, “I am a token”>
TOKEN.set("I am a token")
  • 在执行 TOKEN.get() 时,先通过ThreadLocal对象作为Key检索到线程中存储的HystrixRequestContext,然后通过HystrixRequestVariableDefault对象作为Key从HystrixRequestContext.state中获取对应的值。

上面其实没解决跨线程传递数据问题,绕了一圈,用的还是ThreadLocal,并没有线程间数据传递的过程。

调试后找到跨线程传递数据的地方,如下截图,展示了从Feign发起调用,到任务被Hystrix线程执行的过程:

再看下图,HystrixContexSchedulerAction。

它是在tomcat工作线程中创建的,因此可以拿到token,并将token存在了HystrixContexSchedulerAction对象中。

this.parentThreadState = HystrixRequestContext.getContextForCurrentThread();

当Hystrix线程执行这个任务时,任务本身就存储了token,在执行任务前,利用下面的代码把token存储到hystrix工作线程。需要注意的是:parentThreadState是一个HystrixRequestContext类型的引用,也就是说tomcat工作线程在销毁HystrixRequestContext时,Hystrix线程中存储的数据同样也就销毁了。

HystrixRequestContext.setContextOnCurrentThread(parentThreadState);

上面整个过程比较麻烦,我在调试时也找了很久才找到跨线程传递数据的地方。

跨数据传递的简单例子

通过Task对象本身来跨线程传递数据,Hystrix简化后其实就是下面的样子。

public class Demo {
private static ThreadLocal<String> TOKEN = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
TOKEN.set("1024");
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 在当前线程创建任务, 通过任务把token传递到其他线程
executorService.submit(new Task("1024"));
Thread.sleep(1000);
}
// 通过任务传递token
static class Task implements Runnable {
private String token;
public Task(String token) {
this.token = token;
}
@Override
public void run() {
// 从Task中获取token并设置到当前线程
TOKEN.set(this.token);
}
}
}

小结

Runnable 和 Callable 都是可以被线程执行的Task,无论最终由哪个线程来执行,在各个线程间传递数据比较好的方式依然是通过任务本身。跨线程传递数据只是Hystrix中的一个小细节,实现过程也夹杂在复杂的Hystrix实现中,只是看上去比较复杂。

参考资料

  • Hystrix系列之ThreadLocal跨线程传递问题 from 占小狼
  • Hystrix Isolation from Hystrix Github Wiki

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

最后

以上就是认真小甜瓜为你收集整理的Spring Cloud 之 Hystrix 跨线程传递数据的全部内容,希望文章能够帮你解决Spring Cloud 之 Hystrix 跨线程传递数据所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(48)

评论列表共有 0 条评论

立即
投稿
返回
顶部