@Async标注的回调方法执行顺序与调用顺序相反该如何解决?
回调方法执行顺序异常修复方案
根因分析
两个回调方法均添加了@Async注解,调用时会作为独立任务提交到Spring异步线程池,线程池任务调度顺序、两个回调的网络请求耗时均存在不确定性,因此会出现后调用的sendSuccessCallback先执行完成的问题。
可选修复方案
方案1:封装顺序逻辑到同一异步方法(推荐)
如果需要保留异步能力、避免阻塞支付主流程,可以将两个回调的顺序执行逻辑封装到同一个异步方法中,仅提交一个异步任务,任务内部按固定顺序执行两个回调:
// SessionsCallbackService 新增方法 @Async public void sendSequentialCallback(String sessionSecret, BaseCallbackRequest params) { // 内部按顺序调用,保证update执行完成后再执行success sendUpdateCallback(sessionSecret, params); sendSuccessCallback(sessionSecret, params); } // 原有两个方法移除@Async注解 public void sendUpdateCallback(String sessionSecret, BaseCallbackRequest params) { log.error("sendUpdateCallback"); String url = createCallbackRequestUrl(createSessionPath(sessionSecret) + "/update"); sendSessionCallback(url, sessionSecret, params); } public void sendSuccessCallback(String sessionSecret, BaseCallbackRequest params) { log.error("sendSuccessCallback"); String url = createCallbackRequestUrl(createSessionPath(sessionSecret) + "/success"); sendSessionCallback(url, sessionSecret, params); }
调用处调整为:
SessionSuccessCallbackRequest params = new SessionSuccessCallbackRequest(userId, status); if (!StringUtils.isEmpty(sessionSecret)) { sessionsCallbackService.sendSequentialCallback(sessionSecret, params); }
该方案实现成本低,既保留异步能力不阻塞主流程,也能100%保证执行顺序。
方案2:使用CompletableFuture异步编排
如果需要保留两个方法的独立异步调用能力,可以调整方法返回值为CompletableFuture,通过异步编排保证执行顺序:
// 调整回调方法返回值 @Async public CompletableFuture<Void> sendUpdateCallback(String sessionSecret, BaseCallbackRequest params) { log.error("sendUpdateCallback"); String url = createCallbackRequestUrl(createSessionPath(sessionSecret) + "/update"); sendSessionCallback(url, sessionSecret, params); return CompletableFuture.completedFuture(null); } @Async public CompletableFuture<Void> sendSuccessCallback(String sessionSecret, BaseCallbackRequest params) { log.error("sendSuccessCallback"); String url = createCallbackRequestUrl(createSessionPath(sessionSecret) + "/success"); sendSessionCallback(url, sessionSecret, params); return CompletableFuture.completedFuture(null); }
调用处调整为:
SessionSuccessCallbackRequest params = new SessionSuccessCallbackRequest(userId, status); sessionsCallbackService.sendUpdateCallback(sessionSecret, params) // 等待update执行完成后再触发success .thenRun(() -> { if (!StringUtils.isEmpty(sessionSecret)) { sessionsCallbackService.sendSuccessCallback(sessionSecret, params); } });
方案3:移除@Async注解同步执行
如果对接口性能要求不高,可以直接去掉两个方法上的@Async注解,两个方法会在当前主线程按调用顺序同步执行,天然保证顺序。缺点是回调请求的耗时会直接叠加到支付接口的响应耗时上,若回调超时会影响主流程。
内容的提问来源于stack exchange,提问作者Morozov
相关产品推荐
相关产品推荐

