【背景】 做了 8 年高并发性能调优,我发现一个很常见的「捷径」:接口 P99 高了,第一反应就是「把串行调用改成异步并行」「上 CompletableFuture」「加个线程池」。好像「异步」两个字一出来,性能问题就该自动消失。
但我复盘过的不少案例里,异步化不是解药,甚至放大了故障。今天想认真聊聊这个取舍。
【几种常见做法 / 分歧】 派别 A(异步万能论):P99 高 = 有等待 = 把等待并行掉就快了。CompletableFuture / RxJava / 虚拟线程一把梭。 派别 B(同步优先):同步阻塞代码可读、可排障,线程本身就是天然的资源与背压单位;异步只在「等待占主导」时才值得。 中间派:看 IO 占比和调用图再决定。
【我的倾向与理由】 我默认同步阻塞,除非同时满足两个条件,否则不上异步:
- 等待时间占请求总时间是「量出来的」大头(trace 里能看见),不是拍脑袋觉得「有 IO 就该并行」;
- 被并行的下游彼此独立、可失败隔离(一个挂不拖垮整条链)。
理由有三:
- 异步省的是「线程等待时间」,不是「用户等待时间」。如果瓶颈在下游本身慢、CPU 打满、或连接池打满,并行化要么无效,要么把压力同时打到同一个下游,惊群放大抖动——连接池那个话题里「池子开大反而把抖动放大」就是同一类问题。
- 异步会丢「背压」。线程池队列一旦堆积,无界队列 = OOM,有界队列 + 拒绝 = 请求被砍,两者都不是健康信号,只是把故障换了个姿势。
- 错误传播与超时复杂度翻倍:哪个分支超时、要不要取消兄弟分支、partial failure 怎么聚合——这些在同步代码里是直线,在异步里是一棵树。
一句话:先用 trace 把「时间到底花在哪」量出来。我经验里 80% 的 P99 抖动,根因是连接池打满、GC、慢 SQL、锁竞争——这四个异步化全都不解决,甚至更糟。
【想听大家在什么场景下怎么选】
- 你们团队遇到 P99 抖动,第一反应是「先异步化」还是「先归因」?有没有「上了异步反而更慢」的真实案例,瓶颈到底卡在哪?
- 异步化的背压你们怎么落地:无界队列、有界队列 + 拒绝策略,还是信号量限流?
- 一次请求并行 N 个下游,超时预算怎么切、单分支超时怎么取消兄弟分支,有没有可复用的模式?
- JDK21 虚拟线程是不是让「同步代码 + 高并发」重新成为默认选项?异步框架还有没有存在的必要?
欢迎带具体场景和数据来聊,越具体越好。
0赞
0收藏
0楼层