NAK 定时器调优(杠杆 B)
缓冲区(杠杆 A)负责 预防 丢包;但它们无法让一次丢包变 便宜。 一旦数据包真的丢了,尾部代价由 NAK/重传定时器 决定——在默认值下,组播语义流上的每次丢包都要付出 10–20 ms,无论缓冲区配得多好。本页给出三个旋钮、由 你的 RTT 与接收方数量推导取值的公式,以及实测证据。
三个旋钮与已验证的配置
Section titled “三个旋钮与已验证的配置”Media-driver 属性——必须到达 driver 进程(见下文陷阱):
aeron.nak.multicast.max.backoff=500us # 默认 10ms —— 最主导的旋钮aeron.nak.multicast.group.size=3 # 默认 10 —— 设为你「真实」的接收方数量aeron.retransmit.unicast.linger=2ms # 默认 10ms —— 重传抑制窗口实测(裸金属,1 发布方 → 3 个 MDC 接收方,224B @ 50k msg/s,相同种子的 0.01% 丢包):
| 默认定时器 | 调优配置 | |
|---|---|---|
| p99.9 | 8,286 µs | 105 µs(约 79 倍改善) |
| Max | 17.4 ms | 2.99 ms(= 无丢包下限) |
| p50 | 37 µs | 37 µs(不变——只有丢包的那部分付出代价) |
两个对照组验证了整体框架:零丢包 下,计算器最小值缓冲与 16 倍大的缓冲逐百分位完全一致—— 一旦覆盖 BDP,更大的缓冲毫无收益(杠杆 A 关乎 够不够,不关乎大小)。而丢包 + 默认定时器时, 尾部落在 17.4 ms——正中定时器算术预测的 10–20 ms 区间。
每个旋钮的作用
Section titled “每个旋钮的作用”aeron.nak.multicast.max.backoff—— 接收方检测到间隙后,先等一个[0, ~backoff]内的 随机 延迟再发 NAK,避免大组对发送方形成 NAK 风暴。默认 10 ms 意味着丢包后最多 10 ms 恢复 尚未开始——这就是 20 ms 尾部的大头。随机化按lambda = log(groupSize) + 1在退避窗口上分布 (OptimalMulticastDelayGenerator),其 javadoc 本身就把窗口定义为maxBackoffT = K × GRTT—— 即按 组 RTT 的倍数计,而非绝对常数。10 ms 默认值在互联网级 1 ms RTT 下是 K≈10;在 35 µs 的局域网上则是 K≈300,离谱地过大。aeron.nak.multicast.group.size—— 随机化所假设的接收方数量。不是调优项,是 输入:照实填。aeron.retransmit.unicast.linger—— 发送方重传某范围后,在此时长内忽略针对它的后续 NAK(抑制重复)。但它也会在 重传本身丢失 时阻塞再恢复——默认 10 ms 让双重丢包变成 20 ms+ 事件。
max.backoff —— 下界由 RTT 定,上界由 SLO 定
Section titled “max.backoff —— 下界由 RTT 定,上界由 SLO 定”下界: backoff ≥ ~2 × RTT (抑制机制 + 乱序安全)上界: backoff ≤ 单次丢包预算 − 2 × RTT (每次丢包的尾部 SLO)取值: backoff ≈ ½ × 单次丢包预算,并钳制在 ≥ 2 × RTT下界的由来:抑制机制本身就是一个往返——第一个接收方的 NAK → 重传必须在其他接收方的定时器触发前
到达它们。上界:最坏单次丢包恢复 ≈ backoff + 2 × RTT。
算例:CPG RTT 35 µs、预算 1 ms → 下界 70 µs → 500 µs(实测配置)。跨 AZ RTT 1 ms、预算 5 ms → 下界 2 ms → 2–3 ms。诚实的推论:backoff 随 RTT 缩放——跨 AZ 链路不可能在保住抑制机制的前提下 实现亚毫秒恢复。
group.size —— 照实填
Section titled “group.size —— 照实填”group.size = N_actual (流上真实的接收方数量;若波动则取最大预期)填高了 → 分布拖长(白等);低于真实 N → 多个接收方抽到相近延迟 → 重复 NAK(正是该机制要防的风暴)。 3 节点集群的日志通道填 2(两个 follower);3 接收方的 MDC 填 3。
linger —— 下界是一轮恢复,上界看双重丢包预算
Section titled “linger —— 下界是一轮恢复,上界看双重丢包预算”下界: linger ≥ backoff + RTT (吸收「同一次」丢包迟到的 NAK)取值: linger ≈ 2–4 × (backoff + RTT)双重丢包最坏 ≈ linger + backoff + 2 × RTT算例:backoff 500 µs + RTT 100 µs → 下界 600 µs → 2 ms → 双重丢包 ≈ 2.6 ms(默认值下是 20 ms+)。
三道有效性检查——先过再信
Section titled “三道有效性检查——先过再信”- 孤立恢复机制。 定时器只在恢复互不重叠时有意义:
丢包率 × 消息速率 × T_recovery < 0.1。 实测的失败形态:2% 丢包 × 100k msg/s 时,流坍缩到 444 ms 均值——任何 定时器都救不了, 重叠的恢复会排队。定时器适用于 稀疏残余丢包;频繁丢包是杠杆 A 的问题。 - 重传仓库覆盖。 重传数据来自 term buffer:
λ × (最慢接收方滞后 + T_recovery) ≤ termLength / 2,否则间隙被覆写、image 断裂。 - 风暴防护。 10 ms 默认值保护的是千级接收方的组。激进配置在 N ≤ ~10 下已验证;
N ≫ 10 时把
group.size按真实 N 设置、并保持退避窗口足够宽以摊开 N 个同时的 NAK—— 重新推导,不要照抄。
操作层面的陷阱:这些是 media-driver 属性。driver 是独立进程——它 不会 继承你应用 JVM 的
-D 参数。
# Java driver —— 参数加在「driver 自己的」JVM 上(或 properties 文件作为参数):java -Daeron.nak.multicast.max.backoff=500us \ -Daeron.nak.multicast.group.size=3 \ -Daeron.retransmit.unicast.linger=2ms \ ... io.aeron.driver.MediaDriver
# C driver(aeronmd)—— 环境变量:AERON_NAK_MULTICAST_MAX_BACKOFF=500usAERON_NAK_MULTICAST_GROUP_SIZE=3AERON_RETRANSMIT_UNICAST_LINGER=2ms时间值接受 us/ms 后缀;重启 driver 生效。
内嵌 driver(MediaDriver.launchEmbedded)
Section titled “内嵌 driver(MediaDriver.launchEmbedded)”如果你的应用内嵌 driver,就不存在独立进程——上述陷阱不适用,且有两种方式:
// 方式 1 —— -D 参数加在「应用」JVM 上(内嵌 driver 能读到):// java -Daeron.nak.multicast.max.backoff=500us ... MyTradingApp
// 方式 2 —— 编程式,显式且可 grep(setter 名来自 MediaDriver.java):final MediaDriver.Context ctx = new MediaDriver.Context() .nakMulticastMaxBackoffNs(TimeUnit.MICROSECONDS.toNanos(500)) .nakMulticastGroupSize(3) .retransmitUnicastLingerNs(TimeUnit.MILLISECONDS.toNanos(2));final MediaDriver driver = MediaDriver.launchEmbedded(ctx);方式 1 的编程式变体有一个时序陷阱:Context 在 构造时 从系统属性捕获默认值——
System.setProperty(...) 只有在 new MediaDriver.Context() 之前执行才有效。
把参数放在命令行(或用显式 setter),这个问题就不存在。
该配置要设在 每台主机 上——NAK 延迟累积在每条有损链路的接收侧。
适用范围:哪些流受这些旋钮管
Section titled “适用范围:哪些流受这些旋钮管”组播旋钮管 组播语义 的流:IP 组播与 MDC(dynamic 与 manual 两种 control 模式)——
其中包括 Aeron Cluster 的日志复制通道。纯单播流本就近乎立即 NAK
(aeron.nak.unicast.delay = 1 µs)——但其 重试 延迟是
delay × aeron.nak.unicast.retry.delay.ratio(默认 ×100),所以在乎双重丢包的话,
单播流上也要把 retransmit.unicast.linger 调低。
定时器在 丢包预防之后 调,而不是替代它:先止损(RX ring → 最大、CPU 隔离、
接收路径栈);然后用本配置给残余丢包的代价封顶。
验证只需一行:实测的单次丢包代价应 ≈ backoff + 2 × RTT——若远超此值,说明路径上另有问题。
- Aeron driver 源码 ——
Configuration.java(NAK/重传属性名与默认值)与OptimalMulticastDelayGenerator.java(lambda = log(groupSize) + 1随机化与maxBackoffT = K × GRTT取值依据)。 - 实测结果:裸金属 4 臂 A/B(计算器最小值 vs 大缓冲;种子 0.01% 丢包下默认 vs 推导定时器), 1→3 MDC,固定丢包种子保证 A/B 有效。