首页 > 其他分享 >Fegin异步情况下丢失上下文问题

Fegin异步情况下丢失上下文问题

时间:2023-05-13 22:11:18浏览次数:53  
标签:异步 confirmVo Fegin RequestContextHolder 线程 CompletableFuture 上下文 远程 请求

问题:

Order服务需要远程调用member服务进而查询所有的地址列表,也需要远程调用cart服务进而查询购物车所有选中的购物项,串行执行远程调用会浪费大量时间,因此我们进行异步编排优化

@Override
   public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
       OrderConfirmVo confirmVo = new OrderConfirmVo();
       MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
       
       // 1、远程查询所有的地址列表
       CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
           List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
           confirmVo.setAddressVos(address);
      }, executor);

       // 2、远程查询购物车所有选中的购物项
       CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
           List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
           confirmVo.setItems(items);
      }, executor);
       // feign在远程调用请求之前要构造
       // 3、查询用户积分
       Integer integration = memberRespVo.getIntegration();
       confirmVo.setIntegration(integration);
       // 4、其他数据自动计算
       // TODO 5、防重令牌
       CompletableFuture.allOf(getAddressFuture,cartFuture).get();
       return confirmVo;
  }

开启异步时获取不到老请求的信息即拦截器中会发生空指针的错误,即老request对象获取不到,是null

@Configuration
public class GulimallFeignConfig {
   /**
    * feign在远程调用之前会执行所有的RequestInterceptor拦截器
    *
    * @return
    */
   @Bean("requestInterceptor")
   public RequestInterceptor requestInterceptor() {
       return new RequestInterceptor() {
           @Override
           public void apply(RequestTemplate requestTemplate) {
               // 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
               ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
               HttpServletRequest request = attributes.getRequest();
               // 2、同步请求头数据,Cookie
               String cookie = request.getHeader("Cookie");
               // 给新请求同步了老请求的cookie
              requestTemplate.header("Cookie", cookie);  
          }
      };
  }
}

原因:

@Configuration
public class GulimallFeignConfig {
   /**
    * feign在远程调用之前会执行所有的RequestInterceptor拦截器
    *
    * @return
    */
   @Bean("requestInterceptor")
   public RequestInterceptor requestInterceptor() {
       return new RequestInterceptor() {
           @Override
           public void apply(RequestTemplate requestTemplate) {
               // 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
               ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
               System.out.println("RequestInterceptor线程...."+Thread.currentThread().getId());
               if (attributes != null) {
                   HttpServletRequest request = attributes.getRequest();
                   // 2、同步请求头数据,Cookie
                   String cookie = request.getHeader("Cookie");
                   // 给新请求同步了老请求的cookie
                   requestTemplate.header("Cookie", cookie);
              }
          }
      };
  }
}
@Override
   public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
       OrderConfirmVo confirmVo = new OrderConfirmVo();
       MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
       System.out.println("主线程线程...."+Thread.currentThread().getId());
       // 1、远程查询所有的地址列表
       CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
           System.out.println("member线程...."+Thread.currentThread().getId());
           List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
           confirmVo.setAddressVos(address);
      }, executor);

       // 2、远程查询购物车所有选中的购物项
       CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
           System.out.println("cart线程...."+Thread.currentThread().getId());
           List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
           confirmVo.setItems(items);
      }, executor);
       // feign在远程调用请求之前要构造
       // 3、查询用户积分
       Integer integration = memberRespVo.getIntegration();
       confirmVo.setIntegration(integration);
       // 4、其他数据自动计算
       // TODO 5、防重令牌
       CompletableFuture.allOf(getAddressFuture,cartFuture).get();
       return confirmVo;
  }

测试打印线程:

 

我们给feign的拦截器所拦截的请求加上了请求头,但这个获取请求头的过程是在一个线程执行过程中进行的,但是由于 RequestContextHolder底层使用的是线程共享数据 ThreadLocal<RequestAttributes>,线程共享数据的域是当前线程下,线程之间是不共享的

 

解决:

向 RequestContextHolder线程域中放主线程的请求域。

即先在主线程中获取到请求头相关信息(RequestContextHolder.getRequestAttributes();),然后在异步调用的过程中将情求头加到正在执行异步操作的线程中( RequestContextHolder.setRequestAttributes(requestAttributes);),这样请求头就不会丢失。

@Override
   public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
       OrderConfirmVo confirmVo = new OrderConfirmVo();
       MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
       // 获取主线程的域,主线程中获取请求头信息
       RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
       // 1、远程查询所有的地址列表
       CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
           RequestContextHolder.setRequestAttributes(requestAttributes);
           // 将主线程的域放在该线程的域中
           List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
           confirmVo.setAddressVos(address);
      }, executor);

       // 2、远程查询购物车所有选中的购物项
       CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
           // 将老请求的域放在该线程的域中
           RequestContextHolder.setRequestAttributes(requestAttributes);
           List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
           confirmVo.setItems(items);
      }, executor);
       // feign在远程调用请求之前要构造
       // 3、查询用户积分
       Integer integration = memberRespVo.getIntegration();
       confirmVo.setIntegration(integration);
       // 4、其他数据自动计算
       // TODO 5、防重令牌
       CompletableFuture.allOf(getAddressFuture,cartFuture).get();
       return confirmVo;
  }

虽然都是同一个 RequestContextHolder类在调用方法,但是 RequestContextHolder在不同线程中的意义是不同的,它仅代表当前线程。

@Configuration
public class GulimallFeignConfig {
   /**
    * feign在远程调用之前会执行所有的RequestInterceptor拦截器
    *
    * @return
    */
   @Bean("requestInterceptor")
   public RequestInterceptor requestInterceptor() {
       return new RequestInterceptor() {
           @Override
           public void apply(RequestTemplate requestTemplate) {
               // 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
               ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
               if (attributes != null) {
                   HttpServletRequest request = attributes.getRequest();
                   // 2、同步请求头数据,Cookie
                   String cookie = request.getHeader("Cookie");
                   // 给新请求同步了老请求的cookie
                   requestTemplate.header("Cookie", cookie);
              }
          }
      };
  }
}
 

标签:异步,confirmVo,Fegin,RequestContextHolder,线程,CompletableFuture,上下文,远程,请求
From: https://www.cnblogs.com/xiejixiang/p/17398334.html

相关文章

  • C# Winform 界面操作异步回调 AsyncCallback
    usingSystem;usingSystem.Collections.Generic;usingSystem.Threading;usingSystem.Windows.Forms;namespaceWindowsFormsApp1{publicpartialclassForm1:Form{publicForm1(){InitializeComponent();m......
  • Springboot 开启异步任务Async,邮件发送任务,定时任务
    异步任务1.主启动类开启异步注解 2.service目录下开启异步任务注解@ServicepublicclassAsyncService{@Async//异步任务注解的标志publicvoidhello(){try{Thread.sleep(3000);}catch(InterruptedExceptione){......
  • C#写的异步文件下载器
    C#写的异步文件下载器usingSystem;usingSystem.Collections.Generic;usingSystem.IO;usingSystem.Net;usingSystem.Threading.Tasks;usingSystem.Windows.Forms;namespaceWindowsFormsAppDownloader{publicpartialclassFormDownloader:Form{......
  • 并发/并行/同步/异步概念
    1、应用程序和内核内核具有最高权限,可以访问受保护的内存空间,可以访问底层的硬件设备。而这些是应用程序所不具备的,但应用程序可以通过调用内核提供的接口来间接访问或操作。所谓的常见的IO模型就是基于应用程序和内核之间的交互所提出来的。以一次网络IO请求过程中的read......
  • php 异步形式调取导出数据
    php部分ajax请求此部分functionaysncexec(){$lock_file='filelock.lock';if(file_exists($lock_file)){exit(json_encode(array('code'=>0)));}$url=base_url().'execcmd';......
  • 【转】JavaScript 执行上下文——JS 的幕后工作原理
    转自译文:JavaScript执行上下文——JS的幕后工作原理。译文中图片不显示,要结合原文看,看着不方便。整理了一份含图片的。所以有了此篇的转载,以方便阅读。以下是正文:原文:JavaScriptExecutionContext–HowJSWorksBehindTheScenes,作者:VictorIkechukwu所有JavaScript代......
  • 异步机无感算法解析 提供推导文档,模型,代码…… md500
    异步机无感算法解析提供推导文档,模型,代码……md500ID:442500634075285690......
  • python异步正则字符串替换,asyncio异步正则字符串替换re
     自然语言处理经常使用re正则模块进行字符串替换,但是文本数量特别大的时候,需要跑很久,这就需要使用asyncio异步加速处理importpandasaspdimportreimportasynciodata=pd.read_csv("guba_all_post_20230413.csv")data.dropna(inplace=True)#defreplace_betwee......
  • python异步字符串查找,asyncio和marisa_trie
     自然语言处理当中经常需要字符串的查找操作,比如通过查找返回字串在文本当中的位置,比如通过匹配实现的nerimportpandasaspdimportasyncio#data=pd.read_csv("guba_fc_result_20230413.csv")data=pd.read_csv("guba_all_post_20230413.csv")filename="cate_gr......
  • 异步电机的VVVF的C代码+仿真模型,C代码可直接在simulink模型里进行在线仿真,所见即所得,
    异步电机的VVVF的C代码+仿真模型,C代码可直接在simulink模型里进行在线仿真,所见即所得,仿真模型为离散化模型,C代码嵌入到模型里进行在线仿真,仿真通过后可以直接移植到各种MCU芯片里:1.直接带满载启动,转速超调小,控制精度高2.四种不同的VF曲线可供选择:直线VF,分段VF,抛物线VF,S......