上周文章转发朋友圈后,我补充了个评论,我不晓得为什么RDMA底层传输协议还在复用TCP那一套,只是为了重用而重用吗?完全可以重写的协议还在GBN,还在将SACK作为GBN的优化,沿着老路重走一遍…请用QUIC吧,但这话不能对RDMA说,它用不了QUIC,转念一想,广域网传输也不是非用QUIC替代TCP,剥离导致TCP性能缺陷的因素,学着QUIC的样子改掉它,依然留着TCP的躯壳,作为TCP-ng,岂不是鸠占鹊巢更美妙?抛砖引玉,写下本文。
TCP吞吐性能已经经不住优化了,因为它的固有缺陷来自于它的原始设计。
TCP术语太多,滑动窗口,慢启动,拥塞避免,快速重传,快速恢复,超时重传,几乎任何资料都不可避免地以上述术语为线索,TCP的复杂性,无论是协议还是实现,均来自于此。
如此复杂的协议,竟然仅仅可用(幸亏AIMD也能让它收敛),毫无效率可言,够了!
最初的设计中,TCP只有积累确认,为让TCP滑动窗口持续滑动,必须以dupACK识别丢包,然后GBN(go-back-N),其中N指示重传的数量,这是一个简洁的设计,但低效。TCP原始设计的状态机为:
以Linux TCP实现为例,关于这个状态机的各状态描述如下:
/* Linux NewReno/SACK/ECN state machine.
* --------------------------------------
*
* "Open" Normal state, no dubious events, fast path.
* "Disorder" In all the respects it is "Open",
* but requires a bit more attention. It is entered when
* we see some SACKs or dupacks. It is split of "Open"
* mainly to move some processing from fast path to slow one.
* "CWR" CWND was reduced due to some Congestion Notification event.
* It can be ECN, ICMP source quench, local device congestion.
* "Recovery" CWND was reduced, we are fast-retransmitting.
* "Loss" CWND was reduced due to RTO timeout or SACK reneging.
...
*/
自SACK被引入直到RACK/BBR,围绕TCP原始设计的优化终于再也无法满足需求,并越来越成为吞吐性能提升的障碍。成为性能提升阻碍的原因不外乎:
是时候拿掉这个状态机了,就像赶走牵引蒸汽机的马匹一样。
为此需要设计一个新协议。但evolution比revolution更容易被接受。新协议不是QUIC,而是TCP-ng。重用TCP协议头是它的最大特征。
大致介绍一下TCP-ng对TCP做了哪些扩展以及如何做的。
首先,将TCP协议中不合时宜的机制剥离,如果不晓得哪些是不合时宜的,看看QUIC的方式:
以上措施之外,需要增加的新机制:
综上,TCP-ng需要做的是:
幸运的是,以Linux TCP为例,需要修改的地方非常少,分别来看就是:
取消reneging的风险是,乱序太多或RTT太抖动的弱网环境,收端内存占用会很大,空洞长时间无法填补,严重情况下会出现类DDoS症状。
取消拥塞状态机,TCP的复杂性就砍掉了大部分,背后的思想很简单:
Linux TCP实现了RACK之后,其实已经很完美了,启用RACK后,完全依赖RACK来mark lost,而不再使用oneshot的tcp_update_scoreboard,这表明Linux RACK-Based TCP已经完全基于时间序发包了,此前的scoreboard已经成了历史。
核心问题SACK段数量受限以及时间序发送问题已经解决,TCP-ng便得以快速滑动窗口,HoL得到极大缓解,剩下的就是细枝末节了。
TCP-ng的不足是,ACK依然无法和transmit相对应,造成DSACK误判,undo效率降低。
以Linux TCP实现为例,当前的undo操作依赖于:
tp->undo_retrans == 0
该字段在收到DSACK时递减。由于无法区分一次重传报文带来的DSACK还是两次重传中其中一次带来的DSACK,有可能会造成undo失败并影响重传效率:
好在packets_out是准的,下一轮packets_out清零之时可顺便清除retrans_out,但迟到的DSACK会带来warning。所以我建议在RTO时无条件清除retrans_out。
彻底解决这个问题需要增加类似QUIC的packet id字段。为此,TCP-ng2我这么建议:
undo只是降低一些重传率,对整体的带宽利用率提高影响有限,且机制复杂,为了不引入更多复杂性从而引发额外问题,不建议实现undo。
…
TCP-ng重用TCP协议头及其大多数原有处理逻辑,其原因是:
TCP有诸多缺陷,设计一个针对性弥补这些缺陷的新协议并不是一件很难的事,但在工程和生态视角,将新协议部署落地就太难了,一个很简单的例子就是IPv6。
IPv4的问题众所周知,IPv6逐一针对性解决,可是IPv6的部署之路却很艰难。
与之类似,QUIC也一样。
妇孺皆知的是QUIC对状态防火墙不友好。何止状态防火墙,QUIC对所有途径的运营商设备都不友好:
…
所以,我选择不修改TCP协议头的方式,所有的设备还是和原来一样运作,新协议完全透明经过。再也没有人说新协议不友好了。
下面是我之前写的一篇欺骗网络设备的内核插件,它可以将UDP假装成TCP:https://github.com/marywangran/pseudotcp-tunnel
最后,演示一个扩展TCP协议的实际例子。
当前主机性能相比1980年代已经大幅提升,进程容量大大增加,为提升主机搜索可用端口号的效率(connect/bind中的逻辑),需要扩展TCP端口号位宽。下面介绍如何不改变TCP协议头而将端口号扩展成24位:
浙江温州皮鞋湿,下雨进水不会胖。