Spring DeferredResult 实现长轮询

发布于:2025-07-24 ⋅ 阅读:(20) ⋅ 点赞:(0)

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<>();

    @Override
    public 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;
    }

    @Override
    public String queryExecResult(String flag) {
        //前端根据具体值判断要不要继续轮询
        return EXEC_CACHE.get(flag);
    }

执行超时情况

轮询查询

未超时返回


网站公告

今日签到

点亮在社区的每一天
去签到