【Spring】HystrixRequestVariableDefault
文章目录
技术场景
- 不同用户登录系统,用信息标识(userId、orgId)不同的用户,为了实现多租户的隔离,需要在各微服务间传递用户信息。
- 使用 ThreadLocal 无法实现跨线程传递数据。
- 使用 HystrixRequestContext 可以实现跨线程传递数据。
- 基于 Springboot 2.X,SpringCloud H版。
Hystrix 跨线程保存原理
HystrixRequestVariableDefault
-
使用 HystrixRequestVariableDefault 类型的变量保存上下文信息。
-
HystrixRequestVariableDefault 是 HystrixRequestVariable 接口的实现类,HystrixRequestVariable 接口仅提供了get()来获取属性。
-
HystrixRequestVariableDefault 和 ThreadLocal 一样,提供了 T get() 和 set(T value) 两个工具方法。两个方法都调用了 HystrixRequestContext 的方法完成的。
-
HystrixRequestDefaultVariable 操作源码:
// 拿到当前线程的存储结构state(本身是map), 用HystrixRequestDefaultVariable本身作为key存储实际的数据
public void set(T value) {HystrixRequestContext.getContextForCurrentThread().state.put(this, new LazyInitializer<T>(this, value));
}public T get() {// 拿到当前线程的存储结构(本身是map), 用HystrixRequestDefaultVariable本身作为key检索数据ConcurrentHashMap<HystrixRequestVariableDefault<?>, LazyInitializer<?>> variableMap = HystrixRequestContext.getContextForCurrentThread().state;LazyInitializer<?> v = variableMap.get(this);if (v != null) {return (T) v.get();}...
}
HystrixRequestContext
-
每个线程关联一个 HystrixRequestContext,利用 ThreadLocal 进行get/set。
-
真正存储数据的是 HystrixRequestContext,存储结构是 ConcurrentHashMap,key就是 HystrixRequestVariableDefault。
-
initializeContext():创建一个 HystrixRequestContext, 并与当前线程关联。
-
isCurrentThreadInitialized():当前线程是否初始化了HystrixRequestContext。
-
getContextForCurrentThread():用 ThreadLocal 获取当前线程关联的 HystrixRequestContext。
-
setContextOnCurrentThread():为当前线程设置一个已存在的HystrixRequestContext。
-
shutdown():当前线程清空map。
-
HystrixRequestContext 源码:
public class HystrixRequestContext implements Closeable {// 每个线程各有一份HystrixRequestContext,利用ThreadLocal 进行get/set private static ThreadLocal<HystrixRequestContext> requestVariables = new ThreadLocal<HystrixRequestContext>();// 存储结构是HashMapConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>> state = new ConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>>();// 创建一个 HystrixRequestContext, 并与当前线程关联public static HystrixRequestContext initializeContext() {HystrixRequestContext state = new HystrixRequestContext();requestVariables.set(state);return state;}// 当前线程是否初始化了HystrixRequestContextpublic static boolean isCurrentThreadInitialized() {HystrixRequestContext context = requestVariables.get();return context != null && context.state != null;}// 获取当前线程关联的 HystrixRequestContext, 用的是ThreadLocalpublic static HystrixRequestContext getContextForCurrentThread() {HystrixRequestContext context = requestVariables.get();if (context != null && context.state != null) {return context;} else {return null;}}// 为当前线程设置一个已存在的HystrixRequestContextpublic static void setContextOnCurrentThread(HystrixRequestContext state) {requestVariables.set(state);}
}
Hystrix 跨线程传输 Demo
- 先初始化 HystrixRequestContext,再调用 HystrixRequestVariableDefault 对象的get/set方法。
- HystrixRequestContext 初始化方法:先判断 !HystrixRequestContext.isCurrentThreadInitialized(),再 HystrixRequestContext.initializeContext();
@Test
public void test() throws InterruptedException {// 创建HystrixRequestVariableDefault作为检索数据的keyfinal HystrixRequestVariableDefault<String> variableDefault = new HystrixRequestVariableDefault<>();if (!HystrixRequestContext.isCurrentThreadInitialized()) {// 在当前线程下创建一个HystrixRequestContext对象HystrixRequestContext.initializeContext();}// HystrixRequestVariableDefault.set()将“kitty”存储到当前线程的 HystrixRequestContextvariableDefault.set("kitty");// 用子线程执行任务HystrixContextRunnable runnable = new HystrixContextRunnable(() -> System.out.println(variableDefault.get()));new Thread(runnable, "subThread").start();// 销毁当前线程HystrixRequestContextif (HystrixRequestContext.isCurrentThreadInitialized()) {HystrixRequestContext.getContextForCurrentThread().shutdown();}
}
HystrixContextRunnable
待补充
Hystrix 保存用户信息跨进程传输实现
- 引入依赖 com.netflix.hystrix
- 用户信息类,定义用户属性字段
- 用户信息控制类,使用 HystrixRequestVariableDefault 保存用户信息
- 拦截器,拦截用户信息并保存
- 拦截器注册
- 网络响应 Controller 获取用户信息
1、引入依赖 pom.xml
- 添加 hystrix-core、lombok
<dependency><groupId>com.netflix.hystrix</groupId><artifactId>hystrix-core</artifactId><version>1.5.12</version>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
2、定义用户信息类
- 对外提供用户uid的 get/set 方法。
@Data
public class ServiceContext {private long userId;
}
3、定义用户信息控制类
- 保存用户信息,使用 HystrixRequestVariableDefault 的 get/set 方法
- 对外提供initializeContext()、get、set、shutdown 方法
public class ServiceContextHolder {private static final HystrixRequestVariableDefault<ServiceContext> context = new HystrixRequestVariableDefault<>();public static ServiceContext getServiceContext() {initServiceContext();return context.get();}private static void initServiceContext() {if (!HystrixRequestContext.isCurrentThreadInitialized()) {HystrixRequestContext.initializeContext();}}
4、拦截器 HandlerInterceptorAdapter
- 在拦截器获取用户信息,调用set方法将userId保存到 HystrixRequestVariableDefault。
- 继承 HandlerInterceptorAdapter
- 重写 preHandle:当前线程初始化
- 重写 postHandle:注销当前线程
@Component
public class ServiceContextInterceptor extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {initServiceContext(request, request.getRequestURL().toString());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception {ServiceContextHolder.destroy();}private void initServiceContext(HttpServletRequest request, String url) {ServiceContext serviceContext = new ServiceContext();String userId = request.getHeader("userId");serviceContext.setUserId(Long.valueOf(userId));ServiceContextHolder.setServiceContext(serviceContext);}
}
5、 拦截器的注册类
- 继承 WebMvcConfigurer 接口
- 实现 addInterceptor() 方法,注册拦截器实例
@Configuration
@EnableWebMvc
@Import(value = {RestResponseBodyAdvice.class})
public class MvcConfig implements WebMvcConfigurer {@Beanpublic ServiceContextInterceptor getServiceContextInterceptor() {return new ServiceContextInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(getServiceContextInterceptor()).addPathPatterns("/request-context/**");}// // 拦截配置
// registration.addPathPatterns("/api/**");
// // 排除配置
// registration.excludePathPatterns("/api/word");
}
6、网络响应 Controller
- 响应浏览器访问:@RestController、@RequestMapping(“request-context”)
- 获取用户信息
- @ResetController
- @RequestMapping(“”)
@RestController
@RequestMapping("request-context")
@Slf4j
public class RequestContextTestController {@RequestMapping(value = "test", method = RequestMethod.GET)public String test() {System.out.println("请求的用户id:" + ServiceContextHolder.getServiceContext().getUserId() + "");HystrixContextRunnable runnable =new HystrixContextRunnable(() -> {//从新的线程中获取当前用户idServiceContext context = ServiceContextHolder.getServiceContext();System.out.println("新线程的用户id:" + context.getUserId());context.setUserId(110L);});new Thread(runnable).start();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}return ServiceContextHolder.getServiceContext().getUserId() + "";}
}
参考:
Hystrix实现、代码、原理:
https://www.cnblogs.com/2YSP/p/11440700.html
https://chenyongjun.vip/articles/91
https://cloud.tencent.com/developer/article/1600674
https://blog.51cto.com/u_14888386/2516860?articleABtest=0