【解决方案系列】 利用Spring DeferredResult实现异步API设计

357 阅读1分钟

使用场景

适用于处理前端轮询,避免轮询请求次数过于频繁造成服务器压力

实现效果

前端请求后不能立刻获得返回,在延时期间若某项业务完成,则主动返回,若期间未有指定业务完成,则指定延时时间后进行下一次长轮询。(apollo实现原理)

下面模拟apollo 配置实时变更,来阐述DeferredResult的用法

实现方式

@RestController
public class ApolloController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    //guava中的Multimap,多值map,对map的增强,一个key可以保持多个value
    private Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedSetMultimap(HashMultimap.create());



    //模拟长轮询
    @RequestMapping(value = "/watch/{namespace}", method = RequestMethod.GET, produces = "text/html")
    public DeferredResult<String> watch(@PathVariable("namespace") String namespace) {
        logger.info("Request received");
        DeferredResult<String> deferredResult = new DeferredResult<>();
        //当deferredResult完成时(不论是超时还是异常还是正常完成),移除watchRequests中相应的watch key

        deferredResult.onCompletion(new Runnable() {
            @Override
            public void run() {
                System.out.println("remove key:" + namespace);
                watchRequests.remove(namespace, deferredResult);
            }
        });
        watchRequests.put(namespace, deferredResult);
        logger.info("Servlet thread released");
        return deferredResult;


    }

    //模拟发布namespace配置
    @RequestMapping(value = "/publish/{namespace}", method = RequestMethod.GET, produces = "text/html")
    public Object publishConfig(@PathVariable("namespace") String namespace) {
        if (watchRequests.containsKey(namespace)) {
            Collection<DeferredResult<String>> deferredResults = watchRequests.get(namespace);
            Long time = System.currentTimeMillis();
            //通知所有watch这个namespace变更的长轮训配置变更结果
            for (DeferredResult<String> deferredResult : deferredResults) {
                deferredResult.setResult(namespace + " changed:" + time);
            }
        }
        return "success";

    }
}

使用场景

【电商支付场景】 前端需要根据支付的结果,跳转到不同的页面中。而我们的支付通知是支付方异步通知回来的,因此在发出支付请求后 无法立即获取到支付结果,此时我们就需要轮训交易结果,判断是否支付成功。 可参考 juejin.cn/post/701914…

总结

比普通轮询的好处是不需要多次请求占用服务器资源,且不占用主线程,和websocket类似

参考

github.com/ctripcorp/a…