DeferredResult实现异步处理+长轮询

本文介绍了DeferredResult与Callable功能类似,都是异步返回,但DeferredResult可直接设超时时间。阐述了其核心流程,包括定义、返回结果、任务入队及结果返回等。还以携程Apollo系统为例,说明其通过DeferredResult加超时的长轮询机制实现服务实时通知。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DeferredResult与Callable实现的功能类似,都是异步返回,只不过Callable不能直接设置超时时间,需要与FutureTask配合才行;DeferredResult可直接设置超时时间。

核心流程:

1、定义一个DeferredResult:DeferredResult<ResponseMsg<String>> deferredResult = new DeferredResult<>(OUT_OF_TIME, OUT_OF_TIME_RESULT);

2、然后在主线程中直接返回deferredResult结果;此时servlet容器线程被释放,继续服务其他请求,以此提高吞吐量,后台任务线程执行耗时长的任务

3、将任务放入队列中,后台定义一个专门执行任务的线程,循环执行队列中的任务;

4、执行完的任务,直接调用deferredResult.setResult()方法,即可将结果返回给客户端,和Callable、Future性质一样。

 

具体案例:携程Apollo系统,一个配置中心系统,使用AP模型的eureka作为配置的服务发现系统,客户端和服务端通过springmvc的DeferredResult加超时时间的长轮询机制实现服务实时通知。

1、客户端发起一个请求

2、服务端通过DeferredResult异步方式,hold住这个请求的同时释放容器线程

3、如果有变更,直接返回DeferredResult结果

4、如果没有变更,DeferredResult通过超时时间,等待60s,通过onTimeout记得把任务移除,给客户端返回超时,客户端重新发起请求

5、如果开始无变更,hold住请求后发生了变更,DeferredResult有个map集合,变更的同时看看这个集合是不是有请求变更对应appid系统配置的,有的话,从map中取出DeferredResult,并通过setResult返回结果给客户端。

 

@RestController
public class TaskController {
    private static final Logger log = LoggerFactory.getLogger(TaskController.class);
    //超时结果
    private static final ResponseMsg<String> OUT_OF_TIME_RESULT = new ResponseMsg<>(-1,"超时","out of time");

    //超时时间
    private static final long OUT_OF_TIME = 3000L;
    @Autowired
    private TaskQueue taskQueue;

    @RequestMapping(value = "/get",method = RequestMethod.GET)
    public DeferredResult<ResponseMsg<String>> getResult() {
        //建立DeferredResult对象,设置超时时间,以及超时返回超时结果
        DeferredResult<ResponseMsg<String>> result = new DeferredResult<>(OUT_OF_TIME, OUT_OF_TIME_RESULT);
        result.onTimeout(() -> {
            log.info("调用超时");
        });
        result.onCompletion(() -> {
            log.info("调用完成");
        });
        //并发,加锁
        synchronized (taskQueue) {
            taskQueue.put(result);
        }
        return result;
    }
}
@Component
public class TaskExecute {

    private static final Logger log = LoggerFactory.getLogger(TaskExecute.class);

    private static final Random random = new Random();

    //默认随机结果的长度
    private static final int DEFAULT_STR_LEN = 10;

    //用于生成随机结果
    private static final String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

    @Autowired
    private TaskQueue taskQueue;


    /**
     * 初始化启动
     */
    @PostConstruct
    public void init() {

        log.info("开始持续处理任务");

        new Thread(this::execute).start();


    }


    /**
     * 持续处理
     * 返回执行结果
     */
    private void execute() {

        while (true) {

            try {

                //取出任务
                Task task;

                synchronized (taskQueue) {

                    task = taskQueue.take();

                }

                if (task != null) {

                    //设置返回结果
                    String randomStr = getRandomStr(DEFAULT_STR_LEN);

                    ResponseMsg<String> responseMsg = new ResponseMsg<String>(0, "success", randomStr);

                    log.info("返回结果:{}", responseMsg);

                    task.getTaskResult().setResult(responseMsg);
                }

                int time = random.nextInt(10);

                log.info("处理间隔:{}秒", time);

                Thread.sleep(time * 1000L);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        }

    }

 

### Java DeferredResult 实现 HTTP 长轮询 #### 创建控制器方法返回 `DeferredResult` 为了实现 HTTP 长轮询,可以创建一个 Spring MVC 控制器方法并让其返回 `DeferredResult` 类型的对象。这允许服务器端在处理请求时不立即响应客户端,而是等待某些条件满足后再发送响应。 ```java import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; @RestController public class LongPollingController { @GetMapping("/long-poll") public DeferredResult<String> handleLongPoll() { // 设置超时时间(毫秒) final int TIMEOUT = 60 * 1000; DeferredResult<String> deferredResult = new DeferredResult<>(TIMEOUT); // 模拟后台任务完成后的回调逻辑 simulateBackendProcessing(deferredResult); return deferredResult; } private void simulateBackendProcessing(DeferredResult<String> deferredResult) { // 假设这里有一个异步的任务队列或其他机制来通知当有新数据可用时 // 当事件触发或达到最大等待时间,则设置结果给deferredResult对象 // 这里简单模拟延迟后返回消息 Thread thread = new Thread(() -> { try { Thread.sleep((int)(Math.random()*59*1000)+1000); //随机休眠1~60s // 如果在此期间内完成了工作则调用setResult() deferredResult.setResult("New data available!"); } catch (InterruptedException e) { // 处理异常情况下的错误恢复 deferredResult.setErrorResult(e.getMessage()); } }); thread.start(); } } ``` 此代码片段展示了如何定义一个 RESTful Web Service 接口 `/long-poll` ,它会启动一个新的线程去执行一些耗时操作,并在线程完成后向客户端发送更新的消息[^4]。 #### 客户端发起长轮询请求 为了让浏览器能够持续不断地查询新的信息直到接收到有效的回复为止,在前端部分可以通过 JavaScript 来周期性地发出 AJAX 请求: ```javascript function pollForUpdates(url){ fetch(url).then(response => response.text()).then(data => { console.log('Received:',data); if(!data.includes('timeout')){ alert(`Server sent message: ${data}`); } // 不论成功与否都继续下一次轮询 setTimeout(function(){pollForUpdates(url)}, 1000); }).catch(error=>console.error('Error during polling', error)); } // 开始轮询 pollForUpdates('/long-poll'); ``` 这段脚本会在每次获取到服务器回应之后再次尝试连接同一个 URL 地址,从而形成所谓的 "长轮询" 效果[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值