zoukankan      html  css  js  c++  java
  • DeferredResult使用方式和场景

    为什么使用DeferredResult?

    API接口需要在指定时间内异步操作的结果同步返回给前端时;

    Controller处理耗时任务,并且需要耗时任务的返回结果时;

    当一个请求到达API接口,如果该API接口的return返回值是DeferredResult,在没有超时或者DeferredResult对象设置setResult时,接口不会返回,但是Servlet容器线程会结束,DeferredResult另起线程来进行结果处理(即这种操作提升了服务短时间的吞吐能力),并setResult,如此以来这个请求不会占用服务连接池太久,如果超时或设置setResult,接口会立即返回

    使用DeferredResult的流程:

      1. 浏览器发起异步请求
      2. 请求到达服务端被挂起
      3. 向浏览器进行响应,分为两种情况:
        3.1 调用DeferredResult.setResult(),请求被唤醒,返回结果
        3.2 超时,返回一个你设定的结果
      4. 浏览得到响应,再次重复1,处理此次响应结果

    给人一种异步处理业务,但是却同步返回的感觉。

    场景

    浏览器向A系统发起请求,该请求需要等到B系统(如MQ)给A推送数据时,A才会立刻向浏览器返回数据;

    如果指定时间内B未给A推送数据,则返回超时。

    Demo代码

    接口代码:

    /get是调用A系统的接口返回数据;

    /result模拟B系统向A推送数据进行setResult。

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.context.request.async.DeferredResult;
    
    
    @RestController
    @RequestMapping(value = "/deferred-result")
    public class DeferredResultController {
    
        @Autowired
        private DeferredResultService deferredResultService;
    
        /**
         * 为了方便测试,简单模拟一个
         * 多个请求用同一个requestId会出问题
         */
        private final String requestId = "haha";
    
    
        @GetMapping(value = "/get")
        public DeferredResult<DeferredResultResponse> get(@RequestParam(value = "timeout", required = false, defaultValue = "10000") Long timeout) {
            DeferredResult<DeferredResultResponse> deferredResult = new DeferredResult<>(timeout);
    
            deferredResultService.process(requestId, deferredResult);
    
            return deferredResult;
        }
    
        /**
         * 设置DeferredResult对象的result属性,模拟异步操作
         * @param desired
         * @return
         */
        @GetMapping(value = "/result")
        public String settingResult(@RequestParam(value = "desired", required = false, defaultValue = "成功") String desired) {
            DeferredResultResponse deferredResultResponse = new DeferredResultResponse();
            if (DeferredResultResponse.Msg.SUCCESS.getDesc().equals(desired)){
                deferredResultResponse.setCode(HttpStatus.OK.value());
                deferredResultResponse.setMsg(desired);
            }else{
                deferredResultResponse.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
                deferredResultResponse.setMsg(DeferredResultResponse.Msg.FAILED.getDesc());
            }
            deferredResultService.settingResult(requestId, deferredResultResponse);
    
            return "Done";
        }
    }
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Service;
    import org.springframework.web.context.request.async.DeferredResult;
    
    import java.util.Map;
    import java.util.Optional;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.function.Consumer;
    
    @Service
    public class DeferredResultService {
    
        private Map<String, Consumer<DeferredResultResponse>> taskMap;
    
        public DeferredResultService() {
            taskMap = new ConcurrentHashMap<>();
        }
    
        /**
         * 将请求id与setResult映射
         *
         * @param requestId
         * @param deferredResult
         */
        public void process(String requestId, DeferredResult<DeferredResultResponse> deferredResult) {
            // 请求超时的回调函数
            deferredResult.onTimeout(() -> {
                taskMap.remove(requestId);
                DeferredResultResponse deferredResultResponse = new DeferredResultResponse();
                deferredResultResponse.setCode(HttpStatus.REQUEST_TIMEOUT.value());
                deferredResultResponse.setMsg(DeferredResultResponse.Msg.TIMEOUT.getDesc());
                deferredResult.setResult(deferredResultResponse);
            });
    
            Optional.ofNullable(taskMap)
                    .filter(t -> !t.containsKey(requestId))
                    .orElseThrow(() -> new IllegalArgumentException(String.format("requestId=%s is existing", requestId)));
    
            // 这里的Consumer,就相当于是传入的DeferredResult对象的地址
            // 所以下面settingResult方法中"taskMap.get(requestId)"就是Controller层创建的对象
            taskMap.putIfAbsent(requestId, deferredResult::setResult);
        }
    
        /**
         * 这里相当于异步的操作方法
         * 设置DeferredResult对象的setResult方法
         *
         * @param requestId
         * @param deferredResultResponse
         */
        public void settingResult(String requestId, DeferredResultResponse deferredResultResponse) {
            if (taskMap.containsKey(requestId)) {
                Consumer<DeferredResultResponse> deferredResultResponseConsumer = taskMap.get(requestId);
                // 这里相当于DeferredResult对象的setResult方法
                deferredResultResponseConsumer.accept(deferredResultResponse);
                taskMap.remove(requestId);
            }
        }
    
    }
    import lombok.Data;
    import lombok.Getter;
    
    @Data
    public class DeferredResultResponse {
        private Integer code;
        private String msg;
    
        public enum Msg {
            TIMEOUT("超时"),
            FAILED("失败"),
            SUCCESS("成功");
    
            @Getter
            private String desc;
    
            Msg(String desc) {
                this.desc = desc;
            }
        }
    }

    测试

    1. 超时

    设置超时时间为8s,会一直阻塞8s,如果超时,返回默认超时的结果

     8s过去,没有setResult,返回超时

    2. 进行setResult

    在阻塞期间调用setResult,我这里模拟的是B系统推送数据给A时抛异常失败的情况,会立刻得到返回结果

    参考:

    https://my.oschina.net/ojeta/blog/806087

  • 相关阅读:
    JAVA调用WebService总结
    关于购物车的想法
    ASP.NET中初试Ajax
    转帖:从FxCop归纳出来的一些规范建议
    数据结构(二叉树)C#描述
    FormView控件和DetailsGridView控件实现MasterSlave
    在.NET中使用MySql数据库
    Oracle学习总结1
    Oracle学习总结2
    关于字符匹配所引起的的问题
  • 原文地址:https://www.cnblogs.com/theRhyme/p/10846349.html
Copyright © 2011-2022 走看看