DeferredResult 是如何实现异步处理请求的
创始人
2024-11-15 09:36:39
0

最近遇到了一个问题,我们的一个接口需要去轮询另一个第三方接口,导致这个接口占用了太多工作线程,这些工作线程长时间 running,我们需要解决这个问题。
于是,我们的方案是:用 DeferredResult 实现接口异步。
我们下面讲讲原理 …

DeferredResult 所属包:package org.springframework.web.context.request.async;
我们先实测一波:

    @PostMapping("/pay3")     public DeferredResult pay3() {         log.info("开始支付3...");          DeferredResult result = new DeferredResult<>(60000L);         new Thread(() -> {             try {                 result.setResult(checkPayStatus());             } catch (Throwable cause) {                 result.setErrorResult(cause.getMessage());             }         }).start();         return result;     }          private Integer checkPayStatus() {         for (int i = 0; i < 5; i++) {             try {                 log.info("查询支付状态,第 {} 次查询", i);                 Thread.sleep(10000L);             } catch (InterruptedException e) {                 throw new RuntimeException(e);             }         }         log.info("查询支付状态返回成功");         return 1;     } 

工作线程 XNIO-1 task-2
创建线程后,就跑到了最后的返回。
在这里插入图片描述
按以前都是直接返回结果了,但是由于我们是声明了 DeferredResult 作为 SpringMVC 的返回参数,则此时返回结果并没有真的返回(接口没有返回),但工作线程也没有被阻塞住,工作线程为 WAIT 状态(TIMED_WAITING)。
在这里插入图片描述
工作线程是什么时候挂起的呢?
探究如下:

    @PostMapping("/pay3")     public DeferredResult pay3() {         log.info("开始支付3...");          DeferredResult result = new DeferredResult<>(60000L);         new Thread(() -> {             try {                 result.setResult(checkPayStatus());             } catch (Throwable cause) {                 result.setErrorResult(cause.getMessage());             }         }).start();         try {             log.info("返回前的主线程等待 开始..");             Thread.sleep(100000L);             log.info("返回前的主线程等待 结束..");         } catch (InterruptedException e) {             throw new RuntimeException(e);         }         log.info("返回结果");         return result;     } 

在这里插入图片描述
从执行结果可以看出,如果没有走到 return 结果,那么 SpringMVC 是不会将工作线程挂起的,这也很好理解。
在这里插入图片描述
连接是被hold住的,响应是最后才返回给客户端,我们的代码就在这中间(前提是我们开启了新线程)
而且 有别于 Callable 是 hold 住异步代码,Deferred 是 hold 住返回值。

https://stackoverflow.com/questions/17855852/difference-between-spring-mvcs-async-deferredresult-and-callable

在这里插入图片描述
这里用的是 ForkJoinPool.commonPool() 公共线程池去创建子工作的例子。
在这里插入图片描述
创建了新的线程意味着更多的计算资源,但是工作线程不会被阻塞,因此可以处理更多的请求。
这也在我们测试中被验证,如果不用此方法,我们的 undertow 容器默认的 16 工作线程根本不够用,会导致 k8s 重启 容器。

https://www.baeldung.com/spring-deferred-result
在这里插入图片描述
这里用的是 CompletableFuture 异步处理去创建的,跟上面是一个道理。
https://www.javacodegeeks.com/2015/07/understanding-callable-and-spring-deferredresult.html

官方文献
在这里插入图片描述
前半段比较有含金量,就是说 DeferredResult 是 Callable 的替代,两者都可以实现接口的异步,但是DeferredResult 是可以让子线程去协助返回的,也就是说我们有更多的操作空间。后半段就是说可以通过继承或者其他操作来完成更多的骚操作。

综上我们可以发现,几个关键词:

  1. 异步工作 asynchronous task
  2. 和 Callable 的相似性
  3. 是springmvc的东西,不能脱离spring进行。(我们知道 Callable 是 java.util.concurrent 的东西)
  4. 一般是用来处理长等待的请求。
  5. 服务器释放

DeferredResult 是不能不创建子线程实现异步的。
测试如下:

    @PostMapping("/pay4")     public DeferredResult pay4() {         log.info("开始支付4...");          DeferredResult result = new DeferredResult<>(60000L);         result.setResult(checkPayStatus());         return result;     } 

我们稍微思考下就可以得知,我们的长逻辑直接在工作线程中跑了,自然是阻塞了。
~~

不过,需要注意的是,对于前端,或者这个接口的调用方来说,接口依然是同步的。
我们的接口相当于一个黑盒,我们内部进行的异步让我们可以用其他线程帮助处理业务逻辑,工作线程可以去协调这些工作逻辑,从而实现同时处理更多请求。

创作不易,希望大佬们点赞、收藏、关注~

相关内容

热门资讯

一分钟内幕!科乐吉林麻将系统发... 一分钟内幕!科乐吉林麻将系统发牌规律,福建大玩家确实真的是有挂,技巧教程(有挂ai代打);所有人都在...
一分钟揭秘!微扑克辅助软件(透... 一分钟揭秘!微扑克辅助软件(透视辅助)确实是有挂(2024已更新)(哔哩哔哩);1、用户打开应用后不...
五分钟发现!广东雀神麻雀怎么赢... 五分钟发现!广东雀神麻雀怎么赢,朋朋棋牌都是是真的有挂,高科技教程(有挂方法)1、广东雀神麻雀怎么赢...
每日必看!人皇大厅吗(透明挂)... 每日必看!人皇大厅吗(透明挂)好像存在有挂(2026已更新)(哔哩哔哩);人皇大厅吗辅助器中分为三种...
重大科普!新华棋牌有挂吗(透视... 重大科普!新华棋牌有挂吗(透视)一直是有挂(2021已更新)(哔哩哔哩)1、完成新华棋牌有挂吗的残局...
二分钟内幕!微信小程序途游辅助... 二分钟内幕!微信小程序途游辅助器,掌中乐游戏中心其实存在有挂,微扑克教程(有挂规律)二分钟内幕!微信...
科技揭秘!jj斗地主系统控牌吗... 科技揭秘!jj斗地主系统控牌吗(透视)本来真的是有挂(2025已更新)(哔哩哔哩)1、科技揭秘!jj...
1分钟普及!哈灵麻将攻略小,微... 1分钟普及!哈灵麻将攻略小,微信小程序十三张好像存在有挂,规律教程(有挂技巧)哈灵麻将攻略小是一种具...
9分钟教程!科乐麻将有挂吗,传... 9分钟教程!科乐麻将有挂吗,传送屋高防版辅助(总是存在有挂)1、完成传送屋高防版辅助透视辅助安装,帮...
每日必看教程!兴动游戏辅助器下... 每日必看教程!兴动游戏辅助器下载(辅助)真是真的有挂(2025已更新)(哔哩哔哩)1、打开软件启动之...