丢包注入与 CUBIC 拥塞控制
Java media driver 自带一个扩展包 —— io.aeron.driver.ext —— 其中有两个在运维上很有价值、但默认
driver 不会为你开启的能力:
- 带丢包生成器的 debug channel endpoint —— 主动注入丢包,让你在生产环境替你做这个实验之前先测出恢复行为。
- CUBIC 拥塞控制 —— 用一个会在丢包时回退、随后重新探测上升的窗口,替换 Aeron 固定的接收窗口,适用于共享或长肥网络。
两者默认都不接入。本页讲的是运维侧 —— 如何开启、有哪些旋钮,以及它们如何影响 p50 / p99 / 吞吐。至于 Aeron 内部如何检测间隙、重传、施加流控,请参阅 The Aeron Files。
第一部分 —— 用于混沌测试的丢包注入
Section titled “第一部分 —— 用于混沌测试的丢包注入”容易漏掉的两步接线
Section titled “容易漏掉的两步接线”丢包率由一个系统属性控制,但只设这个属性什么都不会发生。默认的 channel endpoint 根本不会去查丢包 生成器。你必须先换上 debug endpoint,这是另一个独立的属性:
所以一个 10% 接收侧数据丢包的 driver 需要同时设置两个属性:
java \ -Daeron.ReceiveChannelEndpoint.supplier=io.aeron.driver.ext.DebugReceiveChannelEndpointSupplier \ -Daeron.debug.receive.data.loss.rate=0.10 \ -Daeron.debug.receive.data.loss.seed=42 \ io.aeron.driver.MediaDriverDebugSendChannelEndpointSupplier 是发送侧对应的开关。你用到的丢包旋钮作用在哪一侧,就设哪一侧。
四个丢包旋钮
Section titled “四个丢包旋钮”它们由 DebugChannelEndpointConfiguration 读取。每个 rate 是 [0.0, 1.0] 区间内的概率,
每个都有配套的 seed。
| 属性 | 丢弃什么 | 视角 |
|---|---|---|
aeron.debug.receive.data.loss.rate / .seed | 数据 | 接收侧入站 —— 到达接收方的数据 |
aeron.debug.receive.control.loss.rate / .seed | 控制(SM / NAK) | 接收侧出站 —— 离开接收方的控制 |
aeron.debug.send.data.loss.rate / .seed | 数据 | 发送侧出站 —— 离开发送方的数据 |
aeron.debug.send.control.loss.rate / .seed | 控制(SM / NAK) | 发送侧入站 —— 到达发送方的控制 |
默认值:每个 rate 默认为 0.0(无丢包),每个 seed 默认为 -1。
决定你的测试是否有意义的两个事实
Section titled “决定你的测试是否有意义的两个事实”rate 这条路径只用 RandomLossGenerator,仅此而已。 DebugChannelEndpointConfiguration.lossGeneratorSupplier(rate, seed)
在 rate == 0 时短路成一个空操作生成器,否则返回一个 RandomLossGenerator。它的判定逐帧就是
random.nextDouble() <= lossRate。这个包里还有 FixedLossGenerator 和 MultiGapLossGenerator ——
但系统属性这套机制从不实例化它们;它们用于编程式的测试搭建(例如 JavaTestMediaDriver)。因此:
- rate 旋钮给你的是均匀随机丢包,而非编排好的间隙模式。
- 由于每一帧都掷一次骰子,随机丢包也会以同样的概率丢掉重传帧。这对一条有损链路而言是真实的 ——
但意味着高丢包率会让重传不断累积。(相比之下,编排式的
FixedLossGenerator/MultiGapLossGenerator对一帧只丢一次,并放行它的重传。如果你需要这种行为,得走编程式路径,而不是这些属性。)
seed 决定可复现性。 seed == -1(默认)会构造 new Random() —— 每次运行都是不同的丢包模式,
这会让一个偶发失败无从二分定位。固定任意非负 seed,丢包模式就逐次完全一致,即相同 seed 意味着
相同的测试结果 —— 这是有效 A/B(默认 timer vs 推导 timer)和 CI 的前提。
丢包对 p50 / p99 / 吞吐的影响
Section titled “丢包对 p50 / p99 / 吞吐的影响”- 吞吐受冲击最直接:重传与新数据争抢同一带宽,而且一旦重传开始累积,代价上升得比丢包率本身更快。
- p99 / 长尾最先被拉长。一个被丢的帧会让订阅方停顿,直到检测 + 重传完成;这段停顿由 NAK/重传 timer 决定 —— 这正是 NAK timer tuning 成为坍缩丢包长尾那个杠杆的原因。
- p50 在低丢包率下相对稳定 —— 大多数消息第一次就到达 —— 只有当丢包高到影响中位数时才退化。
测量上述任何一项时都要用固定 seed,否则逐次运行的方差会淹没你想读出的效应。
第二部分 —— CUBIC 拥塞控制
Section titled “第二部分 —— CUBIC 拥塞控制”默认情况下,Aeron 接收方通告一个固定窗口(StaticWindowCongestionControl,即 cc=static 策略):
接收窗口不会因丢包而改变。在一条按你的 BDP 大小专建的局域网上,
这是理想的 —— 不做无谓的回退。但在共享或长肥链路上就不然了:固定的大窗口会持续往一条已拥塞的路径里
塞数据,加剧排队丢包和长尾。
CubicCongestionControl 用一个遇丢包收缩、随后沿三次曲线回升的窗口取而代之 —— 与 Linux TCP 默认
算法同一族。它是接收侧控制器:操纵驱动 status message 的接收窗口长度。
CUBIC 按 channel 通过 cc URI 参数选择 —— 不需要 driver 级属性,因为
DefaultCongestionControlSupplier 本身就认得它:
// 按 publication/subscription channel:aeron:udp?endpoint=192.168.1.10:40456|cc=cubiccc=static(或省略 cc)保持固定窗口的默认行为;cc=cubic 让该 stream 启用 CUBIC。
若要让 driver 范围内的每条 stream 都强制走 CUBIC,则设置 supplier:
-Daeron.CongestionControl.supplier=io.aeron.driver.ext.CubicCongestionControlSupplierCUBIC 的三个旋钮
Section titled “CUBIC 的三个旋钮”由 CubicCongestionControlConfiguration 读取:
| 属性 | 默认值 | 作用 |
|---|---|---|
aeron.CubicCongestionControl.measureRtt | false | 为 true 时实时测量 RTT(RTT 探测),而非使用静态估计 |
aeron.CubicCongestionControl.initialRtt | 100us | 不测量时使用的 RTT 估计;同时也是 RTT 超时的下限 |
aeron.CubicCongestionControl.tcpMode | false | 加入 TCP 友好窗口项,使 CUBIC 在低 RTT 区丢包后不会低于 TCP |
算法自身的常量在代码中固定:缩放系数 C = 0.4、乘性递减 B = 0.2、初始拥塞窗口
INITCWND = 10 个 MTU。
它如何反应(直接取自控制器)
Section titled “它如何反应(直接取自控制器)”- 遇丢包: 把当前窗口记为
w_max,然后将窗口砍到cwnd × (1 − B)—— 即减少 20%(下限为 1 个 MTU)。这正是固定窗口做不到的回退。 - 恢复: 窗口沿
W_cubic = C·(T − K)³ + w_max回升,其中K = cbrt(w_max · B / C)是回到w_max所需的时间。靠近w_max时增长慢、远离时增长快 —— 这就是三次曲线的形状。(类注释给了一个算例:MTU 4 KB、 最大窗口 128 KB ⇒K ≈ 2.5 s。) tcpMode开启时: 额外计算 TCP 等效窗口并取较大者,使 CUBIC 在低 RTT 丢包后的区段里至少和 TCP 一样激进。
CUBIC 暴露了两个 per-image 计数器,可用 aeron-stat 实时观察:rcv-cc-cubic-wnd(它当前通告的窗口)
和 rcv-cc-cubic-rtt(它的 RTT 估计)。看着窗口锯齿式上升、遇丢包坍缩,是确认它确实生效的最快方式。
static vs CUBIC —— 各自的适用场景
Section titled “static vs CUBIC —— 各自的适用场景”cc=static(默认) | cc=cubic | |
|---|---|---|
| 最适合 | 按 BDP 大小专建的局域网 | 共享 / 广域网 / 长肥链路 |
| 遇丢包 | 窗口不变 —— 继续推送 | 窗口砍 20%,三次曲线恢复 |
| 吞吐 | 干净链路上最高 | 干净链路上略低;拥塞链路上更高(避免坍塌) |
| 长尾 (p99) | 若固定大窗口压垮拥塞路径,可能膨胀 | 拥塞下更紧;回退封顶排队 |
经验法则:干净专用链路 → 保持 static 并把窗口设为 BDP;争用或长肥链路 → 试 cubic 并对照你的
吞吐目标观察 rcv-cc-cubic-wnd。
两者结合使用
Section titled “两者结合使用”ext 的这两半可以组合:用 cc=cubic 驱动一条 stream,通过 debug endpoint 注入一个固定 seed 的丢包率,
然后观察 rcv-cc-cubic-wnd 的反应。这就是一个可复现的工作台,用来观察你的拥塞控制在已知丢包下如何表现
—— 这种测试若没有确定性丢包源与动态窗口同时在场,是没法诚实做出来的。
附录 —— C driver 的丢包注入方式不同
Section titled “附录 —— C driver 的丢包注入方式不同”C media driver(aeronmd)不使用 Java 的 ext 属性。它通过 UDP transport 拦截器注入丢包,
由环境变量配置:
export AERON_UDP_CHANNEL_INCOMING_INTERCEPTORS="loss"export AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_LOSS_ARGS="rate=0.2|recv-msg-mask=0xF"aeronmd参数是用竖线分隔的 key=value 对:rate(概率,按 double 解析)、seed(无符号),以及
recv-msg-mask(一个十六进制位掩码,选择哪些入站消息类型会被丢弃)。关于你何时会运行 C driver,
参见 Media Driver Selection: Java vs C。
本站与 Adaptive Financial Consulting Limited 或 Aeron 项目无任何关联,未获其背书或赞助。 Aeron 是 Adaptive Financial Consulting Limited 的注册商标。
Aeron 是 Adaptive Financial Consulting Limited 在英国及其他国家/地区的商标。