有用过 Dubbo 的异步调用吗?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
- 是否真用过 Dubbo:这题不是让你背概念,面试官更想听你有没有在项目里处理过远程调用耗时、并行调用、接口聚合这类问题。
- 异步调用理解:要能讲清楚异步调用和同步调用的差别,尤其是 “调用线程会不会一直阻塞等结果”。
- 生产风险意识:异步不是万能药。超时、异常、线程池、上下文传递、结果聚合,这些没想清楚,线上排查会很难受。
核心答案
面试里可以这么说:
用过。Dubbo 的异步调用一般用在 远程调用耗时比较长、多个接口可以并行执行、当前线程没必要一直等结果 的场景里。
同步调用时,消费者线程发起 RPC 后会等 provider 返回;异步调用则是先把请求发出去,马上拿到一个 CompletableFuture 或类似的异步结果,后面再通过回调、future.get()、thenApply()、thenCombine() 等方式处理返回值。
我通常会在接口聚合场景里用它。比如一个详情页要同时查用户信息、订单信息、优惠信息,如果这 3 个接口互不依赖,就可以并行发起 Dubbo 异步调用,最后再汇总结果。这样总耗时更接近最慢的那个接口,而不是 3 个接口的耗时直接相加。
深度解析
一、同步调用和异步调用差在哪?
| 调用方式 | 调用线程行为 | 适合场景 | 问题 |
|---|---|---|---|
| 同步调用 | 发起 RPC 后阻塞等待结果 | 简单 CRUD、强依赖链路 | 多个接口串行时耗时累加 |
| 异步调用 | 发起 RPC 后先返回 Future |
聚合查询、并行远程调用 | 代码复杂度更高,异常处理更麻烦 |
同步调用很好理解:
UserDTO user = userService.getUser(userId);
OrderDTO order = orderService.getOrder(orderId);
这段代码会先等 getUser() 返回,再调用 getOrder()。如果两个接口各耗时 100 ms,总耗时大概就是 200 ms 起步。
异步调用的思路也简单:如果两个接口没有依赖关系,就别一个一个排队了,直接一起跑。
二、Dubbo 异步调用大概怎么写?
现在更推荐的方式,是接口直接返回 CompletableFuture:
public interface UserService {
CompletableFuture<UserDTO> getUserAsync(Long userId);
}
消费者侧可以这样用:
CompletableFuture<UserDTO> userFuture = userService.getUserAsync(userId);
CompletableFuture<OrderDTO> orderFuture = orderService.getOrderAsync(orderId);
CompletableFuture<ResultDTO> resultFuture = userFuture.thenCombine(orderFuture, (user, order) -> {
ResultDTO result = new ResultDTO();
result.setUser(user);
result.setOrder(order);
return result;
});
ResultDTO result = resultFuture.get();
这段代码的重点不是语法,而是执行方式:getUserAsync() 和 getOrderAsync() 可以先并行发出去,最后通过 thenCombine() 把结果合起来。
老版本 Dubbo 里,也能看到通过 async=true 配置,再从 RpcContext 里拿 Future 的写法。不过新项目里,我更倾向于用 CompletableFuture。语义清楚,也更贴近 Java 8 之后的异步编程习惯。
三、调用流程是什么样的?
这条链路里,业务线程发起调用后,不会一直卡在远程调用上。Dubbo 代理先把请求发出去,然后返回一个异步结果对象。等 provider 返回结果后,网络线程或相关执行线程会把结果写回 CompletableFuture,业务代码再通过回调或阻塞方式拿最终结果。
有一点要讲清楚:异步调用不会让远程接口本身变快,它只是减少调用方无意义的等待。 如果 provider 处理一次请求就是要 500 ms,那还是 500 ms。异步的价值主要在并行、削峰,以及释放调用线程。
四、哪些场景适合用?
比较适合的场景:
- 接口聚合:页面详情、首页聚合、报表聚合,需要并行查多个服务。
- 非强依赖调用:主流程不需要马上拿结果,比如异步记录日志、异步通知,不过这类场景也要考虑 MQ 会不会更合适。
- 耗时远程调用:调用第三方或跨机房服务时,不想让业务线程长时间阻塞。
不太适合的场景也不少。比如下单、扣库存、支付这种强一致链路,异步不是不能用,但要非常谨慎。超时、失败补偿、幂等、事务边界都得提前设计好,不然后面出了问题很难补。
五、生产里最容易踩的坑
第一个坑是 只异步发送,最后又立刻 get()。
CompletableFuture<UserDTO> userFuture = userService.getUserAsync(userId);
UserDTO user = userFuture.get();
这样写当然能跑,但如果你发起异步后马上阻塞等待,收益就很小。更有价值的写法,是先把多个请求都发出去,再统一等待或组合结果。
第二个坑是 异常处理缺失。远程调用可能超时、网络失败,也可能是 provider 自己抛异常。异步链路里如果没写 exceptionally()、handle() 这类逻辑,很容易出现结果丢失,或者异常被包了好几层,排查时很烦。
第三个坑是 线程池和上下文问题。异步回调在哪个线程执行、会不会阻塞 Dubbo IO 线程、日志链路里的 traceId 能不能传过去,这些生产环境都要看。回调里别写重活,必要时切到业务线程池。
面试高频追问
-
Dubbo 异步调用能提升接口本身性能吗?
不能直接提升 provider 的处理速度。它提升的是调用方的并发编排能力,减少串行等待。
-
异步调用和 MQ 异步有什么区别?
Dubbo 异步本质上还是一次 RPC,请求和响应关系比较明确;MQ 更适合解耦、削峰、最终一致。需要拿返回结果时,更适合 Dubbo 异步;不关心立即结果时,可以考虑 MQ。
-
异步调用怎么处理超时?
Dubbo 本身有超时控制,业务侧也可以对
CompletableFuture做超时兜底。关键是先定清楚失败后怎么处理:返回默认值、走降级,还是直接失败。 -
多个异步结果怎么聚合?
简单场景可以用
thenCombine(),多个任务可以用CompletableFuture.allOf(),然后分别取结果并做异常兜底。
常见面试变体
- “Dubbo 异步调用怎么用?”
- “Dubbo 异步调用适合什么场景?”
- “Dubbo 异步调用和同步调用有什么区别?”
- “
CompletableFuture在 Dubbo 里怎么配合使用?” - “Dubbo 异步调用有哪些坑?”
记忆口诀
能并行就异步,强依赖就同步;先发出去,再合结果;异常超时,必须兜底。
总结
Dubbo 异步调用的价值,不是让 provider 执行得更快,而是让消费者别傻等多个远程调用串行返回。真正用好它,要会用 CompletableFuture 组合结果,也要把超时、异常、线程池和上下文传递这些生产问题想明白。
