spring|5年华为架构师1小时把SpringBoot项目并发提升了10倍,网友:牛掰( 二 )


问题定位出来了 , 解决方案就是:让子线程能获取到主线程的 request 头信息 , 主线程把 数据透传到子线程 。
我使用的是RequestContextHolder来透传数据什么是 RequestContextHolder?
RequestContextHolder 是spring mvc的一个工具类 , 顾名思义持有上下文的Request容器 。
如何使用:
//获取当前线程 request请求的属性
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
//设置当前线程 request请求的属性
RequestContextHolder.setRequestAttributes(attributes);
RequestContextHolder的会用到的几个方法

  1. currentRequestAttributes:获得当前线程请求的属性(头信息之类的)
  2. setRequestAttributes(attributes):设置当前线程 属性(设置头信息)
  3. resetRequestAttributes:删除当前线程 绑定的属性
下面是他们的源码 , 可以简单看一下 , 原理是通过ThreadLocal来绑定数据的:


下面我编写了一套遇到问题的代码例子以及解决的代码:
TestUserController
测试接口


TestRequestService
聚合数据的类



下面是两个请求 用户和订单请求类
OrderService 请求订单的服务的聚合方法




UserService 请求订单的服务的聚合方法



OrderController 你可以理解成其他其他微服务的接口(模拟写的一个接口 , 用来测试 请求接口的时候是否携带 请求头了)




下面三个接口的由来:
  1. /v1/testUser/listUser 接口:就是串行调用其他服务接口, 性能比较慢 。
  2. /v1/testUser/listUser2 接口:是通过@Async 异步注解 , 并行调用其他 系统的接口 , 性能是提升上去了 , 但灰度环境 是需要根据请求头里面的数据判断是否把流量打到灰度环境 。
  3. /v1/testUser/listUser3接口:对@Async注解没有找到透传 主线程request头信息的方案 , 就使用线程池+CompletableFuture.supplyAsync的方式 每次执行异步线程的时候 , 把主线程的 请求参数设置到子线程 , 然后通过try-finally 参数使用完之后RequestContextHolder.resetRequestAttributes() 删除参数 。
注意:parallelStream它也是属于并行流操作 , 也要设置 请求头信息 , 虽说子线程(getDateResp3方法)能获取到主线程的请求头信息了 , 但是parallelStream 又相当于子线程的子线程了 , 它是获取不到的 主线程的attributes的 , 当时我就是没在parallelStream设置attributes , 它没有走到灰度环境 ,让我 耗费了两个多小时 , 代码加了四五次日志输出 , 才把这个问题定位出来 , 这是一个坑 。。。
下面是代码:

上面说到 , 之前使用了@Async注解 , 子线程无法获取到上下文信息 , 导致流量无法打到灰度 , 然后改成 线程池的方式 , 每次调用异步调用的时候都手动透传下文(硬编码)解决了问题 。
后面查阅了资料 , 找到了方案不用每次硬编码 , 来上下文透传数据了 。
方案一:
继承线程池 , 重写相应的方法 , 透传上下文 。
方案二:(推荐)
线程池ThreadPoolTaskExecutor , 有一个TaskDecorator装饰器 , 实现这个接口 , 透传上下文 。
方案一:继承线程池 , 重写相应的方法 , 透传上下文 。1、ThreadPoolTaskExecutor spring封装的线程池
ThreadPoolTaskExecutor 线程池代码如下:


1、MyCallable是继承Callable , 创建MyCallable对象的时候已经把Attributes对象赋值给属性context了(创建MyCallable对象的时候因为实在当前主线程创建的 , 所以是能获取到请求的Attributes) , 在执行call方法前 , 先执行了RequestContextHolder.setRequestAttributes(context);