5月27日 23:23
如何实现 RPC 的异步调用?
答案
RPC 异步调用的核心思路是:客户端发起调用后不阻塞等待响应,而是通过 Future、回调或响应式流等机制在结果就绪时获取。常见有三种模式:
- Future/Promise — 调用立即返回 Future 对象,调用方自行决定何时获取结果。Dubbo 原生支持
async=true,通过RpcContext.getFuture()拿到返回值。 - Callback 回调 — 传入回调接口,服务端响应到达时自动触发。适合事件驱动场景,但多层嵌套易产生回调地狱。
- 响应式流(Reactive) — 基于 Reactor/RxJava,以 Mono/Observable 表示异步结果,支持背压和链式组合,适合流式处理。
此外,Java 8 的 CompletableFuture 兼具 Future 和回调的优点,支持 thenCombine 组合多个异步结果,是目前最常用的异步编排工具。gRPC 则通过 StreamObserver 实现异步,并原生支持双向流通信。
异步调用的优势
- 不阻塞调用线程,单线程可同时处理多个请求,提高并发能力和吞吐量
- 并行调用多个服务,用
CompletableFuture.allOf或响应式zip组合结果,显著降低总延迟 - 避免资源浪费,线程不必在 I/O 等待上空转
关键挑战
- 上下文传递:异步线程切换时 TraceId、用户信息等上下文易丢失,需用 TransmittableThreadLocal 显式传递
- 超时控制:必须设超时并取消,否则请求可能无限挂起。
future.get(timeout)+future.cancel(true)是基本模式 - 线程池管理:异步任务不能无限制创建线程,需配置有界线程池并监控队列积压
追问
Q: 同步调用和异步调用怎么选?
调用方需要立即拿到结果才能继续(如下单扣库存),用同步;调用方不依赖结果或可以后续处理(如发通知、写日志),用异步。实际中大部分 RPC 调用用同步,并行调用多个服务时用异步组合。
Q: CompletableFuture 和 Reactor 的区别是什么?
CompletableFuture 处理单值异步结果,API 简单,适合多数业务场景;Reactor 基于响应式流规范,支持多值序列和背压,适合流式数据处理,但学习成本更高。如果只是组合几个 RPC 调用,CompletableFuture 足够。
Q: 异步调用失败怎么重试?
不要简单循环重试,应采用指数退避策略(如初始 100ms,每次翻倍,最多 3 次),配合熔断器在连续失败时快速失败。CompletableFuture 可用 exceptionally 或 handle 捕获异常后触发重试逻辑。