您现在的位置是:首页 >其他 >【Spring】HystrixRequestVariableDefault网站首页其他
【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>();
// 存储结构是HashMap
ConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>> state = new ConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>>();
// 创建一个 HystrixRequestContext, 并与当前线程关联
public static HystrixRequestContext initializeContext() {
HystrixRequestContext state = new HystrixRequestContext();
requestVariables.set(state);
return state;
}
// 当前线程是否初始化了HystrixRequestContext
public static boolean isCurrentThreadInitialized() {
HystrixRequestContext context = requestVariables.get();
return context != null && context.state != null;
}
// 获取当前线程关联的 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);
}
}
Hystrix 跨线程传输 Demo
- 先初始化 HystrixRequestContext,再调用 HystrixRequestVariableDefault 对象的get/set方法。
- HystrixRequestContext 初始化方法:先判断 !HystrixRequestContext.isCurrentThreadInitialized(),再 HystrixRequestContext.initializeContext();
@Test
public void test() throws InterruptedException {
// 创建HystrixRequestVariableDefault作为检索数据的key
final HystrixRequestVariableDefault<String> variableDefault = new HystrixRequestVariableDefault<>();
if (!HystrixRequestContext.isCurrentThreadInitialized()) {
// 在当前线程下创建一个HystrixRequestContext对象
HystrixRequestContext.initializeContext();
}
// HystrixRequestVariableDefault.set()将“kitty”存储到当前线程的 HystrixRequestContext
variableDefault.set("kitty");
// 用子线程执行任务
HystrixContextRunnable runnable = new HystrixContextRunnable(() -> System.out.println(variableDefault.get()));
new Thread(runnable, "subThread").start();
// 销毁当前线程HystrixRequestContext
if (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 {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
initServiceContext(request, request.getRequestURL().toString());
return true;
}
@Override
public 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 {
@Bean
public ServiceContextInterceptor getServiceContextInterceptor() {
return new ServiceContextInterceptor();
}
@Override
public 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(() -> {
//从新的线程中获取当前用户id
ServiceContext 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