那次,我花了整整一下午,把拖垮API的数据库查询压下去200毫秒。索引重建、语句重写,一切做得精密漂亮——当时我觉得,这是一场漂亮的胜仗。
错得离谱。
![]()
不到一周,网络延迟就成了新的短板。我们搞定了网络,客户端的渲染速度又顶了上来。再接着,内存占用开始暴增。每一次“修复”,都只是把问题往水流下游推了一步。
久而久之,我被迫面对一个现实:瓶颈根本不是可以消灭的敌人。它是系统运行的物理法则,你永远只能把它搬到别处。
每个系统都有一个最窄的截面
约束理论早就讲透了这件事。想象一条水管:无论别处有多宽,水流大小只由最细的那一段决定。软件系统完全一样。任何时刻,总有一环是那个勒紧喉咙的约束——可能是CPU、数据库、网络延迟、磁盘读写,也可能是内存。
你优化了当前约束,系统总吞吐量确实会提高,但约束并不会消失。它只是转移到了第二慢的组件上。然后第三慢,第四慢……你其实是在玩一场永不停歇的“打地鼠”。
阿姆达尔定律把话说明白了
哪怕给你一亿个处理器,你也逃不出数学的牢笼。阿姆达尔定律指出:如果一段程序有10%的代码必须顺序执行,那么无论你堆上多少并行算力,整体加速比永远不可能超过10倍。这10%的串行部分,就是永恒的瓶颈。
我们的社交应用也一样。有些事天生就得排队:先验明用户,再查权限,然后才能拉取信息流。为了安全,你无法把它们拆开并行。于是身份验证就成了那个微小但永远存在的瓶颈,任凭其他部分再快也没用。
一场大型取舍游戏
这里面藏着一个不体面的秘密:你为某件事优化,几乎必然要伤及另一件事。你不是在解决问题,你是在主动选择要承受哪一种约束。
拿一个经典的API扩展过程来说吧。假设最初它每秒处理10个请求,瓶颈是数据库。我们加上缓存,吞吐量立刻涨到每秒100次,但瓶颈转移到了内存——缓存太大了。我们分片缓存,吞吐量冲到500,瓶颈变成节点间的网络带宽。优化序列化,站上1000,客户端解析响应又慢了。启用了Gzip压缩,跑到了2000,上游服务居然开始喘不过气。每一步我们都在“解决”问题,但我们从没消灭过瓶颈。我们只是在编排一场迁移之舞,让瓶颈在数据库、内存、网络、客户端、上游之间流转。
分布式系统让问题更棘手
在分布式系统里,瓶颈不光会移动,还会藏在暗处,按照你预想不到的方式闪现。
网络往返时间就是一条物理定律。你没办法让数据比光跑得更快。对于全球部署的系统,每一“跳”天生就欠着至少50到150毫秒的最低时延。每多一次网络调用,这笔账就要再乘一次。这个问题你永远无法消除,只能小心地计算每一跳的代价。
领导者选举也很昂贵。当某个节点故障,集群就要花时间选出新领导者,这段时间里系统可能完全停摆。你以为自己在为高可用做设计,但其实只是把瓶颈从单点故障转移到了选举过程的耗时和不确定性上。每一次为消除某个瓶颈所做的设计,常常会制造出一个别处的、甚至更微妙的瓶颈。
瓶颈不是故障,是路标
想通了这一点,看待性能工作的眼光完全变了。瓶颈不是意外,它们是系统固有的呼吸节奏。每一次移动瓶颈,其实都是在重新回答同一个问题:此刻,你愿意让系统的哪一部分成为最慢的那一环?
比起幻想消灭瓶颈,更有用的是学会认识它们、预测它们下一次会在哪里出现,然后清醒地做出取舍。毕竟你能做的,从来都不是让水管没有最窄处,而是决定让哪个截面窄得更有意义。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.