您现在的位置是:首页 >其他 >【Spring】HystrixRequestVariableDefault网站首页其他

【Spring】HystrixRequestVariableDefault

RiceVan 2023-06-06 12:00:02
简介【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

https://cloud.tencent.com/developer/article/1600674

https://blog.51cto.com/u_14888386/2516860?articleABtest=0

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。