TCP吞吐性能优化的吐槽与拯救

Posted dog250

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP吞吐性能优化的吐槽与拯救相关的知识,希望对你有一定的参考价值。

上周文章转发朋友圈后,我补充了个评论,我不晓得为什么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原始设计的优化终于再也无法满足需求,并越来越成为吞吐性能提升的障碍。成为性能提升阻碍的原因不外乎:

  • 状态机依然根据原始设计转换,在非Open状态接管拥塞控制算法。

是时候拿掉这个状态机了,就像赶走牵引蒸汽机的马匹一样。

为此需要设计一个新协议。但evolution比revolution更容易被接受。新协议不是QUIC,而是TCP-ng。重用TCP协议头是它的最大特征。

大致介绍一下TCP-ng对TCP做了哪些扩展以及如何做的。

首先,将TCP协议中不合时宜的机制剥离,如果不晓得哪些是不合时宜的,看看QUIC的方式:

  • 收端不再支持reneging,内存已不再是瓶颈,手机内存动辄10G+。
  • SACK段数量不再受限,带宽已足够,不必再压缩协议头空间。
  • end-to-end越来越普遍得使用TLV而不再青睐定长的短协议头。

以上措施之外,需要增加的新机制:

  • 以发送顺序而不是序列号标识传输,分离end-to-end和传输逻辑。
  • 完备的不依赖TCP拥塞状态机的拥塞控制算法。

综上,TCP-ng需要做的是:

  • 取消reneging。QUIC不允许reneging。
  • 完全的SACK。QUIC即完全SACK。
  • 独立的传输。QUIC中的packet ID
  • 解放拥塞控制算法。

幸运的是,以Linux TCP为例,需要修改的地方非常少,分别来看就是:

  • 收端和发端删除reneging相关的代码。
  • 收端将所有SACK段放入payload。发端用payload解析出的SACK段扫描retrans rbtree。
  • 发送端实现完整的RACK,发送时不区分new data和retrans。
  • 取消拥塞状态机,cc算法根据采集带宽来自己决策。

取消reneging的风险是,乱序太多或RTT太抖动的弱网环境,收端内存占用会很大,空洞长时间无法填补,严重情况下会出现类DDoS症状。

取消拥塞状态机,TCP的复杂性就砍掉了大部分,背后的思想很简单:

  • 拥塞一定会带来测量吞吐及RTT的变化,与丢包无关。
  • 丢包一定可以靠RACK重传恢复。如下图:

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失败并影响重传效率:

  • 进入重传状态后,只要之前有丢失的重传依然未决,本次undo就会失败。
  • retrans_out占据inflight份额,重传窗口无法张开。

好在packets_out是准的,下一轮packets_out清零之时可顺便清除retrans_out,但迟到的DSACK会带来warning。所以我建议在RTO时无条件清除retrans_out。

彻底解决这个问题需要增加类似QUIC的packet id字段。为此,TCP-ng2我这么建议:

  • 将SACK option分裂为3个type:type1为packet number(8字节,必选); type2为SACK(in payload);type3为stream ID(4字节,可优化接收负载均衡,或自研网卡优化RSS)
  • ACK中SACK段格式为begin data-seq:begin packet-seq~end data-seq:end packet-seq的映射段,用于发端检测并对应DSACK。
  • data seq和packet seq全部采用字节单位,不影响TSO/GSO,GRO/LRO等offload机制的运行。

undo只是降低一些重传率,对整体的带宽利用率提高影响有限,且机制复杂,为了不引入更多复杂性从而引发额外问题,不建议实现undo。

TCP-ng重用TCP协议头及其大多数原有处理逻辑,其原因是:

  • 主观原因:我太懒惰且编程水平太差,重写一个新协议虽好看但beyond my ability。
  • 客观原因:TCP-ng兼容性极好。

TCP有诸多缺陷,设计一个针对性弥补这些缺陷的新协议并不是一件很难的事,但在工程和生态视角,将新协议部署落地就太难了,一个很简单的例子就是IPv6。

IPv4的问题众所周知,IPv6逐一针对性解决,可是IPv6的部署之路却很艰难。

与之类似,QUIC也一样。

妇孺皆知的是QUIC对状态防火墙不友好。何止状态防火墙,QUIC对所有途径的运营商设备都不友好:

  • UDP没有状态机,设备无法跟踪连接,便无法精确实施ACL,计费等关键动作。
  • QUIC over UDP,作为应用层协议迄今没有统一标准,且变化多端,设备商的固化升级成本太高。
  • QUIC协议并非没有生态,而是各大厂商自立门户,生态太多,无法协调。
  • QUIC用户态的cc算法很容易被不懂拥塞控制为何物的人乱搞,严重破坏互联网公平。

所以,我选择不修改TCP协议头的方式,所有的设备还是和原来一样运作,新协议完全透明经过。再也没有人说新协议不友好了。

下面是我之前写的一篇欺骗网络设备的内核插件,它可以将UDP假装成TCP:https://github.com/marywangran/pseudotcp-tunnel

最后,演示一个扩展TCP协议的实际例子。

当前主机性能相比1980年代已经大幅提升,进程容量大大增加,为提升主机搜索可用端口号的效率(connect/bind中的逻辑),需要扩展TCP端口号位宽。下面介绍如何不改变TCP协议头而将端口号扩展成24位:

  • 新端口号的低16位用TCP协议头中的端口号字段表示。
  • 新端口号的高8位用TCP协议头中2个新的8位自定义option表示。
  • 发送端自行拼接端口号组装数据包。
  • 接收端解封装端口号多路复用。

浙江温州皮鞋湿,下雨进水不会胖。

以上是关于TCP吞吐性能优化的吐槽与拯救的主要内容,如果未能解决你的问题,请参考以下文章

接口性能优化技巧,干掉慢代码!

Java接口性能优化技巧,都是干货...

TCP吞吐性能缺陷的根源

深夜里的自我吐槽与自我展望

Java:如何去优雅地优化接口

关于山东大学(青岛)的吐槽