有用过 Dubbo 的异步调用吗?

#中间件#面试八股

有用过 Dubbo 的异步调用吗?

结论先行:用过。Dubbo 的异步调用分三代:2.6 的 async=true 配 RpcContext.getFuture(),2.7 起 getFuture() 返回 CompletableFuture,再往后推荐接口直接返回 CompletableFuture。本质是把“调用线程阻塞等响应”改成“调用发出后立刻返回,响应到达时回调”,用少量线程撑住高并发。

一、三种写法

// ① 2.6 时代:显式 async + Future
@DubboReference(async = true)
private UserService userService;

public void oldStyle() {
    userService.getById(1L);
    Future<Object> f = RpcContext.getContext().getFuture();   // 拿上一次调用的 Future
    // ...
}
// ② 2.7+:getFuture() 返回 CompletableFuture,可组合
@DubboReference(async = true)
private UserService userService;

public void futureStyle() {
    userService.getById(1L);                                     // 立即返回 null
    CompletableFuture<Object> f = RpcContext.getContext().getFuture();
    f.thenAccept(this::handle).exceptionally(e -> {
        log.error("async call failed", e);
        return null;
    });
}
// ③ 推荐:接口直接返回 CompletableFuture
public interface UserService {
    CompletableFuture<User> getByIdAsync(Long id);
}

二、同步 vs 异步的线程账

同步调用(200 线程池,单次 50ms,目标 10k QPS)
  需要 10000 × 0.05 = 500 个线程 → 超池 → 排队 → 雪崩

异步调用(50 个线程发起,响应回调处理)
  发起即返回,线程可复用 → 线程数与并发解耦
维度同步异步
线程占用与在途请求数成正比与 CPU 核数相关
编程复杂度低高(回调地狱)
错误处理try-catch回调里判断,或 exceptionally
上下文传递ThreadLocal 直接可用需显式透传,线程已切换
超时语义阻塞到超时超时后 Future 异常完成

三、组合与超时

CompletableFuture<User> fu = userService.getByIdAsync(1L);
CompletableFuture<List<Order>> fo = orderService.listByUserAsync(1L);

// 并行聚合,整体 800ms 超时
CompletableFuture<UserDetail> detail =
    fu.thenCombine(fo, (u, os) -> new UserDetail(u, os))
      .orTimeout(800, TimeUnit.MILLISECONDS)
      .exceptionally(e -> UserDetail.empty());

orTimeout 只让 Future 失败,不会取消已在执行的服务端请求,写操作仍要靠业务幂等或状态机兜底。

四、什么时候不该用异步

  • 逻辑本身有强先后依赖,异步化只是把阻塞从线程搬到回调,收益很小。
  • 链路调用深度大且每层都异步,排障成本陡增,建议只在扇出(fan-out)场景用。
  • 事务边界内不要异步化,提交与回滚在另一个线程上,事务上下文会丢。

常见追问

  • 追问:异步调用后 ThreadLocal 里的租户 ID 还在吗?不在,回调可能在其他线程执行,要用 RpcContext 的 attachment 显式透传,或包装 executor 做上下文复制。
  • 追问:RpcContext 是线程安全的吗?不是,它绑定当前线程,异步回调里要重新设置。
  • 追问:异步会不会导致请求堆积?会。没有背压时发送速度超过响应速度,内存里会堆积大量未完成 Future,需要配 actives 限流。

小结

  • 异步把线程占用与并发量解耦,适合扇出、批量聚合、网关类场景。
  • 三种写法中优先用返回 CompletableFuture 的接口,显式且类型安全。
  • 超时只影响 Future,不取消服务端执行,写操作必须幂等。
  • 上下文与事务不会自动跨线程,异步化的隐性成本主要在这一点。
笔记加载中…