Spring 异步线程中传递ThreadLocal

上周熬夜查了一个线上问题,最后复盘会议却没有深入,部门里还是要求快,根据熵增定律,软件系统需要定期注入能力,以维持系统稳定,如果只求快,那么就是牺牲系统的扩展性、健壮性、稳定性。

后期总结一篇生产环境排查问题指南。

今天主体是一个补丁,设计dubbo 超时,spring 异步方法,spring 异步线程传递ThreadLocal 信息。

一、dubbo 超时怎样

{"code":"9","msg":"服务方法(xxx.xxx.xxx:1.0)业务逻辑出错","body":"java.lang.RuntimeException: com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method execute in the service com.fancv.xxx.ccc.client.DamClient. Tried 1 times of the providers [101.51.21.415:30001] (1/1) from the registry zoo1.wwww.cc:30002 on the consumer 12.50.12.148 using the dubbo version 2.5.3. Last error is: Invoke remote method timeout. method: execute, provider: dubbo://10.50.2.145:30001/com.fancv.xxx.ccc.client.DamClient?anyhost=true&application=fancv-wede-sop&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=dubbo&interface=com.fancv.xxx.ccc.client.DamClient&metadata-type=remote&methods=execute&pid=411&pln=fancv-main-sop-gray&protocol=dubbo&release=2.7.7&retries=-1&revision=1.0.0.gray-SNAPSHOT&side=consumer&timeout=5000&timestamp=1640324905371&version=1.0.0-gray, cause: Waiting server-side response timeout by scan timer. start time: 2021-12-27 18:30:28.986, end time: 2021-12-27 18:30:34.018, client elapsed: 0 ms, server elapsed: 5032 ms, timeout: 5000 ms, request: Request [id=180989, version=2.0.0, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=execute, parameterTypes=[class com.fancv.www.sdk.api.RopRequest], 

报错日志显示 dubbo 服务调用超时,测试反馈页面报错,但是程序逻辑执行成功了。

二、 改进方法

spring 异步调用,一般情况下能利用成熟框架的功能就不要自己造轮子,这里采用spring的 @Async 注解

使用方法自行研究

参考 https://www.jianshu.com/p/2d4b89c7a3f1

三、改进中遇到的问题

发现整个文件复制的业务逻辑流程需要用户的登录状态,显而易见,大多数系统会把用户的登录状态放入在Context 中,使用ThreadLocal 的方式进行传递,那么起了一个异步线程之后,登录信息如果不做任何处理就消失了,程序报错。

1.方法传递,把用户信息当做一个参数 传递的新的线程

简单,容易理解

2.很多场景需要异步调用,那么可以使用一个自定义的注解

拦截件处理加了注解的方法,增加ThreadLocal的传递过程。

或许不就的将来spring 框架会支持。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
异步线程获取到的request为空,这是因为异步线程和原始的请求线程不在同一个线程异步线程无法直接访问原始的请求线程的request对象。解决这个问题的方法有两种: 1. 使用Callable接口:可以将异步任务封装成一个Callable对象,并将原始的request对象作为参数传递给Callable对象。在异步任务,可以通过Future对象获取到Callable对象的返回值,从而获取到request对象。 代码示例: ``` @RestController public class MyController { @Autowired private AsyncService asyncService; @RequestMapping("/test") public String test(HttpServletRequest request) throws Exception { Callable<String> task = () -> { // 异步任务获取request对象 HttpServletRequest asyncRequest = AsyncRequestContextHolder.getRequest(); // 处理业务逻辑 return "success"; }; Future<String> future = asyncService.execute(task); String result = future.get(); return result; } } @Service public class AsyncService { @Autowired private AsyncTaskExecutor taskExecutor; public <T> Future<T> execute(Callable<T> task) { AsyncRequestContextCallable<T> callable = new AsyncRequestContextCallable<>(task); return taskExecutor.submit(callable); } } public class AsyncRequestContextCallable<T> implements Callable<T> { private final Callable<T> task; private final HttpServletRequest request; public AsyncRequestContextCallable(Callable<T> task) { this.task = task; this.request = AsyncRequestContextHolder.getRequest(); } @Override public T call() throws Exception { AsyncRequestContextHolder.setRequest(request); try { return task.call(); } finally { AsyncRequestContextHolder.resetRequest(); } } } public class AsyncRequestContextHolder { private static final ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<>(); public static void setRequest(HttpServletRequest request) { requestHolder.set(request); } public static HttpServletRequest getRequest() { return requestHolder.get(); } public static void resetRequest() { requestHolder.remove(); } } ``` 2. 使用ServletRequestAttributes:可以使用Spring提供的ServletRequestAttributes类来获取request对象。这个类是一个请求属性的存储器,可以在任何线程存储和获取请求属性。在异步任务,可以通过ServletRequestAttributes来获取到原始的request对象。 代码示例: ``` @RestController public class MyController { @Autowired private AsyncService asyncService; @RequestMapping("/test") public String test(HttpServletRequest request) throws Exception { Callable<String> task = () -> { // 异步任务获取request对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest asyncRequest = attributes.getRequest(); // 处理业务逻辑 return "success"; }; Future<String> future = asyncService.execute(task); String result = future.get(); return result; } } @Service public class AsyncService { @Autowired private AsyncTaskExecutor taskExecutor; public <T> Future<T> execute(Callable<T> task) { return taskExecutor.submit(() -> { RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); try { RequestContextHolder.setRequestAttributes(attributes, true); return task.call(); } finally { RequestContextHolder.resetRequestAttributes(); } }); } } ``` 以上两种方法都可以解决异步线程获取request对象为空的问题,具体选择哪种方法取决于具体的业务需求和开发习惯。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值