86% 这个数被转了很多次。原文十月三号发在 DEV 上,标题里挂着 Linkerd sidecar 和 86% 的吞吐损失。
这次我不重跑。集群不在我手上,为一个已经有人付过账的实验再付一遍,不划算;况且延迟和吞吐人家都摆出来了,我再抄一遍没有任何增量。我要拆的是那个分母:51,791 req/s 这个无 mesh 基线,在原文自己同一套环境、同一份脚本下,八次跑测的区间是 25,000 到 52,000。
测试范围先划一下:只讨论 Linkerd sidecar 模式,单集群单版本,K8s 1.36.3,三节点各 4 vCPU / 8 GB。ambient 没测,原文也没测,下面任何一条结论别往 ambient 上搬。mTLS 和平凡转发各占多少成本,这篇里也没拆开——评论区有人问了,作者说没跑,我认这个“没跑”。
先把原文主结果摆出来,口径是 fortio 在集群内跑,一跳直连后端、两跳经前端反代:
| 路径 | 并发 | P50 无 mesh | P50 有 mesh | 吞吐 无 mesh | 吞吐 有 mesh |
|---|---|---|---|---|---|
| 一跳 | 1 | 0.537 ms | 0.693 ms | 2,737 | 1,356 |
| 一跳 | 32 | 0.585 ms | 3.808 ms | 51,791 | 7,018 |
| 两跳 | 32 | 1.486 ms | 7.160 ms | 17,481 | 3,949 |
看第一行。吞吐从 2,737 掉到 1,356,腰斩 50%;延迟从 0.537 ms 涨到 0.693 ms,加了 0.16 ms。同一组数字,写成“吞吐腰斩”是一个故事,写成“延迟加了 0.16 毫秒”是另一个故事。真要挑一个,我会说 0.16 ÷ 0.537 = 30%,这个比例才是你该拿去跟 SLO 比的东西——0.16 毫秒本身听起来像误差,30% 听起来像事故,它们是一回事。
后端 nginx 返回一个固定字符串。没有数据库,没有序列化,没有业务逻辑。这一点决定了后面所有百分比的方向:应用几乎不干活的时候,代理干的每一件事都占满了分子。
32 条连接,一个出站代理
最刺眼的是并发 32 一跳那行的 86%。评论区 Kalislav Smirnov 一句话就把它按住了:fortio 自己也在注入过的命名空间里,所谓“一跳”其实是负载生成器出站代理 + 后端入站代理,两跳;而且 32 条并发连接全挤在同一个出站代理上。
这不是挑刺,这是拓扑。作者认了,一周后补了三组重测——我认为那是整篇最有价值的部分:
负载生成器在 mesh 内、单实例 32 并发:6,009 req/s。
负载生成器在 mesh 外、路径上只留一个代理:13,000 req/s。
负载生成器在 mesh 内、拆成四个实例各 8 并发:10,927 req/s。
四个实例那档比单实例高了 82%,但没摸到 13,000。原因在 LINKERD2_PROXY_CORES 默认是 1——代理 metrics 里 tokio_rt_workers 就是 1。压测时负载生成器的 sidecar 跑到 810m,一个线程快被打满;后端那几个代理停在 450m 上下,离顶还远。
一个 worker 线程打满,加核没用。另一组看着别扭的数也归到这:早先在 2 vCPU / 4 GB 节点上,并发 32 的 mesh 内吞吐是 4,888;换到 4 核 8 GB 是 7,018。机器翻倍,吞吐只涨 44%。原文说“比例几乎没变”是对的,但它没往下说:比例没变不是因为 mesh 的固有开销稳定,是因为瓶颈压根不在核数上。
我干了自己批评的事
写到这我停了一下。
前面我差点拿“中位基线大概四万上下”去折算 6,009 的损失率,得出八成五——干的正是我准备批评原文的那件事:拿一个会飘的分母算百分比,再把这个百分比当结论。
退回来。基线在八次跑测里从 25,000 飘到 52,000,波动接近一倍,任何单次百分比都该带个区间。86% 那次用的 51,791 落在区间上沿;换成 25,000 做分母,7,018 对它是 72%。86 和 72,十四个百分点的差距全部来自“今天基线心情好不好”。这不是说原文算错了,是说 86% 这个写法给了一个它撑不住的精度。要写就该写成“七成到九成之间,取决于基线取哪一次”,丑,但诚实。
作者自己在 10 月 10 号的回复里把话说圆了:86% 里的大部分,既不是 sidecar 这套设计,也不是 mTLS,就是一个代理撞到了自己的上限。这个结论我信,因为它是被三组重测和一行 tokio_rt_workers 1 顶出来的,不是被推理出来的。
塌的是百分比,没塌的是 CPU
并发 32 持续压着时,后端 nginx 用了 91–125m,它的代理 340–435m,约 3.3 倍;前端 nginx 273–395m,代理 627–688m;fortio 218–264m,代理 559–596m。整体落在“sidecar 吃掉的 CPU 是被代理容器的两到三倍”这个区间。
内存那栏是另一个方向:每个代理约 12 MiB,空闲和打满一个数,平的。
所以容量规划里该留预算的是 CPU,不是内存。12 MiB 乘 Pod 数,一台 8 GB 机器上你甚至不用为它单独拉一行;但两到三倍的应用 CPU 乘你真实 Pod 数,是一笔要进报价单的钱。
这几行数字的口径得说清楚:采样点在 75 秒跑测的第 45 秒之后,连取三次。早先在第 12 秒取的 1m CPU 读数是假的——1 分钟滑动窗口在第 12 秒只装了两成真实负载,读出来是一个偏低的、看着很正常、但跟下午要写进报告那个数没关系的数。这条我记进表里,因为它不是 Linkerd 的问题,是所有用 kubectl top 在压测刚起来时瞄一眼就下结论的人的问题。我自己也栽过这种,不点名了。
顺便说,原文列了四个设置失误:未定义的 $BIGBODY 让 nginx 反复崩、fortio 的 distroless 镜像里没有 sleep、DOKS 默认不带 metrics-server、12 秒采样。愿意把这四条写出来的测试,方法论披露水平已经高于这个品类的平均线。这点我认得干脆。
那段外推,我不认
作者有一句判断:nginx 返回固定字符串是 mesh 的最坏情况,因为代理的活儿占了绝对大头。这话没问题。
紧接着他把 +3 ms 摊到一次 50 ms 的数据库查询上,说大约 6% 的延迟增幅,大概率没人会注意到。
这一步我不认。它和“86% 是 mesh 属性”是同一个毛病,只是这次换了个乐观方向:拿一个特定场景里测出来的数,去代表另一个完全没测的场景。50 ms 的查询里没有连接池排队,没有序列化,没有 p99,也没有回答一个更要紧的问题——并发 32 的时候那 3.8 ms 会不会变成 30 ms。原文自己测出来的就是并发 1 加 0.16 ms、并发 32 加 3.2 ms,同一个 mesh、同一条路径,差了 20 倍。既然并发能把 0.16 抬到 3.2,凭什么在“6%”里把并发当成常数。
不是说他错得离谱,是说这一段没有数字支撑,只有换算。换算不是测量。
置信标签
高置信:sidecar 的 CPU 开销是被代理容器的 2–3 倍这个量级,以及每代理 12 MiB 内存不随负载变化。六组采样、两个角色、一个负载生成器,方向一致,离散度也交代了——尽管只有一轮三节点,跨集群外推之前你自己再验一遍更稳。
方向大致对:单代理存在吞吐上限,那个上限由 LINKERD2_PROXY_CORES 和单 worker 线程决定。三组重测(6,009 / 10,927 / 13,000)都指向这个方向,机制层面也有一行 metrics 顶着。但“上限具体是多少”我不敢给数,那取决于你的连接数和每连接的在途请求数,我这儿只有一个拓扑。
证据不够,先别信:任何把 86% 当成 Linkerd 固有属性的引用。这个百分比的分母在八次跑测里飘了一倍,作者自己在评论区把它拆过一次,ambient 对照没跑,mTLS 和平凡转发各占多少至今是空白。谁要在选型文档里引这个数,请把这三条一起引上。
整个实验两套集群四次跑测,据作者说烧了四十美分集群时间,manifest 和 fortio 原始输出都在仓库里。四十美分买一个能被别人重跑的口径,这个价钱我觉得比买一张发布会的对比图值。
想拿自己的服务重测的,两件事就够了:在接近生产的并发下测,别只测单连接延迟;测吞吐,别只测 P50。数字对不上告诉我,我表里 Istio 那一行还空着。
