当前位置: 首页 > news >正文

Spring DeferredResult 实现长轮询

1、背景

在项目开发中,有一个流程性的方法执行,这个方法会调用各种方法,可能会导致时间比较长 ,如果一直等待响应结果的话,可能会造成超时,如果直接使用异步的方式的话,前端无法知道整体流程什么时候会结束,

2、解决方案

使用了DeferredResult 的方式,设置超时时间,当流程执行完了没有超过指定时间就可以直接返回结果,如果超过了指定时间就给前端先返回超时结果,并且指定一个唯一标志放到结果中返回,前端后续可以拿着这个唯一标志来轮询,知道返回执行完成

关于 DeferredResult :请求的处理线程(即 tomcat 线程池的线程)不会等DeferredResult#setResult() 被调用才释放,而是直接释放了

也就是说 tomcat 线程安排好 DeferredResult 的一些配置后,不会等逻辑处理完(DeferredResult->setResult()的调用或者超时)。

而是直接释放了,这样 tomcat 线程就被回收到线程池中了,可以响应其他请求,不会傻傻地阻塞等着 DeferredResult->setResult() 被调用或超时。

我们都知道 tomcat 的线程池大小是有限的,如果我们的一些业务逻辑处理慢的话,会渐渐地占满 tomcat 线程,这样就无法处理新的请求,所以一些处理缓慢的业务我们会放到业务线程池中处理,但单纯的放到业务线程池中处理的话,我们无法得知其什么时候处理完,也无法将处理完的结果和之前的请求匹配上,所以常做的方式就是轮询。

而 DeferredResult 的做法就类似仅把事情安排好,不会管事情做好没,tomcat 线程就释放走了,注意此时不会给请求方(如浏览器)任何响应,而是将请求存放在一边,等后面有结果了再把之前的请求拿来,把值响应给请求方。

用简单的话来总结下 Spring DeferredResult :如果返回值类型是 DeferredResult 则表明其是异步请求,tomcat 线程不会等到应用程序处理完或者超时,而是会立即释放线程。

而这个未处理完的请求则会暂存,tomcat 知晓其为异步请求,也不会对客户端进行响应,直至 tomcat 线程扫描到请求超时或者应用线程将 result 塞入到 DeferredResult 中。

3、一些常用方法

public void onTimeout(Runnable callback)

public void onError(Consumer<Throwable> callback)

public void onCompletion(Runnable callback)

public boolean setResult(T result)

  • onTimeout():仅超时触发。
  • onError():仅异步任务抛出异常时触发。
  • onCompletion() 是兜底回调,无论何种结束方式都会执行,适合释放共享资源(如移除缓存、关闭连接)
  • setResult() 设置返回结果集

4、具体简单代码实现

// -1 表示任务未完成 0 表示任务失败 其它是具体值private static final Map<String,String> EXEC_CACHE  = new ConcurrentHashMap<>();@Overridepublic DeferredResult<Map<String, String>> exec() {DeferredResult<Map<String, String>> deferredResult = new DeferredResult<>(5000L);// 设置超时回调(如果任务未在 2 秒内完成,返回超时响应)String flag = UUID.randomUUID().toString();Map<String, String> result = new HashMap<>();deferredResult.onTimeout(() -> {result.put("code", "410");result.put("message", "任务未完成,已超时!");result.put("flag",flag);deferredResult.setResult(result);EXEC_CACHE.put(flag, "-1");});//执行具体任务CompletableFuture.runAsync(()->{try {//模拟执行耗时Thread.sleep(1000);//模拟获到执行结果String execResult = UUID.randomUUID().toString();//放入缓存EXEC_CACHE.put(flag, execResult);//完成响应result.put("code", "200");result.put("message", "任务完成");result.put("flag",execResult);deferredResult.setResult(result);}catch (Exception e){//完成响应result.put("code", "500");result.put("message", "任务完成");result.put("flag",null);deferredResult.setResult(result);EXEC_CACHE.put(flag, "0");}});return deferredResult;}@Overridepublic String queryExecResult(String flag) {//前端根据具体值判断要不要继续轮询return EXEC_CACHE.get(flag);}

执行超时情况

轮询查询

未超时返回

http://www.dtcms.com/a/293805.html

相关文章:

  • Http证书体系及证书加密流程(通信流程)
  • 第九章 W55MH32 HTTP Server示例
  • ARM入门学习方法分享
  • 2025年华为HCIA人工智能认证发展前景如何?客观分析!
  • 7月23日华为机考真题第一题100分
  • LazyVim 加载顺序
  • react18更新哪些东西
  • 5G时代PCB设计新突破:猎板三大创新技术重塑高频信号完整性
  • ES6 标签模板:前端框架的灵活利器
  • 2025年电赛--电源题赛前押题
  • LeetCode 460:LFU 缓存
  • LeetCode热题100--383
  • 时序数据库主流产品概览
  • 基于单片机排队叫号系统设计
  • 自动化运维:从脚本到DevOps的演进
  • Win10_Qt6_C++_YOLO推理 -(1)MingW-opencv编译
  • 人工智能——Opencv图像色彩空间转换、灰度实验、图像二值化处理、仿射变化
  • 腾讯iOA:企业软件合规与安全的免费守护者
  • 建数仓该选大SQL还是可视化ETL平台?
  • kotlin基础【2】
  • kettle 8.2 ETL项目【一、初始化数据库及介绍】
  • UniappDay01
  • Django学习之旅--第13课:Django模型关系进阶与查询优化实战
  • 傅里叶转换(机器视觉方向)
  • Oracle19c HINT不生效?
  • Unreal5从入门到精通之使用 Python 编写虚幻编辑器脚本
  • WWDC 25 给自定义 SwiftUI 视图穿上“玻璃外衣”:最新 Liquid Glass 皮肤详解
  • 设备虚拟化——软堆叠技术
  • CNN正则化:Dropout与DropBlock对比
  • iOS开发 Swift 速记7:结构体和类