【浅谈架构】微服务调用之间的用户信息传递

1,726 阅读3分钟

背景

用户信息作为面向用户业务的根源,基本上在每个服务间的业务都需要响应的用户信息。因此就引发了一个问题,若一条业务链路需要调用到多个服务来完成,并且多个服务之间也都需要用户信息,那就在链路内需要多次请求用户服务来获取用户信息,若用户服务设计为高实时性的用户信息(即每次获取用户信息都获取数据库内的用户信息)这将对用户服务和用户服务数据库造成不小的压力。

在物理资源有限的情况下,我们怎么对这进行优化?

解决设想

既然调用次数多,那就减少调用次数。在整条链路内,第一次获取完用户信息就把用户信息传递给接下来的所有用户节点。或在第一个业务节点的时候就获取好用户信息,并传递给接下来的所有用户节点。

那怎么把用户信息进行传递呢?

传递方案

在springcloud中,我们常用的是通过feign使用http的方式去请求下一个服务,通常我们传递从前端来的请求头信息的时候会通过实现 feign 依赖包内的RequestInterceptor 并重写他的apply(RequestTemplate)方法,将请求头内的所有参数依次取出并放到RequestTemplate的header内。 那我们是否可以也将用户中心按我们的方式序列化后放到其中。

实现

先介绍一下ThreadLocal

ThreadLocal

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。 ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。 ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。 总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景

image.png

参考来自:blog.csdn.net/u010445301/…

那我们在当前请求线程内,就可以通过threadLocal来共享数据,在通过feign去调用下一个服务节点的时候,将threadLocal内容取出来放到header里,在下一个被调服务节点中,通过pre过滤器将头内容取出放到threadlocal中。

代码实现

这是jdk8下的threadlocal get方法实现代码

image.png

set方法:

image.png

我们可以通过这个方式将用户信息放置到threadlocal中

    ThreadLocal threadLocal=new ThreadLocal();
    UserInfoDTO userInfoDTO=userService.getCurrentUser();
    threadLocal.set(userInfoDTO);

在feign拦截器内添加一个私有方法

   public UserInfoDTO getUserInfoByThreadLocal(){
       ThreadLocal threadLocal=new ThreadLocal();
       Object obj = (UserInfoDTO)threadLocal.get();
       if(Objects.isNull(Obj)){
           obj=new UsereInfoDTO();
           threadLocal.set((UsereInfoDTO)obj);
       }
       return (UsereInfoDTO)obj;
   }

在重写apply方法传递头:

ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
        .getRequestAttributes();
if (attributes != null) {
    HttpServletRequest request = attributes.getRequest();
    if (request != null) {
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = request.getHeader(name);
                template.header(name, values);
               }
        }
        UserInfoDTO userInfo=getUserInfoByThreadLocal();
        //序列化成字符串
        String userInfoStr=JSONObject.toJSONString(userInfo);
        template.header("userInfo", userInfoStr);
    }
 }
     

在请求过滤器内增加:

String userInfoStr = request.getHeader("userInfo");
if (!StringUtils.isEmpty(strcontext)) {
    UserInfoDTO userInfo=JSONObject.parseObject(userInfoStr,UserInfoDTO);
    ThreadLocal threadLocal=new ThreadLocal();
    threadLocal.set(userInfo);
}

Tips

在后置过滤器内需要清空threadLocal,防止该threadLcoal在下一次的请求进来时,直接取到该线程,从而导致用户信息错误。

ThreadLocal threadLocal=new ThreadLocal();
threadLocal.remove();