看 BBR 论文 展示的一幅猛图:

很多人惊讶于 BBR 竟然对丢包无感,稍微近看一点,BBR 只是在 20% 以内的丢包率下对丢包无感,更深入探究,会发现抗 20% 丢包率与 pacing_gain = 1.25 有关。
但这个图还是欺骗了绝大多数人。
注意横轴标度,loss rate = 1% 之前采用 10 倍标度,1% 往后采用 2 倍标度,给人的观感是,描绘 BBR 的那条绿线是平的,哇,丢包无感。即使在 20% 的丢包率下,吞吐依然可以接近 100Mbps 而不是 80Mbps,从而产生广告效应。
幸亏还有一条黑色的 ideal 线揭穿了真相。对数横坐标下,黑色线事实上是一条 y = -x 线,它显示了理想情况下, throughout = 100 * (1 - loss_rate)
事实上,这幅图的真相如下:

上面的图广告效应就不明显了。
简单计算一下 BBR 的抗丢包能力。
计算之前,必须按链路画像分类讨论,考虑以下的链路:

设 loss rate = p,gain = g,由于 sender 侧不限带宽,它可以任意 pacing rate 发送,我们当然希望经过 p 损失后吞吐依然可以保持限速带宽 B,因此:
(g * B) * (1 - p) = B (式子右边的 B 是图右边的 B) x100*0.99 = 100
此时 g * B 作为整体,视为从 sender 网卡发出的 rate,经过 p 损失后,delivery rate 为 B,这就是一般意义上所谓的 “抗丢包” 神话,代价是 “从 sender 侧一直到丢包点” 需提供足够的余量发送带宽,用来包容丢包损失。
更严格的约束如下:

这种情况下 sender 侧不再有余量带宽可供 probe 增益,因此:
g * (B * (1 - p)) = B (式子右边的 B 是图左边的 B)
和上一种情况相反,此时 B * (1 - p) 作为整体,表示经过 p 损失后的 delivery rate。这种情况很容易理解,单位时间内发 10 个包,50% 丢包率丢 5 个,receiver 永远只能收 5 个,p 的丢包率需要多发的 g * B - B 的数据来重传补偿。
第二种情况相当于从水管一端注水,水管是漏的,中间漏掉一部分,水管另一段不可能流出与注水等量的水。
设 p = 99%,B = 100,上述两种情况各举一例。
情况一,sender 发 g * 100 * 0.01 = 10000 个包,丢 99%,还剩 100 个,打满 100 带宽。
情况二,sender 只能发 g * (100 * 0.01) = 100 个包,丢 99%,还剩 1 个,只能打满 1% 带宽。
现实场景应是上述两者结合,如果 firstmile 丢包限速,就是后一种,若 lastmile 丢包限速,就是前一种。可能还有更复杂融合,理论上不矛盾。
同时,这两种情况提示了一种链路整形原则,loss 一定要离 sender 越近越好,坏事尽早发生,限速要离 receiver 越近越好,给补偿留足余量。lost 在前,限速在后,提高效能。
…
但现实中,BBR 的表现如何呢?设置 p = 20%,g = 1.25,得到接近 80% * B 的实际吞吐,但根据上述公式,考虑上面第二种情况,p = 40%,g = 1.7,却没有得到 B * 60% 的吞吐。Why?
周五早上起床很早,做了些测试和 fix,发了一则朋友圈:

我来说说是怎么回事。
按照 BBR 算法逻辑,首先,连续两次 ProbeUP 必须在 10 round 以内才可能保持住 maxbw 以防止 maxbw 以 p 衰减下去,其次,每次 ProbeUP 必须探测到真实的上限才能获得真实的 maxbw,然而 BBR 却被 loss 打断:
/* A pacing_gain > 1.0 probes for bw by trying to raise inflight to at
* least pacing_gain*BDP; this may take more than min_rtt if min_rtt is
* small (e.g. on a LAN). We do not persist if packets are lost, since
* a path with small buffers may not hold that much.
*/
if (bbr->pacing_gain > BBR_UNIT)
return is_full_length &&
(rs->losses || /* perhaps pacing_gain*BDP won't fit */
inflight >= bbr_inflight(sk, bw, bbr->pacing_gain));
只要检测到 loss,就退出了 ProbeUP。BBR 的理由是 “We do not persist if packets are lost, since a path with small buffers may not hold that much.” 它害怕一些小 buffer 的链路兜不住 g * BDP 这么大的 inflight,所以才对 loss 进行反应。
我认为这个对 loss 反应的理由不伤大雅,挺合理,但这只是一个微小的假设,却放在 ProbeUP 的关键路径无条件执行,只要发生 loss,即便不是因为小 buffer 无法 hold that much,也会退出 ProbeUP,这让 ProbeUP 更容易违背 “raise inflight to at least pacing_gain*BDP” 的承诺。此其一不合理之处。
再看 bbr_set_cwnd_to_recover_or_restore,同样对 loss 进行反应,一旦检测到 loss 便进入数据包守恒,此时便失去了 cwnd_gain = 2 的 cwnd 增益,如果恰有 new-data 和 retrans-data 需要以 pacing_gain * delivery_rate 发送,数据包守恒可能导致 cwnd-limited。此其二不合理之处。
BBR 理论上对 new-data 和 retrans-data 一视同仁不 care 丢包,依赖 maxbw,minrtt 驱动发送,同时以 secondary controller cwnd 辅助限制 buffer 的侵占。但实际上这些几乎全部被违背。
BBR 不 care 丢包事实上只是在 loss 状态 “记住了 maxbw”,并且多流共存场景下 BBR 会逐渐侵占 buffer 直到 ProbeRTT。和 BBR 不 care 丢包的宣传相反,BBR 对丢包的反应过激,loss 状态下,BBR 无法进行完整周期的 ProbeUP,由于数据包守恒,它甚至没有足够的潜在 inflight 进行 ProbeUP。
如果 BBR 属实如宣传般那样名副其实,它不需要在 loss 状态如此谨慎。BBR 的拥塞自适应逻辑就像一个无级变速装置,早就内置其中,一旦发生拥塞,无需丢包指示,有效测量 delivery rate 会逐步滑跌,而 RTT 也总以 10s 内最小的采样值算数,BDP 在拥塞状态趋向变小,这本身就有拥塞控制的效果。
但仅从算法本身看,我们也能看得出,BBR 对事件反应非常迟钝,所以才需要加些催化剂,但不管怎么说,BBR 名不副实。我一直说 BBRv2 是妥协的产物,它确实是,它最终还是不得不回归 Reno/AIMD 那套逻辑,然而数据包守恒确实也是前 AIMD 时期的产物,如今在 BBR 依然有影子。
…
将 loss 判断全部忽略,完全靠 BBR 自身收敛,就是我朋友圈发的图了:

预设 40% 丢包率,重传率完全一致,忽略 loss 反应的吞吐要大很多。
如果执念不忽略丢包,还有一种方法。loss 退出 ProbeUP 导致其未竟全功,保留这个逻辑,多几次 ProbeUP 也是等效的。ProbeUP 碰到 loss 的概率随丢包率增加而增加,在 8 round 中多次 ProbeUP 可以累加 ProbeUP 结果,低效 loss 提前退出 ProbeUP 的 inflight 损失:
static int bbr_pacing_gain[] = {
BBR_UNIT * 5 / 4, /* probe for more available bw */
BBR_UNIT * 3 / 4, /* drain queue and/or yield bw to other flows */
BBR_UNIT * 5 / 4, /* probe for more available bw */
BBR_UNIT * 3 / 4, /* drain queue and/or yield bw to other flows */
BBR_UNIT * 5 / 4, /* probe for more available bw */
BBR_UNIT * 3 / 4, /* drain queue and/or yield bw to other flows */
BBR_UNIT * 5 / 4, /* probe for more available bw */
BBR_UNIT * 3 / 4, /* drain queue and/or yield bw to other flows */
//BBR_UNIT, BBR_UNIT, BBR_UNIT, /* cruise at 1.0*bw to utilize pipe, */
//BBR_UNIT, BBR_UNIT, BBR_UNIT /* without creating excess queue... */
};
但无论怎样,BBR 大开合的本质是改变不了的,这是它所有优势的根源,也是它固有的缺陷。多流共存场景,所有上述看起来合理的假设和更正全部失效,不管是 BBR 本身的假设还是我的假设,都将拉胯,唯一留下来的效果是比其它流高的实际吞吐,比其它流高的重传率,以及对 buffer 的侵占,而这里面成也萧何,败也萧何。
BBR 依赖 gain * delivery_rate 来探测余量带宽,但在丢包场景下,同样行为的语义便成了补偿丢包。delivery_rate 作为 pacing rate 随丢包率衰减,gain 作为增益系数增益补偿重传。可是至少 Linux Kernel TCP BBR 的实现却不是这样,相反,它对丢包反应太过激,以至于 BBR 未竟全功。然而修改却不容易,我们不能光考虑吞吐效率,更要考虑共存公平性,显然这二者对于 BBR 是一个得此失彼的矛盾双方,而 BBR 的大开合就在这两者之间伸展收缩。换句话说,BBR 是一个粗糙的算法。
浙江温州皮鞋湿,下雨进水不会胖。
我的问题很简单:我是否必须在使用RubyonRails的类上require'csv'?如果我打开一个railsconsole并尝试使用CSVgem它可以工作,但我必须在文件中这样做吗? 最佳答案 CSVlibrary是ruby标准库的一部分;它不是gem(即第三方库)。与所有标准库(与核心库不同)一样,csv不会由ruby解释器自动加载。所以是的,在您的应用程序中某处您确实需要要求它:irb(main):001:0>CSVNameError:uninitializedconstantCSVfrom(irb):1from/Us
Ruby是完全面向对象的语言。在ruby中,一切都是对象,因此属于某个类。例如5属于Objectclass1.9.3p194:001>5.class=>Fixnum1.9.3p194:002>5.class.superclass=>Integer1.9.3p194:003>5.class.superclass.superclass=>Numeric1.9.3p194:005>5.class.superclass.superclass.superclass=>Object1.9.3p194:006>5.class.superclass.superclass.superclass.su
有人知道为什么我的rails3.0.7cli这么慢吗?当我运行railss或railsg时,他大约需要5秒才能真正执行命令...有什么建议吗?谢谢 最佳答案 更新:我正在将我的建议从rrails切换到rails-sh,因为前者支持REPL,而rrails不是用例。此外,当与ruby环境结合使用时,修补似乎确实可以提高性能变量,现在反射(reflect)在答案中。一个可能的原因可能是这个performancebuginruby每当在ruby代码中使用“require”时,它就会调用一些代码(更多详细信息here)。在使用Rai
最近火热的“数字藏品”,你真正了解吗?其实有很多人会把数字藏品跟NFT混为一谈,但其实这两者还是有差别的。数字藏品并不等同于NFT数字藏品是什么?直观来看,它可能就是一张数字化照片或视频,甚至就只是一串数字。但它却是一件对应特定作品、艺术品生成的包含着大量数字信息且拥有唯一加密信息的可以买卖交易的收藏品。NFT则是指一种基于以太坊区块链的“非同质化代币”。它在百度百科里的释义是“用于表示数字资产(包括jpg和视频剪辑形式)的唯一加密货币令牌,可以买卖”。比如已被很多人认识的比特币就是NFT的一种。NFT在元宇宙中发挥的作用是巨大的,目前正是它在支撑着元宇宙中的经济体系。数字藏品其实也是NFT的
我正在开发Rails5应用程序并使用Assets管道。它在开发模式下运行良好,但如果我尝试在生产模式下运行它,它无法正确加载图像和样式。我查了一下,发现是因为config.assets.compile=false在config/environments/production.rb中除非我将其设置为真,否则它根本不起作用。我知道实时编译不适合生产,有什么解决方案? 最佳答案 有两个与在Rails服务器中提供Assets相关的选项:Assets编译config.assets.compile=true指Assets编译。也就是说,当Rai
我从Rails收到了很多回击,因为我将User子类化为许多不同的子类。在我的应用程序中,并非所有用户都是平等的。实际上有很多模型对象,并不是每个用户类型都可以访问它们。我还需要一种方法来执行多态行为。例如,许多方法的行为会因类型而异。多态性不就是为了这个吗?但问题是,我总是被Rails拒之门外。默认值——尤其是表单提交到参数哈希的方式——似乎像非子类模型一样工作。链接和参数哈希值只是默认值真正让您厌烦的两种方式。在Rails中处理不同类型用户的复杂逻辑的“正确”方法是什么?在Java中,子类化模型是有效的——您不必为了让它按照您想要的方式工作而费尽心思。但是在Rails中,很难让子类与
Ruby真的可以用作函数式语言吗?有哪些好的教程可以教授该语言的这一方面?注意:我真的想使用并坚持使用Ruby作为我的主要语言,所以我现在对转换为YAFL(另一种函数式语言)不感兴趣。我对Ruby的功能方面相对于标准功能语言基线的表现非常感兴趣。谢谢。 最佳答案 是的......有点。Ruby缺乏合理的结构来强制实现不变性。(Object#freeze不算)不变性确实是函数式语言的基石。此外,Ruby的核心库高度面向命令式设计。它的Array和Hash类本质上都是可变的,甚至String也有使非不可变的方法(例如gsub!)。具有讽
我正在尝试获得最佳的代码覆盖率/开发时间结果目前我使用rspec+shoulda来测试我的模型,使用rspec+capybara来编写我的验收测试。我尝试为一个简单的crud编写一个Controller测试,但它花费的时间太长了,最后我得到了一个令人困惑的测试(可能是我的错)使用rspec进行Controller测试的最佳实践是什么?这是我的测试和我的Controller的要点(一个测试还没有通过):https://gist.github.com/991687https://gist.github.com/991685 最佳答案 也
我正在尝试查找满足两个条件的所有记录。例如:ruby-1.8.7-p302>Person.all=>#=>#=>#我想获取“Jane”和“Tom”的记录。我正在尝试这个,但它不起作用:Person.find_all_by_state("Wisconsin").find_all_by_single(true) 最佳答案 Person.where(:state=>"威斯康星州",:single=>true) 关于ruby-on-rails-查找两个条件都为真的所有记录,我们在StackOve
我是不是从根本上误解了Ruby?我已经编写Ruby代码大约2年了,就在今天偶然发现了这个......ruby-1.8.7-p249>i=trueandfalse=>falseruby-1.8.7-p249>i=>true有人可以解释一下这里发生了什么吗?我确定它符合规范,但对我来说这似乎违反直觉...... 最佳答案 操作符&&和and有不同的优先级,=恰好介于两者之间。irb(main):006:0>i=trueandfalse=>falseirb(main):007:0>i=>trueirb(main):008:0>i=true