> 文章列表 > 【Spring】HystrixRequestVariableDefault

【Spring】HystrixRequestVariableDefault

【Spring】HystrixRequestVariableDefault

文章目录

  • 技术场景
  • Hystrix 跨线程保存原理
    • HystrixRequestVariableDefault
    • HystrixRequestContext
    • Hystrix 跨线程传输 Demo
    • HystrixContextRunnable
  • Hystrix 保存用户信息跨进程传输实现
    • 1、引入依赖 pom.xml
    • 2、定义用户信息类
    • 3、定义用户信息控制类
    • 4、拦截器 HandlerInterceptorAdapter
    • 5、 拦截器的注册类
    • 6、网络响应 Controller

技术场景

  • 不同用户登录系统,用信息标识(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