为什么你的服务性能调优后反而更慢了?聊聊我踩过的几种反直觉坑

🔑 关键词:性能调优,JVM,吞吐量,反直觉,Full GC

📖 摘要:从一次真实的Full GC事故说起,拿线程池、连接池、JVM参数和锁优化的例子,聊一聊那些看似标准却越调越慢的操作。

为什么你的服务性能调优后反而更慢了?聊聊我踩过的几种反直觉坑

图片

上个月帮一个团队看老出问题的会员服务,他们很有成就感的说“我们调过优了,-Xms=-Xmx=8g,线程池也调到了200”。 但我打开监控一看,CPU使用率只有15%,GC线程却频繁告警,平均每6秒一次Full GC。 他们的“调优”就是把堆内存从2G加到8G,以为加内存就完事。 结果Full GC停顿直接从原来的20ms变成了280ms,接口P99从66ms涨到了190ms。 这不是我编的,就是上个月的真实事故。很多人做性能调优就是凭感觉堆参数,完全没意识到改动是有连锁反应的。

图片

这里先说一个大家不爱听的对比:调优不是改一个点,而是改一条链。 同样一个接口慢,你可以说是代码慢,也可能是网络抓包重传,可能是下游Redis慢查询,甚至是宿主机上隔壁租户抢CPU。 我见过最典型的反面例子是开发同学发现MySQL慢,就拼命加连接池,从10加到80,业务确实快了20%,然后数据库主库CPU到97%触发告警,还是我把连接池调回32,CPU降到60%,QPS反而又升了10%。 这说明池子不是越大越好,真正限制吞吐量的瓶颈往往是临界资源——锁、IO和物理核数,而不是线程数本身。 不要一上来就套“线程池200、队列5000”这种模板,每个系统的上下文切换成本完全不同,先看你的机器核数和任务类型再说。 我自己的体验是,那些所谓的标准优化参数,在Java 8下和Java 17下效果能差出一个数量级。

图片

再说说全局视角和局部指标的“对比度”。 今天很多文章教你看CPU、内存、磁盘IO、网络,可是很少人把“用户感受”当成一个指标加进去。 有个晚上线上某接口RT从6ms变到40ms,从技术面板上看CPU、内存一切正常,人人都说不是我们服务的问题。 后来发现是压测机上的wrk用的网卡旧,重传率高,导致你测出来的P99是假的。 别笑,我那次浪费了两天。 所以后来我给自己定了一条规矩:所有优化前后,必须用同一个压力机、同一份流量和同样的JVM预热时间来做对比,至少跑30分钟才配说提升了多少。 而且要看长尾,不能只看平均——平均响应时间降了30%但P99如果涨了5倍,那这个优化就是失败的。

图片

最后说点“反直觉”的实际经验。 我目前维护的一个支付网关,核心里有一段加锁的名单校验,一开始用ConcurrentHashMap加锁,压测到3000 TPS就不行了。 后来我把所有名单按userId分段,搞了16个segment锁,TPS直接到8000,CPU才多6%。 这不是重点,重点是后来我把锁去掉改成读时Copy的不可变Map,TPS到了12k,CPU反而降了。 很多时候瓶颈不在锁竞争本身,而在于JMM内存屏障下的缓存失效,想明白这个你才敢下手。 JVM调优也别总盯GC,先试试调整Young区与Survivor比例是否匹配对象晋升速率,而不是堆大小。我们用G1,把-XX:MaxGCPauseMillis从100改成50,结果GC频率从每小时20次飙到200次,吞吐量掉了8%——以为目标停顿时间越短越好的小白思维,正是曾经的我。 唯一能确定的是:性能调优必须通过A/B对比加可观测数据来验证,每次只改一个参数,记录业务成功率、P99、GC时间、CPU核秒成本四条曲线,否则你优化了个寂寞。

图片

🏷️ 标签: