诚实解读 Aeron® 基准测试
Aeron® 基准测试本身是个好工具,问题几乎总出在我们解读它的方式上。最常见的错误:把某个选定速率下的延迟当成容量来引用。这是两种完全不同的说法,团队往往就在这个落差里骗了自己。
这一页要纠正三种具体的误读:
- 把”基准峰值”当成”消息上限”。 基准从不主动去找上限。你给它速率,它给你延迟。真正的上限——那个拐点(延迟越过 SLA 的地方)——基准压根没测。
- 把
burst=10的数字当生产结果引用。burst旋钮把流量预先攒成一个生产里根本不存在的最佳情形——没人会在实时行情源上装个”按 10 条打包”的开关。漂亮数字有一部分就是这个旋钮造出来的。 - 跟着复读”DPDK 比 Java driver 快约 60× / 200×”。 这个比值拿的是健康的 DPDK 点去比已经饱和的 Java 点。让两者都跑在拐点之下,差距就塌缩成一个小倍数——AWS 自己 2025 年的基准也证明了这一点(见 DPDK 神话)。
这些不意味着基准坏了,也不是说跑过的测试白费了。工具本身没问题,问题在于从它身上推出了过头的结论。
1. 基准到底是什么
Section titled “1. 基准到底是什么”cluster 基准(remote-cluster-benchmarks)和 MDC 基准(remote-echo-mdc-benchmarks)都是闭环延迟测量台。压测生成器会:
- 以你指定的固定目标速率(
--rate)向 cluster(或 MDC publication)发消息。 - 在 offer 时用
clock.nanoTime()给每条消息打时间戳。 - 把往返延迟(ingress → 共识/commit → service → egress)测进一个 HdrHistogram。
- 报告该速率、该消息大小下的 P50 / P99 / P99.9 / Max。
它是块秒表,不是台容量计。它回答的是*“在速率 R、大小 S、MTU M、到达形态 B 下,延迟分布长什么样?“——一个精确、可复现的问题。它不直接回答”这台机器最多能扛多少?“*
| 它测的 | 它不测的 |
|---|---|
| 给定速率/大小下的延迟 | 你自己流量的真实饱和上限 |
| 一个人为的到达过程(限速 + 打包) | 真实的突发 / 可合并的生产到达 |
| transport + 共识的热路径 | 你应用的业务逻辑、GC、序列化 |
| 一次只测一种消息大小 | 混合大小的真实流量 |
| 固定窗口内的稳态 | 冷启动、failover、开盘尖峰 |
正确的心智模型
Section titled “正确的心智模型”把每一行基准结果当成一句带全部限定语的话来读,不能只看数字:
“如果我把这套确切配置按速率 R、消息大小 S、MTU M、到达形态 burst=B 跑一个稳态窗口,那么往返延迟分布是 P50/P99/P99.9 = …,X% 的消息到达。”
“那么”之前的一切都是你选的输入。数字只是这些输入喂出来的结果,不是这台机器的固有属性。
基准擅长的用途:
- 给芯片 / NIC / MTU / driver 做相对排名——只要每个输入都固定(同样的 R/S/B/MTU 下比 c8a、c8i、c8g)。这是它最强、最可信的用法。
- 找拐点——前提是你拿真实的消息大小和到达形态去扫速率:P99.9 越过 SLA 的速率,就是那种流量下的可用上限。
- 回归检测——某次代码/配置改动有没有在固定工作点上挪动延迟?
- 尾部刻画——在指定负载下,看 NAK/重传/排队在哪里冒出来。
- 可复现、可独立验证的同类对比(这正是测量台要固定速率的原因——见下文 AWS 的方法论)。
基准不是:
- 不是容量/吞吐上限。 “1M,✅” ≠ “上限是 1M”。上限是拐点,而这一行没找到它。
- 不是生产延迟预测——除非你的到达形态、大小、MTU 都和这次运行相符。一个
burst=10、32B 的头条数字,在均匀节奏、470B 的生产源上不会复现。 - 不是 driver 的”速度倍数”。 DPDK 和 Java 的差距不是一个恒定倍数,而是拐点位置和尾部的差异,只有一方已经饱和时才显得巨大。
- 不是整机数字。 它不含你的业务逻辑、序列化、GC、磁盘、failover——而这些全都会落进用户真正感受到的端到端延迟里。
- 不是突发下的保证。 稳态 P50 对开盘尖峰只字未提;那要看突发形态下的 Max/P99.9。
一句话准则: 流量形态是你自己发明的,基准告诉你的是这种形态下的延迟;而流量形态本身,是生产告诉你的。绝不要拿前者当后者的容量来引用。
2. 神奇的 burst 旋钮
Section titled “2. 神奇的 burst 旋钮”BURST_SIZE → 压测生成器里的 -Dio.aeron.benchmarks.batch.size。它不是 Aeron 的代码路径——不管它取几,Aeron 的 offer() 和 driver 的 TermScanner 都逐字节一模一样。这个旋钮只改offer 的节奏:
batch.size = N → 连续 offer N 条消息,然后等下一个速率 tick。batch.size=1(均匀): offer → 等 → offer → 等。media-driver 的 Sender 在下一条到来前就把每一帧排空了 → 每个 datagram 一条消息 → 接收端付满每 datagram(每秒包数)的代价 → 它很早就饱和。batch.size=10(突发): 十次 offer 之间没有间隔 → 这十帧共处于同一个 term buffer →TermScanner.scanForAvailability()把它们合并进一个 ≤ MTU 的 datagram → 接收端只做约 1/10 的每-datagram 工作 → 同样的速率轻松通过。
为什么这重要
Section titled “为什么这重要”合并这个机制在生产里 100% 真实。但硬凑一批固定 10 条的那个旋钮,只存在于基准里。所以 burst=10 的数字是一次最佳情形、预先合并的测量。生产源本是均匀到达,你却拿 burst=10 的”峰值”当容量,就会错出一个大倍数。
一个旋钮把 5 毫秒的灾难变成 56 微秒
Section titled “一个旋钮把 5 毫秒的灾难变成 56 微秒”这不是假想。我们特意做过一次严谨的 A/B 来了结这个问题——同一套 4 节点测试床(c8i.8xlarge、单 AZ placement group、隔离并 pin 核、关 C-states),同样的 1M msg/s × 32B cluster 负载,每组 MTU 都是 8192,而且交错着跑(每轮把所有配置各跑一遍,重复 3 次),这样 session 漂移就没法冒充成结果。唯一变动的只有 BURST_SIZE 这个旋钮。
Java media driver,burst=1(均匀节奏)——一台饱和的机器:
| 指标 | 结果 |
|---|---|
| P50 | 4,698 – 7,737 µs |
| P99 | 13,214 – 15,385 µs |
| P99.9 | 13,623 – 16,974 µs |
| Max | 高达 20,677 µs |
| 状态 | 饱和——撞到了包速率的墙 |
中位数五到八毫秒,P99 尾部约 14 ms。作为消息传输这是不及格的——单个接收线程每秒派发不了 1M 个独立 datagram,于是进料队列一路堆到失控。
同一台机器、同样的代码、同样的硬件——把 BURST_SIZE=1 → 10:
| 指标 | 结果 |
|---|---|
| P50 | 56.4 – 125.5 µs |
| P99 | 72.8 – 188.2 µs |
| P99.9 | 90.0 – 197.5 µs |
| Max | 267 – 505 µs |
| 状态 | 脱离饱和——稳稳扛住整个 1M/s |
中位数从 ~5–8 ms 塌到 ~56 µs——改善约 60–130×,最好的一轮进到亚 100 µs,P99.9 尾部从约 14 ms 降到 ~90–197 µs。没换更快的 NIC,没上 DPDK,没加核,没改一行代码。 变的只是一个决定客户端什么时候调用 offer() 的旋钮。魔法全在这里:burst=10 时大约十条 32 B 帧合并进一个 datagram,于是 leader 的接收端只做约 1/10 的每-datagram 工作,翻过了原本快淹死它的那堵墙。
2b. MTU:什么时候有用,什么时候毫无作用
Section titled “2b. MTU:什么时候有用,什么时候毫无作用”MTU(AERON_DPDK_MTU,或 channel 的 mtu/term 配置)是合并用来装东西的那个盒子的大小。巨型 MTU(8192 / 9001)只有在有东西可装时才有用。这是最容易被误解的旋钮,因为MTU 和 burst 是相乘关系——MTU 定合并的天花板,burst 决定你能不能够到它。
TermScanner.scanForAvailability() 会把尽可能多的、能塞进 min(senderWindow, MTU) 的驻留帧打进一个 datagram。所以合并比受两个独立上限约束:
- MTU → 能塞下几帧(一个天花板)。
- Burst / 到达形态 → sender 扫描时实际驻留了几帧(供给量)。
你拿到的是 min(装得下的, 在场的)。盒子再大,每次只塞一件东西也白搭。
情形 A——burst=1(均匀,只有一帧驻留):MTU 几乎不起作用。 均匀 offer 时,Sender 在下一帧到来前就把当前帧排空了,所以每次扫描只有约 1 帧驻留。这一帧不管 MTU 多大都照样进 datagram——8192 字节的 MTU 只装一帧 32B,线上就是 32 字节,跟 1408 字节的 MTU 没两样。均匀情形下加大 MTU 并不会降低你的 datagram(PPS)速率,因为 datagram 数量是由消息条数决定的,不是 MTU。这就是为什么面对均匀源的饱和,“直接开巨型帧”往往一点用都没有。(MTU 甚至会稍微拖累均匀情形的延迟尾部:buffer 更大,缓冲就多那么一点。)
情形 B——burst=10 / 连续(多帧驻留):MTU 是真杠杆。 现在多帧共处,datagram 会装到 MTU 上限。MTU 直接决定合并比,且随 消息大小 vs MTU 缩放:
| 消息大小 | 每个 1408B MTU 装几帧 | 每个 8192B MTU 装几帧 | 每个 9001B MTU 装几帧 |
|---|---|---|---|
| 32B | ~40 | ~230 | ~250 |
| 288B | ~4–5 | ~26 | ~29 |
| 1344B | 1(勉强塞下) | ~6 | ~6 |
(近似值——要减去 Aeron/UDP/IP 的封装开销;帧按 32 字节对齐。)
从这张表能读出两点:
- 32B、突发下,MTU 影响巨大: 40:1 对 250:1 的 datagram 缩减。巨型帧把天花板抬高一大截。
- 1344B 下,1408B 的 MTU 根本合并不了(一帧勉强塞满),所以在你同时抬高 MTU 之前,
burst什么都给不了你。这正是为什么 1344B 那些行在 MTU 1408 下即便上了 DPDK 也会在 1M “饱和”,却在 8192 或更低速率下通过——那里缺的杠杆是 MTU,不是 burst。
陷阱: 一个漂亮的头条数字通常把两个旋钮都拧到了最佳:小消息 + 大 MTU + 突发。也就是 32B / 8192 / burst=10 → 约 250:1 合并 → 每-datagram 天花板一下子好躲了 250 倍。生产里消息更大、MTU 是默认值、或者均匀到达,这三样任占一样都吃不到这个乘数,会早得多就饱和。
经验法则:
- 均匀(burst=1)流量: MTU 对你的 datagram/PPS 上限几乎无关——别指望巨型帧救你。要么修到达形态(micro-batch),要么抬高天花板(DPDK)。
- 突发/连续流量: MTU 是一等杠杆,消息相对 MTU 越小,它的收益越大。把基准的 MTU 和消息大小都对齐生产,否则合并比(连带峰值)迁移不过去。
2c. 如何正确地把”burst”公式化
Section titled “2c. 如何正确地把”burst”公式化”到目前为止,“burst”指的都是基准的 batch.size 旋钮。但当你给一个真实系统做容量规划时,burst=10 这个单位就用错了。基准旋钮问的是*“我这一个客户端连续 offer 几条消息。”* 生产要问的是管道另一头:“一个排空间隔内能有多少消息落到接收端,我的 buffer 扛不扛得住这一坨、会不会丢包?” 这是两个不同的量,而只有第二个能预测你在现场真正看到的几十毫秒尾部。
burst 是同时到达的条数,不是速率
Section titled “burst 是同时到达的条数,不是速率”现实里最大的突发来源是N 个独立生产者在同一瞬间开火——而不是某一个客户端的节奏。我们做过根因分析的一起生产事故就是教科书级的例子:
- 8 条 replay 流,各自都是稳定的长生命周期 session,偏偏帧对齐了,同时爆发。
- 基线 45.9 pkts/ms;叠加起来的尖峰冲到 658 pkts/ms——17.2× 基线。
- 单个 Aeron 接收线程只能串行排空,于是在一个 211 ms 的窗口里 57,165 个包堆在了 OS socket buffer 里 → 最坏延迟 57.7 ms,45,259 次慢事件。
网络本身没问题(transit 19 µs,p99 28.5 µs),没有哪个环节”太慢”。就是 N 个源撞到了一起,这一坨超过了一个排空周期能清掉的量,于是排起了队。这才是生产里”burst”的含义:一次叠加,用**一个服务间隔内到达了多少包(或多少字节)**来度量。这一节是微突发基准测试展开的接收端视角。
把 burst 建模成:当到达短暂超过排空时,接收端必须扛住的那个队列:
峰值到达速率 λ_peak = Σ (每个源的峰值速率) [msgs/s] ← 叠加所有源排空速率 μ = 接收端能派发的 msgs/s [msgs/s] ← 拐点突发时长 T_b = 这一坨持续多久 [s] ← 来自你的 trace / 对齐
要吸收的积压 Q_burst = max(0, (λ_peak − μ) × T_b) [msgs] ← 真正要紧的数字buffer 必须覆盖 B_bytes ≥ Q_burst × 每条消息平均线上字节数 [bytes]最坏排队延迟 L_q ≈ Q_burst / μ [s] ← 例如 57k / (1M/s) ≈ 57 msλ_peak是个总和,不是单条流。 有N个同步的源、各自峰值r,那么λ_peak ≈ N·r。那次事故的尖峰就是 8 个源叠加达到 17× 基线。永远做叠加;只看单源速率会骗了你。Q_burst才是要算出来的东西。 它是λ_peak > μ这段时间里堆起来的消息数。你的 socket buffer / Aeron 接收窗口 / term buffer 只要装不下Q_burst,就会丢包 → NAK → 重传 → 冒出那条多毫秒的尾巴。这直接连到 buffer 规划:按Q_burst来定 buffer,而不是按平均速率。 见接收路径 buffer 和 buffer 规划计算器。L_q才是你真正会测到的尾部。57,165 msgs ÷ ~1M msgs/s ≈ 57 ms——公式把观测到的事故复现了出来。这才是要摆到产品团队面前的数字,不是基准中位数。
两笔各自独立的预算——别混为一谈
Section titled “两笔各自独立的预算——别混为一谈”有两种”突发容量”,由不同的东西决定:
- 传输/流控的突发预算(发送端)。在 NIC egress 限速器(AWS token-bucket)上,基线之上有一笔固定的比特突发额度(比如一个深 1 ms的桶,在 5 Gbps 实例上就是 5,000,000 比特),而恢复时间 = 突发额度 ÷ 每毫秒的富余比特。这笔预算按比特算,跟 MTU 无关;MTU 只决定这笔固定比特预算里能塞几条消息(32 B 的 Aeron 帧头是每条各算一份、摊不掉的,所以又小又单帧的消息几乎占不到便宜)。它在发送端堆起的队列,见发送端排队与突发限速。
- 接收吸收预算(接收端)。就是上面的
Q_burst——受 socket buffer + 接收窗口 + 排空速率μ约束。上面那起事故里崩掉的正是这个。
你实际的突发耐受度取两者的最小值。 就算 5 Gbps 的 egress 额度再慷慨,只要接收端的单线程排空 + socket buffer 吸不下由此产生的那一坨,也是白搭——事故里发生的就是这一幕。
所以”burst 的正确公式”是
Section titled “所以”burst 的正确公式”是”不是
batch.size。是Q_burst = (λ_peak − μ)·T_b,其中λ_peak叠加了所有同时开火的生产者,μ是接收端实测的排空速率(拐点),T_b是 trace 里那一坨的时长。把 socket buffer / 接收窗口 / term buffer 定到 ≥Q_burst × 每条线上字节数,让λ_peak保持在 发送端比特预算 和 接收吸收预算 两者的最小值之下,并拿L_q ≈ Q_burst/μ当作你的最坏尾部去引用。缩小Q_burst有两个杠杆:把μ抬上去(把 subscription 拆到 K≥2 个接收线程/driver、上 DPDK),或者把λ_peak/T_b压下来(给生产者去同步、给它们的发送错峰加抖动,别让 N 个源帧对齐)。
3. 限速器 vs 开环——以及生产究竟是什么
Section titled “3. 限速器 vs 开环——以及生产究竟是什么”基准的速率限速器(那个”等下一个 tick”)是为了让各次运行可比才存在的。它在生产里不存在——你没法叫交易所按 300K/s 发行情。
生产是开环的:数据来了你就 offer()。约束不来自时钟,而来自背压(publication 窗口满时 offer() 返回 < 0 / BACK_PRESSURED)。真正的生产设计决策是你的溢出策略,而这一点基准从来不去触碰:
| 策略 | 何时适用 | 代价 |
|---|---|---|
| 阻塞 / 自旋重试 | 要求无损(下单、有序命令) | 背压必须往上游传;否则延迟尾部无界 |
| 合并 / 丢到最新 | 行情数据 / 状态快照(只有最新的要紧) | 丢掉中间值(对行情源通常没关系) |
| 有界缓冲 + 策略 | 吸收微突发,满了才施加策略 | 务实的默认选择 |
“去掉 sleep” ≠ “burst=10”。 去掉 sleep 是撤掉速率控制、改跑开环;这时合并变成了饱和自然带出来的副作用(受 MTU 上限约束,往往不止 10:1),而任何排队延迟都会落进你测到的延迟里。那是一次吞吐实验,不是受控速率下的延迟实验。
4. 贴近生产的基准长什么样
Section titled “4. 贴近生产的基准长什么样”要让基准能预测你的部署,别再优化头条,开始贴合现实:
- 对齐到达过程,而不只是速率。
- 最好:回放一段抓下来的生产 trace(真实的到达间隔与突发)。
- 否则:给突发性建模。别因为
burst=10好看就默认用它。
- 把
burst=1(均匀)作为保守下限报出来,把burst=10标注成”要流量确实突发,或者你加了应用层 micro-batch,才达得到”。两个都报,各自标清楚。 - 消息大小和 MTU 都对齐生产。 合并比随 size:MTU 缩放(32B 进 1408B ≈ 40:1;470B ≈ 3:1;1344B 可能压根合并不了)。
- 测尾部,别只看中位数。 突发形态下的 P99.9 / Max 才是 NAK/重传和队列堆积冒头的地方——那才是你真正的风险,不是 P50。
- 如果生产是均匀节奏、你又想要更高的数字,那就得靠设计: 在客户端加一个有界 micro-batch(凑够 N 条或攒够 T µs 就 offer,谁先到算谁——两头都要封顶,这样一段冷清期也不会把某条消息卡住)。然后去测那个客户端,而不是那个人造旋钮。
- 把和 SLA 相关的冷路径也纳进来: 启动、leader failover/RESIGN、快照、开盘尖峰。
- 在你的目标 ρ、拐点之下去测。 定容量时让正常的微突发永远碰不到背压;一旦碰到,就把它当过载信号看,而不是稳态。
准备你自己的负载——即便没有历史数据
Section titled “准备你自己的负载——即便没有历史数据”你能做的最有价值的一件事,就是别再喂给基准一个人造形态,改喂你自己的形态。
如果你有生产流量——搭一个回放:
- 把到达时间戳抓下来,不只是条数。要紧的是到达间隔分布和突发结构。来源有:应用层 publish 时间戳(最好,和
offer()同一层)、ingress 流的pcap/tcpdump、或带每条接收时间的 feed-handler 日志。 - 消息大小分布也一并抓(小心跳 vs 大快照)。一张大小直方图比单个平均值管用得多。
- 忠实回放——直接拿抓下来的
(timestamp, size)trace 去驱动,别把它压成固定的--rate+burst。要是你的测量台没法回放 trace,至少把 trace 拟合成一个速率加一个真实的突发系数,跑那个,并注明是近似。 - 放大也要诚实。 预期 3× 增长,就按 3× 回放(把到达间隔压缩),但突发结构要保住——别只抬一个平坦速率,那会把真正搞垮你的突发给抹平了。
- 回放最坏的那个窗口,不是平均的一天。 回放你开盘 / failover / 重试风暴的那一分钟。
如果你是个没有数据可回放的新项目,那就把真相框在一个区间里,别去猜某一个点:
- 两端永远都要跑:
burst=1(均匀节奏)= 你的悲观下限(这里达标,那个速率下对任何到达形态你都安全);burst=high/ 开环 = 你的乐观上限。真实生产落在两者之间。按下限定容量,别按上限。 - 找拐点,别信头条。 在
burst=1和你最靠谱的那个消息大小下扫--rate,记下 P99.9 越过 SLA 的位置。那个拐点才是你站得住脚的容量数字。 - 从第一性原理去估到达: 拿你的业务峰值(开盘时的每秒订单数、最活跃品种的每秒 tick 数),乘一个安全系数(金融峰值常用 2–3×),并且在没被证伪之前一律当它是突发的——真实的事件流会成团,不像 Poisson 那么平滑。消息大小从你的 schema 来(编一条有代表性的 SBE/proto 消息、量出它的线上大小),别靠猜。
- 先留出余量,再测进去。 在你估的峰值下把目标定到 ρ < ~0.7,上线,然后从第一天起就给生产装上探针(publish 时间戳、
offer()背压计数、driver 周期时间阈值),让你猜出来的模型在几周内就被真实 trace 换掉。 - 溢出策略要在上线前就定,别拖到第一起事故——无损阻塞 vs 合并丢弃 vs 有界缓冲。没有数据时,速率估错了会怎样比速率估得准不准更要紧。
心态上的转变: 没有历史数据时,你的任务不是憋出一个惊艳的数字——而是给出一个你站得住脚的安全下限(burst=1 拐点),外加一份尽快用真实数据替换掉假设的计划。
5. 用基准结果选 EC2 规格
Section titled “5. 用基准结果选 EC2 规格”基准做相对比较——芯片、NIC、MTU、driver——是真有用的,前提是你固定住到达形态,并且把它当相对量、而不是绝对量来读。
要做:
- 在同样的速率/大小/burst/MTU 下比机器,给芯片排序(比如 c8a、c8i、c8g)——基准在排序上靠得住。
- 给每个规格找拐点: 用你的生产消息大小和
burst=1(或你的 trace)去扫--rate,记下 P99.9 越过 SLA 的位置。那个拐点——不是头条 P50——才是这个规格在那种流量下的可用上限。 - 按余量定容量: 挑那个拐点稳稳高于你峰值到达速率的规格(目标 ρ < ~0.7),让突发有地方可去。
- 按你的 SLA 给尾部加权。 一台 P50 更低、突发下 P99.9 却更肥的机器,对延迟敏感路径反而可能更差。
- 算整台机器的账。 Cluster 得给 conductor/sender/receiver/archive/共识/service 分核;一个”更快”却逼出跨 NUMA 或超线程争用的规格,可能反倒输。
不要:
- 不要拿一个
burst=10、32B、1M 的头条去定容量——除非你的生产真就那么突发、那么小。那是最佳情形。 - 不要把”饱和”当成”这机器太慢”。 它通常只是说这个速率在这个大小/MTU/burst 下撞穿了每-datagram 或每-frame 的天花板——同一台机器换个 MTU、burst 或速率就可能过(比如 1344B 在 1M 饱和、在 100–300K 就过了)。
- 不要把一种大小外推到另一种。 合并比和每-frame 成本都跟大小有关。
核心告诫:基准结果 ≠ 消息上限。 一行写着”1M msg/s,P50 42 µs,✅ 100%“,意思是*“在我们要求的速率(1M)、这个大小/MTU/burst 下,延迟是 42 µs、没丢包。”* 它不等于”1M 就是上限”。上限在你的流量形态对应的那个拐点上——可能远低于 1M(均匀、大消息),也可能高于它(突发、小消息)。永远为你自己的工作负载找拐点;绝不拿头条当容量来引用。
5b. 对产品团队设定预期
Section titled “5b. 对产品团队设定预期”出事通常就出在这一步。工程师一句”我们测到 1M/s 下亚 100 µs”,几次会开下来,就变成了对客户的 SLA——“每秒一百万条消息、亚 100 µs”,然后生产差了 50×,酿成信誉危机。基准数字是你选定条件下的实验室读数;产品承诺则必须是你在最糟的生产日也守得住的话。两者几乎从来不是同一个数字。
要翻译,不要照抄
Section titled “要翻译,不要照抄”绝不把一个原始数字直接递给产品团队。先把它翻成一句带齐所有条件的话,再单独给出可承诺的那一版:
| ❌ 不该说 | ✅ 应该说 |
|---|---|
| ”Aeron 能做到 1M msg/s、亚 100 µs。" | "在实验室最佳情形下(小消息、预先打包、不含业务逻辑),我们看到 1M/s 时约 56 µs。对着你的流量形态,我们能承诺的目标是 P99.9 < X µs @ Y msg/s,并留出到 Z 的余量。" |
| "P50 是 42 µs。" | "中位数在拐点之下的负载时约 42 µs;但对你的 SLA 要紧的是 P99.9 尾部(A µs),以及突发下的最坏 Max(B)。" |
| "它没饱和。" | "它能维持 Y msg/s、留着 ρ ≈ 0.6 的余量;就你的消息大小而言,一过约 K msg/s 它就饱和,延迟进入毫秒级。“ |
在尾部、在真实工作点、带余量地承诺
Section titled “在尾部、在真实工作点、带余量地承诺”- 要报 P99.9(和 Max),绝不能只报 P50。 产品和客户感受到的是尾部——42 µs 的中位数配上 14 ms 的 P99.9,那是个 14 ms 的产品,不是 42 µs 的。
- 在你规划的工作点(ρ < ~0.7)上承诺,别在拐点上承诺。 拐点是它崩的地方;你要承诺的是拐点之下那个从容的速率。
- 承诺
burst=1/ 你自己 trace 的数字,别承诺burst=10头条(除非配得上它的 micro-batch 你已经做出来并上线了——那就去承诺那套系统实测的数字)。 - 余量要写明。 内部目标得比对外 SLA 留出一个安全系数,因为生产里有 GC、业务逻辑、failover 和吵闹的邻居,基准里都没有。
让条件成为承诺的一部分
Section titled “让条件成为承诺的一部分”每一条性能承诺都得带齐它的五个限定语,不然那就不是承诺,是传闻:速率、消息大小、MTU、到达形态(burst/trace)、分位数。“P99.9 < 200 µs @ 300K msg/s、288 B、MTU 8192、按生产 trace 到达”是一句你能测、也守得住的话;“亚 100 µs”不是。
明确把三个数字分开
Section titled “明确把三个数字分开”- 实验室最佳情形(头条):“传输在理想条件下能跑到什么程度”——用于竞争定位,绝不拿来当 SLA。
- 可承诺目标(你的
burst=1/trace 拐点,再减去余量):“我们为你的工作负载承诺什么。“这是 SLA 的输入。 - 崩溃点(拐点 / 饱和速率):“它从哪里开始劣化”——好给容量规划和告警划一条真正的红线。
要主动挑明的失效模式
Section titled “要主动挑明的失效模式”- 饱和悬崖。 延迟不是线性的——它先一路平着,一过拐点就跌下悬崖(56 µs → 5 ms)。告诉他们这里有一道硬边、它在哪、以及你会把工作点定在它下面。
- DPDK”快 60ד陷阱。 有人抛出一个巨大的加速比时,说清楚那多半是饱和假象,拐点之下诚实的差距是 ~1.3–1.5×——所以 DPDK 买来的是上限和尾部的稳定性,不是一个能拿去对客户承诺的通用延迟倍数。
给产品团队的一句话: “这是它在实验室能做到的(最佳情形),这是我们对你真实流量会承诺的(带余量、看尾部),这是它崩的地方。请引用中间那个——连同它的条件——千万别引用第一个。“
6. “DPDK 比 Java driver 快 8× / 60× / 200ד的神话
Section titled “6. “DPDK 比 Java driver 快 8× / 60× / 200ד的神话”内核旁路的 DPDK driver 确实比 Java/C media driver 明显更快、抖动更低。但那些吓人的比值是牛头不对马嘴的假象——拿一个健康的 DPDK 点去比一个已经饱和的 Java 点。这话你不用光听我们的,因为 AWS 自己 2025 年的 Aeron 基准恰好把这一点摆了出来。
来自 AWS Industries 的文章”Aeron on AWS: 2025 Performance Benchmark Results”(全部在 c6in.16xlarge、Cluster Placement Group、MTU 1408 上):
Aeron Cluster @ 100k msg/s(P99,µs):
| Driver | P99 |
|---|---|
| OSS – Java | 136 |
| OSS – C | 139 |
| Premium(内核旁路 / DPDK) | 98 |
→ 在 100k,Premium 的 P99 好约 1.4×。不是 8×,不是 59×,就 1.4×。
Aeron Cluster @ 1 百万 msg/s(P50 / P99 / P99.9,µs):
| Driver | P50 | P99 | P99.9 |
|---|---|---|---|
| OSS – Java | 3,301 | 8,479 | 9,306 |
| OSS – C | 4,948 | 8,577 | 8,987 |
| Premium(内核旁路 / DPDK) | 106 | 143 | 158 |
→ 在 1M,OSS 已经跌下拐点(P99 8,479µs = 8.5 ms),Premium 还平着(143µs)。AWS 自己把这说成*“59× 的性能提升”*。
把这两张表并排一读,整个故事就摆在那儿: 同样两个 driver,在 100k 差 1.4×、在 1M 差 59×。driver 没在这两行之间变快 40×。是 OSS driver 饱和了,DPDK driver 没有。 “59ד不是速度倍数——它是一个越过了拐点的系统和一个还在拐点之下的系统之间的距离。AWS 甚至把设置也点明了:1M 那个数字意在反映*“真实的峰值和突发速率”,而”生产系统通常会超额配置,好避开网络饱和”*——换句话说,你不会在生产里把 OSS 跑到那个饱和点;你会配在拐点之下,那里差距很小。
AWS 的 Transport(非 cluster)表讲的是一模一样的故事:@100k,OSS-Java P99 32µs vs Premium 29µs(基本打平);@1M,OSS-Java P99 57µs vs Premium 39µs(~1.5×)。博客自己那句总结——“Premium 比 OSS 高出 33%(Transport)和 29%(Cluster)“——才是诚实的、拐点之下的倍数:大约 1.3×,离头条那个 59× 远着呢。
碰到任何 DPDK-对-标准 的说法,解读准则就一条: 先问*“这个工作点上,两个 driver 是不是都在各自拐点之下?”* 是的话,预期一个个位数的小倍数差异。要是标准 driver 饱和了、旁路的没有,那个巨大比值就是饱和假象——它告诉你的是 OSS 拐点在哪,不是传输快了多少。内核旁路也不是白来的(专用核、hugepages、NIC/PCI 绑定、和平台相关的搭建),所以该拿它去掂量的是你到底要不要更高的上限、或者更紧的尾部——而不是去信一个抽象的”快 8ד。
7. 信一个数字之前的核对清单
Section titled “7. 信一个数字之前的核对清单”- 这一行背后的到达形态(
burst、限速还是开环)我清楚吗? - 消息大小 + MTU 跟生产对齐了吗?
- 这是 P50 头条 还是 P99.9 尾部——我的 SLA 在乎的是哪个?
- 这台机器在这个速率下是在拐点之下、还是已经在饱和?(状态标了”Saturated”或劣化吗?)
- 我做的是同类对比吗(两个 driver 都没饱和、大小/速率/burst 都一样)?
- 我为自己的流量找过拐点了吗,还是拿头条当容量在引用?
- 我选的规格,在峰值到达时留了 ρ < ~0.7 的余量吗?
- 算
Q_burst时我把所有同时开火的生产者都叠加进去了吗(而不是用单源速率),我的 socket buffer / 接收窗口 / term buffer ≥Q_burst × 每条线上字节数吗? - 我是不是正要把这个数字递给一个非工程师?如果是,我把它翻成可承诺目标了吗(看尾部、在工作点、带条件)——而不是甩出实验室头条?
这一页讲的是怎么解读数字。至于 driver 如何把字节送上线路的那套机制——TermScanner、流控、丢包恢复——请去 The Aeron Files,我们不在这里重新推导。至于 Q_burst 往下喂进去的 buffer 规划,见接收路径 buffer、buffer 规划计算器 和发送端排队与突发限速。
本站与 Adaptive Financial Consulting Limited 或 Aeron 项目无任何关联,未获其背书或赞助。 Aeron 是 Adaptive Financial Consulting Limited 的注册商标。
Aeron 是 Adaptive Financial Consulting Limited 在英国及其他国家/地区的商标。