上周从摩尔定律的角度写了一篇关于串行到并行的辩证 摩尔定律与并行多路径传输,本周抛开摩尔定律,从事实本身出发继续扯从串行和并行的架构转变。先看下列辩证:
- 单服务台系统,任务排入单队列,服务台需视任务轻重缓急调度任务;
- 多服务台系统,任务有多个队列可排入,应视各队列长短自选服务台;
万恶归于时间,在单核系统,影响吞吐的核心要素就是从千万 task 中找到最合适的那一个所花的时间,这是传统算法的要素,例如二分查找比遍历查找更快;而在多核系统中,task 和处理核心倒转,对应的要素就是从诸多核心中选择最合适的那一个所花的时间,每一个单核心的队列被被 sibling 均分,理论上不会太长,类似 Hash 表,多核系统下,“从千万 task 中找到最合适的那一个” 属于次要。
多核相对单核,类似 GPU 之于 CPU,GPU 很简单,只关注阵列结构,而 CPU 复杂到要用算法调教。
多核为达到类似 Hash 效果,在软件设计上,task 粒度必须足够小且互相无依赖,若非如此,假设存在一个前后相关联的 task 序列选择了同一个处理核,该 “大象 task” 势必引起该处理核上其它标准大小的 “老鼠 task” 饥饿,但这种相互依赖的串行 task 设计(比如 MPTCP)非常常见,无疑是受单核系统串行理念的思维定势驱使,该定势下,只是 “将串行软件适配到了多核系统”,而非 “为多核系统设计串行软件”。
这个适配过程也简单,Linux 内核就是个大实例,它只是在可能被分配到不同 CPU 上执行的代码区间加了 lock 来互斥,但理论上在真正的并行系统,lock 本身就不该存在,它只是将单 CPU 上的程序适配到多 CPU 环境的适配方案,换句话说,源自传统的 Unix/Linux,Windows 等内核均是 “适配” 内核,而非天然的多 CPU 并行内核。
串行硬件适合大粒度大软件,忌小粒度独立软件,避免全是查找时间;并行硬件适合无互相依赖的小粒度软件,忌大软件,避免饥饿,亦忌相互依赖的小软件,避免同步。要之,硬件结构决定软件行为,相似才相容。
铺垫之后,再拐回 TCP 传输协议相关联的话题。
基于上述,审查 TCP 序列号。简言之,只有相互依赖的片段才需要序列号,独立的消息不需要序列号;换言之,序列号将 TCP 绑成了一条 stream,这种宏观上的 “大粒度 stream”,微观上 “相互依赖的 segment” 无法并行分发,并不适合多核心系统,阻碍了并行多路径传输,基于 TCP 思想设计的一切传输协议均如是,有一个是一个,包括不限于 QUIC,MPTCP,它们并没有修正 TCP 的串行本质。
然而原始数据毕竟是一个大整体,若不用序列号将拆分传输的小 segment 串联起来,如何再拼接成大整体,用序列号标记想必是标准做法,从大件物流到 TCP 传输,皆如是。TCP 的掣肘何在?
TCP 序列号的单处理器串行本质体现在 TCP 在单核接口被终结,即所有数据在送到读接口前必须重排序,而这动作在单核完成。这只是 “端到端接口” 而非真正 “端到端传输”,后者从源数据地址开始,于目标数据地址结束。
从端到端的数据源开始,直接以独立,无重叠的数据地址作传输控制中并行分发标记,可实现真正并行传输。以原子变量(如典型的全局,本地 version)管理固定窗口滑动,而多路径时延微抖动天然打散严格同步,原子变量操作更高效。
颇似 RDMA 语义,但很遗憾,RDMA 的实现还是被 TCP 的影子笼罩着。
再说说并行传输的拥塞控制。
传统拥塞控制,无论 inflight-based 还是 rate-based,均基于单路径假设,其算法逻辑也均由单路径特征指标获得,比如计算 BDP 或 delivery rate 均使用 RTT 做输入,而 RTT 是一个典型的单路径度量,RTT 变大则拥塞,RTT 恢复到 RTprop 则拥塞解除。单路径串行传输符合典型的的广义的电学串联分压原则,串联 buffer 大小与其贡献时延成正比。但由于 TCP/IP 网络的逐跳特征,sender 几乎不可能获知 buffer 时延的分配情况,因此,单路径拥塞控制公平性和效率天生矛盾。
并行多路径传输的拥塞控制则完全相反,多条路径传输符合典型的广义电学并联分流原则,queuing 时延越大,调度 packet 越少。以 packet spray 为例,哪条路径拥塞了,只需将本该调度到该路径的 packet 分发到其它路径,比如 RTT 最接近其 RTprop 的路径。这简单的策略可实现多流共享多路径的天然公平性,因为它们总是在多条路径 spray 相同比例的负载,公平和效率天然兼得:
并行多路径传输存在两层拥塞控制机制,以 rate-based cc 为例:
- 全局速率控制,与单路径基于 RTT 计算 rate 不同,多路径不能用 RTT,要选取固定时间 D,计算 rate = data_acked/D;
- 路径调度控制,若 rate 反应拥塞,将 queuing delay 偏移 minrtt 最大的路径负载调度到最接近 minrtt 的路径,而不是直接减少总负载;
inflight-based cc 类似,AIMD 只降低单路径负载,却不降低总量,只有在没有任何路径可接纳拥塞负载时,总量才下降。这很像现实调配中的 “晃荡” 动作,一碗水端平,均匀摇晃,收敛到公平。
最后,以 TCP/IP 哲学之 Postel 定律 “宽进严出” 结束本文。
该原则最早见于 RFC793:
be conservative in what you do, be liberal in what you accept from others.
TCP 的串行处理本质和网络的并行不确定性天然矛盾,该原则用 “算法” 换取了松弛。早期 TCP 运行在低效处理器和网络上,receiver 收到乱序报文即丢弃,sender 超时 GBN,这种简单粗暴的实现非常迎合时局。随着处理器,带宽性能提升,资源更加倾向于集约化利用而不是浪费,这有悖于直觉,但要理解,浪费的代价也在增加。TCP 标准的 Postel 原则意味着收到 “不应该的” 乱序报文也要缓存起来,以 SACK 小心翼翼通知 sender,是为 RFC2018。
整个原则是在说,不确定性在整个过程占比越大,越要对它宽容,否则就要付出更大的资源浪费为代价,而交换这个收益的,就是算法,比如你要安排一个高效的 SACK 乱序重组算法,正因如此,在带宽大到一定程度后,Linux TCP 的 RTX queue 从 list 换成了 rbtree。
在并行多路径传输中,Postel 定律弱化了。如前述,并行处理的首要原则是相似相溶,以独立 packet 为传输单元,无串行依赖,无保序约束,不确定性低,它甚至就是确定的,某个 packet 在某条路径被传输,它要么到达,要么没到达。基于此,可推导出并行多路径传输一定是弱算法协议,这和上篇文章以时间空间的倒转,即 “串行处理在时间维度积累,重视算法,并行处理在空间维度展开,重视结构” 推导的结论殊途同归。
浙江温州皮鞋湿,下雨进水不会胖。