跳转到内容

NAK 定时器调优(杠杆 B)

缓冲区(杠杆 A)负责 预防 丢包;但它们无法让一次丢包变 便宜。 一旦数据包真的丢了,尾部代价由 NAK/重传定时器 决定——在默认值下,组播语义流上的每次丢包都要付出 10–20 ms,无论缓冲区配得多好。本页给出三个旋钮、由 你的 RTT 与接收方数量推导取值的公式,以及实测证据。

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.98,286 µs105 µs(约 79 倍改善)
Max17.4 ms2.99 ms(= 无丢包下限)
p5037 µs37 µs(不变——只有丢包的那部分付出代价)

两个对照组验证了整体框架:零丢包 下,计算器最小值缓冲与 16 倍大的缓冲逐百分位完全一致—— 一旦覆盖 BDP,更大的缓冲毫无收益(杠杆 A 关乎 够不够,不关乎大小)。而丢包 + 默认定时器时, 尾部落在 17.4 ms——正中定时器算术预测的 10–20 ms 区间。

  1. 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,离谱地过大。
  2. aeron.nak.multicast.group.size —— 随机化所假设的接收方数量。不是调优项,是 输入:照实填。
  3. 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 = 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+)。

  1. 孤立恢复机制。 定时器只在恢复互不重叠时有意义:丢包率 × 消息速率 × T_recovery < 0.1。 实测的失败形态:2% 丢包 × 100k msg/s 时,流坍缩到 444 ms 均值——任何 定时器都救不了, 重叠的恢复会排队。定时器适用于 稀疏残余丢包;频繁丢包是杠杆 A 的问题。
  2. 重传仓库覆盖。 重传数据来自 term bufferλ × (最慢接收方滞后 + T_recovery) ≤ termLength / 2,否则间隙被覆写、image 断裂。
  3. 风暴防护。 10 ms 默认值保护的是千级接收方的组。激进配置在 N ≤ ~10 下已验证; N ≫ 10 时把 group.size 按真实 N 设置、并保持退避窗口足够宽以摊开 N 个同时的 NAK—— 重新推导,不要照抄。

操作层面的陷阱:这些是 media-driver 属性。driver 是独立进程——它 不会 继承你应用 JVM 的 -D 参数。

Terminal window
# 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=500us
AERON_NAK_MULTICAST_GROUP_SIZE=3
AERON_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.javalambda = log(groupSize) + 1 随机化与 maxBackoffT = K × GRTT 取值依据)。
  • 实测结果:裸金属 4 臂 A/B(计算器最小值 vs 大缓冲;种子 0.01% 丢包下默认 vs 推导定时器), 1→3 MDC,固定丢包种子保证 A/B 有效。