iis7建设网站,可以拿自己电脑做网站,wordpress 太慢了,网站建设 管理与维护试题工作原因要对一个 newreno 实现增加 sack 支持。尝试写了 3 天 C#xff0c;同时一遍又一遍梳理 sack 标准演进。这些东西我早就了解#xff0c;但涉及落地写实现#xff0c;就得不断抠细节#xff0c;试图写一个完备的实现。
这事有更简单的方法。根本没必要完全实现 RFC…工作原因要对一个 newreno 实现增加 sack 支持。尝试写了 3 天 C同时一遍又一遍梳理 sack 标准演进。这些东西我早就了解但涉及落地写实现就得不断抠细节试图写一个完备的实现。
这事有更简单的方法。根本没必要完全实现 RFC目标是高吞吐而不是实现标准 TCP因此只要保证最宽容的可用性剩下的交给现实。我们做 cc 时为提高性能何尝不是这里 cwnd 10那边 cwnd whatever(sk)异曲同工。
前面两周写了一些关于 TCP 演进的文字无论怎样这是个好机会以一个实例来展示演进过程中的方法论。捕捉其中关于 sack 的两个细节降窗和重传再写一些文字。
TCP 丢包后进入 fast retransmit/recovery 后涉及如何降窗和如何重传两件事TCP 经过 40 多年的演化关于这两件事铸建了 4 个里程碑。
最初的 TCP 只有 rto没有 fast retransmit此即 TCP tahoe只要有丢包即将 cwnd 1重新开始慢启动。第 1 个里程碑是 TCP reno 和 TCP newreno二者一脉相承。
reno 引入了 fast retransmit但有很多问题newreno 解决了这些问题。这部分参见 Wiki。seastar 只实现了 RFC6582 定义的 newreno非常原始但基本上这就是一个最小能用的 TCP 实现。
从第 2 个里程碑开始TCP 开始起飞。我的工作也就是尽量追着这个尾迹跟着飞在必要时加入一些自己的 trick或觉得标准实现太麻烦时偷一下懒裁剪掉些东西。
引入 fast retransmit 时 TCP 还不支持 sack收到 3 个 dupack 即进入 fast retransmit/fast recovery但只留下一个未被 ack 的 holehole 后面的情况什么也看不到没有任何启发式手段让 sender 猜测哪些 seg 丢了。
降窗如 范雅各布森 所述直接将 cwnd ssthresh cwnd / 2。RFC5681 不允许超过一半的 seg 在途(这似乎是在迎合或确认 ssthresh cwnd/2 这件事详见雅各布森管道) until all lost segments in the window of data in question are repaired, the number of segments transmitted in each RTT MUST be no more than half the number of outstanding segments when the loss was detected. 这显然降低了带宽利用率但这时没有任何信息指示如何做得更好(每发送一个 seg 只能至多带回 1 个 ack)解决这个问题的条件尚未具备。
关于重传除了一个 hole 没有任何辅助信息只能从 una 开始每次重传 1 个往前挪 nua没有任何别的办法。如下图所示整个图景相当于一个 seq space 构成的一维世界一 hole 以障目 随着 sack 被引入降窗和重传逻辑均起了大变化。我们关注的是这个变化是如何如丝般顺滑而一气呵成的以便这个方法论能指示 TCP(or any others) 未来的演化方向。
第 3 个里程碑来自 sack 被引入后。详见 RFC6675。 sack 反馈了更加丰富的信息sender 有了绕到 hole 后面查看究竟的途径这相当于引入了额外的维度将 seq space 直立了起来仰起头就能看到 seq space 的细节 TCP 将 seq space 抽象成一个 scoreboard该 scoreboard 上对 seq space 的每一个 seg 区分对待可 mark sackedmark lostmark reordering针对 lost seg 进行重传并 mark retransinflight 从而可精确获取
inflight snd_nxt - snd_una snd_retrans - snd_lost - snd_sacked
基于守恒律于是 cwnd inflight 更加精确。
二维世界丰富的多的信息指示 sender 做出更好的决策不再每次 retransmit 一个 segTCP 重传算法第一次统一处理重传 seg 以及新 seg它们统一受 cwnd 限制cwnd - inflight 0。
重传逻辑将 seg 分三个优先级mark lost 的 seg 被优先传输因为它们最容易补 hole 而推进 una新 seg 第二优先它们可能带回新的 sack推进 mark lost背后的考虑是尽量延后 mark lost而剩余的最后重传 下面再看降窗逻辑。
重传 seg 和新 seg 在 fast retransmit/recovery 状态被统一安排一个 seg 带回的 sack 可导致大量 mark lost参考前面 inflight 公式大量 seg 由于 mark lost 被清出 pipeinflight 急剧减少而腾出大量 pipe 空间。cwnd 一定时后果便是 burst(可 burst 一个 cwnd ssthresh 的数据)。而 burst 会加剧网络拥塞而丢包形成正反馈。
TCP 第一次以 ack 携带信息(sack)而非 ack 本身来计数引入了 scoreboard 细化了 seq space但处理降窗的方式却没有同步跟进处理这个 burst 问题。
有两个更平滑降窗的方案第一个是 Linux TCP 的 rate-halving 算法。rate-halving 思路很简单不再一下子将 cwnd 折半而是每收到 2 个 ack 将 cwnd 减 1这样收到一个 cwnd 的 ack 后cwnd 即原来的一半了。
但彼时 cc 已模块化丢包降窗的目标可自定义比如 cubic 将 cwnd 降为 0.7*cwnd而 rate-halving 写死了比例 0.5且未考虑 sack 计数而精度不够(sack 大大提高了判断精度rate-halving 却没有利用任何 inflight 信息)。 后来 Google 提出了 prr 算法将 cwnd 精确平滑收敛到 ssthresh (1 - beta)*cwnd。
思路和 rate-halving 类似只是 prr 基于 scoreboard 精确计算 fast retransmit/recovery 期间被 ack/sack 的数据 prr_delivered 和实际发送的数据 prr_out 以获得收益。按照正常的 seg 守恒律
cnt prr_delivered - prr_out
结果应为 0意思是 prr_delivered 被确认之后才能兑换等量的 prr_out 而发出按比例缩放这个守恒律即可
cnt (1 - beta)*prr_delivered - prr_out
以 beta 0.3 为例意思是 0.7 个单位的 prr_delivered 被确认后兑换 一个单位的 prr_out为了满足守恒律使结果为 0prr_out 也要进行 0.7 缩放因此必须从 cwnd 中扣除这个差异
cwnd inflight - |cnt|
由此在一个 rtt 内sender 缓慢地平滑地等比例地将 cwnd 降到 (1-beta)*cwnd。
…
降窗逻辑看起来很 perfect。
让我们回看引入 sack 后的第 3 个里程碑的重传逻辑看它有什么问题不能解决并且该问题还必须被解决。
随着带宽资源的增加传输协议对带宽利用率有所期待而不再仅仅满足于保证可用性的 AIMD 算法。需实时测量的 rate-based cc 在这个背景下被设计。
与 cwnd-based cc(cwnd 配额用尽即不可再发送) 不同实时测量考虑 delivery rate 反馈而非仅仅 ack 时钟需要 sender 有持续数据流可发送即使 rwnd 憋死(矢量滑动 rwnd 导致我对此有单边解法)在收不到重传 seg 的 sack 后要再次重传该 seg(这在某种意义上确实可以取消 rto 了但这是后话)。
RFC6675 标准 sack 重传机制涉及两个细节一个是 reordering 更新一个是依赖前者的 mark lost先看 reordering 更新 reordering 根据 sack 的布局和批次不断被更新关于 reordering 的细节详见早期的一篇分析TCP 的乱序丢包判断。
基于此再看 mark lost 这两个机制揉在一起非常复杂。
RFC6675 的算法显然无法满足多次重传相同 seg因为 sender 的 scoreboard 没有信息区分多次 mark lost 被重传的 seg这就无法在 mark lost 和 mark reordering 之间做判断 每个 seg 只能 mark lost 一次如果重传丢了只能 rto。
第 4 个里程碑就来了。
在 sack 引入的额外维度后一维 seq space 展成了二维sender 可以从额外的维度绕过 hole 看到信息量更大的 scoreboard。寻着同样的思路再加一个维度为传输加上时间轴这就是 rack 两根坐标轴就搞定了所有事情不再需要额外的两个过程虽然也依然会有由于 reordering 对 rack window 所做的调整但相比更新 reordering 值本身的逻辑就太简单和直观了。
看一下清爽的 rack 全景 sender 维护一个统一安排 mark lost 重传 seg 和新 seg 的按发送时间 fifo 队列。每一个 hole 只要在一定时间(比它后发送的都被 ack/sack而它在此一定时间后仍是 hole)内没被 ack/sack 可以重新被 mark lost与其是否被重传过以及重传过几次无关。每次传输都会将时间戳打入以区别传输轮次。
就这样rack 引入一个时间序解决了 reordering 和 mark lost 歧义的问题。
rack 靠时间驱动即时间序而此前的方法则是在 seq space 上根据字节序关系靠启发(且看 Linux TCP 的 update scoreboard)驱动再往前则连启发都没得启发了因为根本没信息。过程的发展非常柔滑值得再次总结。
一维世界一个 hole 就堵住了sack 相当于二维平面sender 可绕到 hole 后看情况顺着往后用时间编码发送顺序就是 racksender 在一个三维看板看到的信息更详细从而可做出更精确合理的判断。
接下来的事情尚未发生(或者发生了一点点)但按照上面的推理逻辑它应该会发送。
rack 就像一架引擎只要 ack 时钟不断在丢包时亦始终有 seg 被发送这些 seg 将带回驱动引擎的进一步的 ack 流该策略非常适合高速网络发送引擎不再区分 seg 类型与 scoreboard 解耦rack 将代替任何基于重复 ack/sack 的启发算法 mark lost发送引擎维持高速运转源源不断提供数据用于实时测量。
BBR 即使用 rack 作为自己的丢包探测驱动。
随着带宽资源进一步丰富类似 BBR 的算法未竞全功。由于 TCP 没有打包直接传输 seq space 字节流导致无边界确认处理非常复杂由此带来了 undo 歧义和 ack 歧义问题不必要重传不准确的 rtt 测量和 delivery rate/cwnd 计算都是其影响。这些复杂且不精确的处理是高带宽利用率的绊脚石。
问题根源在于正向传输的 seg 是 seq space 的矢量字节流而 ack/sack 指导 cwnd 更新的只有标量 account丢失了 seg 传输顺序。换句话说sender 发送的 seg 是按序的而 sender 接收到的 ack/sack 却没有信息可还原对应 seg 的顺序。
按以往惯例既然 rack 已将时间序编码到了 sender 本地只需将该时间序同时编码到 seg 本身这样 seg 将引导 ack 反馈更丰富的 receiver 端信息该信息与 sender 的本地传输时间序进行比对就什么都有了。过年期间我曾想了一个方法详见重新设计 TCP。下面是对该文字的一点前置分析。
如果 seg 携带 1 bit 额外信息就能区分一次重传和原始 seg携带 2 bit 能区分 3 次重传和原始 seg以此类推携带 32 bit 能区分 4G 次传输(1 次原始 seg 和 4G-1 次重传)为将每次传输识别为一个不允许切割的整体需将 seq space 的字节流按序打包进固定长度的 packet以 packet 为单位传输并针对 packet 确认为每一个 packet 打标递增的 packet id 作为序列号。
这想法是个自然而然的过程。逻辑反而更简单明加入一个 packet id 便清除了启发式判断。
可 TCP 没地方编码 packet id 了况且 TCP 的标准处理流程无法要求不切割打包过程很难合并入 TCP。但 QUIC 接力了。虽然 QUIC 未必是 TCP 后继但它在 sackrack重传等方面的处理方式正是针对 TCP 的 bugfix整个过程一脉相承可理解。
后面的路怎么走我不知道也不预测但俯拾皆是的肯定依然一地鸡毛而且如果它确实发生了将它与前面的事情连起来必将在同一条线上。
浙江温州皮鞋湿下雨进水不会胖。