独立双(N)拥塞窗口的TCP单边加速思想

让TCP以流水线方式工作靠谱吗
也许你听说过MPTCP,也许你听过P2P下载是多么的天下人为我而我负天下人。
        如果我能将一个TCP流拆分成多个TCP流,理论上来讲传输速度会有很大的提升,因为TCP拥塞控制算法是必须携带公平性收敛特征的(不然paper不会通过…),TCP反馈系统会为每一个加入的流分配一张船票,反馈系统不会管你们几个是不是一伙儿的,它只按人头计数,不会分组,这样就有很多好玩的事情可以做了。
        我平时比较喜欢跟一些非工作关系的同行做技术交流,随便找个地方,或者就是打电话,大多是下班后,这样会显得比较轻松,惬意并且会大有收获,一般而言,交流沟通都是双向的,如果你老是听而自己不说的话,慢慢人家就会觉得你无非就是一个偷技术者或者说学生气未泯的书生,那种天天盯着别人在干嘛,生怕人家懂得太多把自己甩开的那种学生…上周末我跟前同事聊了个把小时,他目前在构思一个TCP虚拟流的概念,这也正是我比较感兴趣的,不谋而合。
        一个超级简单的思路,那就是通过捆绑多个流来达到提速的目的,这个技术无关任何公司和个人的版权或者专利,分享出来也无伤大雅,时间过去了一周,天天做工没时间,又到了周末半夜三点起床的清爽时刻,总结一下,下面是关于这个技术交流的备忘。
        首先简单说一下公平性以及公平性的指标

1.TCP的公平性

TCP的公平性指的是资源分配的公平,对于TCP而言,说的就是指带宽分配的公平。业内有一个公平性指数,如下所示:

因此抢占式算法无法得到认可,必须偷偷摸摸的默默搞。
        理论上来讲,没有任何算法可以在保证公平性的前提下可以获得比其它流更多的带宽,之所以也有像华夏创新这种,完全是建立在“其它算法不够好”这个基础之上打了擦边球。bic算法在某种特定场景下不是也有抢占性吗界专注了三十年也没有一个完美的算法,因此每个公司甚至个人都可以提出一个“在路上”的算法,注意,能被认可的在路上的算法,这是说那种真正考虑了公平性但打了擦边球的算法,业界并没有规定完美的公平,而只是规定了“可用的”公平,至于那种一个数据发两遍等诸如此类的方式,永远都是不登大雅之堂的。
        除了那些自私的拥塞控制算法无法被接纳之外,它们自身也将面临算法失败巨大代价。在我们国家,很难想象一个普通公司里会有数学家,也不敢想象如此浮躁的环境可以让一个公司或者个人坚持一个方向数十年,不必说几乎所有的自私拥塞算法已经排除了公平性,它们连自身的“效率-流量”都无法权衡,完全就是以量取胜,这也完全符合我们骨子里的本源,然而运营商那里的流量是要买单的,谁买单那就是另一个话题了。
        所以说,与其说费力去开发一个最终还可能不成功的“在路上”的拥塞控制算法,不如说简单一点,在算法之上设计一个新协议。

2.多个流之间的公平性

目前Linux系统默认的拥塞控制算法是Cubic,它工作地非常好,公平性表现也很出色,如果我相信它的表现,那么如果我再可以把一个TCP流变成两个或者N个TCP流,那么这
N个TCP流将公平地分享到达接收端的带宽。为了让讨论更加直观,我将模型简化。
        假设节点A为发送端,节点B为接收端,中间链路总带宽为W,当前链路上有m个TCP流共享带宽W,按照公平性,每个TCP流的带宽为W/m,作为其中一员的A-tcp-B,自然它的带宽也是W/m,现在如果我将该TCP流拆分了,拆成了n个流,那么当前链路上存在的TCP流的数量变成了m+n个,按照公平性,每一个流的带宽为W/(m+n),其中n*(W/(m+n))的带宽属于我。现在证明n*(W/(m+n))>W/m
        程序员不是数学家,如果能有直观的东西就不想去分类讨论,上述结论的证明归为以下:

直观上看,就好像揭手机贴膜一样,xy都是大于x+y的,但是注意边缘的颜色,有部分xy是小于x+y的,我们放大来看:

由于x和y的对称性,这样不等比缩放的意义在于,固定一个变量,观察另一个变量对它影响的长期趋势。为什么要这么麻烦去比较两个曲面的关系,直接用z=x+y-x*y不是更好吗0比较。原因还是那句话,不直观。因为我们不能直观地判定曲面的凸凹,万一在0点附近做波浪状怎么办,用偏导数可以搞定,但那又回退到了数学推导。事实上,我们把z=0这个曲面画出来再与z=x+y-x*y比较可能会更直观:

事实上,MPTCP以及迅雷所依托的就是以上这个加速比!
        在实际的实现中,n往往受制于系统的开销不能太大,10量级即可看到效果,在我的这个双窗口单边方案中,我自然选择2即可。

3.TCP流组间公平性

上面我们提到,MPTCP以及迅雷都采用了捆绑流的方式来获得最佳的加速比,这两种方案又有不同,MPTCP是在TCP层将流进行捆绑,而迅雷之类的P2P软件则是直接将应用数据进行了拆分,直接在应用层创建了多个流,哪一种方式更好呢实无论使用哪种方式,都涉及到了对原有的应用程序或者协议栈进行修改,大部分场景下无法推动,但是它们的这种做法却直接给了我们一些思路。
        它们之所以可以这么做,是由于TCP虽然规定了流与流之间必须公平共享带宽,但是并没有规定流与流之间协作的细节。我们知道,竞争和互助永远都是任何事情的两个面,一直在相互转换。
        类比进程调度的一些原理,近些年来,由于容器,轻虚拟化等技术的兴起,组调度策略也变得越来越复杂。早期的时候,调度的公平性是在进程与进程之间展开的,后来由于进程少的会话会被进程多的会话饿死之类,就引入了类似分组调度。最终,进程调度完全变成了一个分层的机制,谁也无法钻空子了。目前TCP拥塞控制还不是一个分层的机制,起码在协议规范的层面上没有,这就留下了很多空子。
        但是,目前的分层分组拥塞控制机制在TCP之外却普遍存在,比如路由器交换机,很多实现了加权公平队列,它们可以根据源IP/目标IP对进行分组,也可以通过目标端口进行分组,甚至可以通过TCP的初始序列 指纹进行分组,由此,只要来自相同的主机,去往相同的服务器的所有TCP流,可能就会被作为一个流来对待,这就为多流捆绑的TCP加速实现造成了很多的障碍。
        不过不管怎么说,我们并不是需要完美的N加速比,而是一个可用的N-加速比,不是吗

4.双(N)拥塞窗口TCP单边加速

前面都是铺垫,分别描述了:
1).为什么TCP的新拥塞控制算法很难开发因为效率和公平必须权衡但却很难权衡。
2).为什么多流捆绑可以获得收益完全基于数学推导,gnuplot非常好用且直观。
3).为什么多流捆版可以实行因为TCP没有规定不能这么做。
4).为什么即便是多流捆绑也很难获得N加速比因为中间设备会进行流量分组调度。
5).为什么成型的方案无法直接使用因为MPTCP是双边方案,要改双方协议栈,P2P是应用层方案,太复杂。
最终我们只剩下了一个问题,那就是:
6).基于上述1-5,怎么实现我们自己的多流捆绑的TCP加速方案/strong>
我们知道,TCP是按序发送的,在拥塞窗口内(我们暂且忽略对端通告窗口的限制)依照序列 每次递增1依次发送,为了实现两个拥塞窗口,我们引入“TCP虚拟流”和“虚拟序列”的概念,虚拟序列将不再保证序列 在发送中每次递增1,而是递增w,w的值由“平滑参数”控制,如下图所示:

咋一看,好像这个思想其实就是本来应该发x个数据的,现在发x+a个数据,这个和Reno,Bic,Cubic在一个窗口内多发一些数据有什么区别案在于这里的方法中x和a是独立的,二者均受到其自身发送数据的ACK以及RTT的独立驱动。还记得那张“公平-效率”坐标系吗第三次把那个图贴如下:

不过我们知道端到端的延时包括两大部分,包括主机延时和 络延时,我们说的这个“发送时间一样”指的是主机延时一样,相对于 络延时,主机延时会显得微不足道。这样,主机延时可以忽略,对于 络来讲,可以看做是两个TCP流同时发送。如果构建N个TCP虚拟流的话,最终的加速比会接近但不等于N,毕竟主机时延是不可忽略的。
        由上图可以看出,如果TCP按照标准流序列发送,假设拥塞窗口是10,那么它只能发出去10个段,然而创建了5个TCP虚拟流之后,每一个虚拟流的拥塞窗口都是独立计算的,这里n就是5,而m可能是一个数以万计的数值,此时每一个虚拟流计算出的拥塞窗口应该接近于10,这就达到了N加速比。
串行TCP虚拟流还有两个额外的作用,那就是:

1.可以平滑 络设备整形的影响

如果是标准的TCP流的发送,即便的连续发送的段,到了接收端也可能被整形成阵发的到达的形式,这个我也在多篇文章中有所提及。加入多个TCP虚拟流之后,可以有效弥补整形之间的空闲时隙,但是有个前提,那就是,整形设备必须不依赖五元组进行整形,虽然很少见,导致这个有利的副作用很少有人被惠及,然而世事总归聊胜于无。

2.降低延迟ACK导致的ACK时钟的慢拍问题

如果TCP数据接收端启用了延迟ACK,这其实也算是一种整形方式,不同的是,整形针对的不是数据,而是ACK,然而被整形的ACK流会反过来作用于数据流。使用多个TCP虚拟流在接收端看来并无法识别这多个虚拟流,客户端感知到的只是数据的到达更加快了,更加平滑了,因此ACK更容易被触发了。

TCP虚拟流的并行加速比

在超高速 络中,有一条优化原则是降低时延!此时串行TCP虚拟流就显得不合适了,我们更希望的是两个或者多个TCP虚拟流同时被发送,MPTCP就是这么做的,但是由于我们是一个单边加速方案,没有接收端会配合我们去重组多个流为一个流,考虑到 络的无序性,如果我们在多个CPU核心同时并行发送以下的TCP虚拟流:
虚拟流1:1,3,5,7,9,11
虚拟流2:2,4,6,8,10,12
考虑到 卡调度同样不保证时序,接收端存在很大的乱序收包的概率,而这对于接收端的接收缓存是一个极大的考验!只要有一个空洞未被补全,数据就无法向上层交付!因此解决方案就是同步并行发送,每个TCP虚拟流在不同CPU核心上的发送时机与其前一个TCP虚拟流的发送错开一个固定的间隔时隙:

独立双(N)拥塞窗口的TCP单边加速思想

这样我们就有了一个发送矩阵,在横向上,通过多个TCP虚拟流来获得接近于N的加速比,在纵向上通过固定间隔的并行发送最大限度地降低了主机发送延时。这个矩形的面积可以看作是时间和TCP虚拟流数量N的乘积,是一个数据总量的度量,类似时延带宽乘积,在我们希望传输时间更短(大多数TCP加速的目标)的情况下,为了保持面积不变,办法就是创造N加速比,即构建多个TCP虚拟流,将矩形给拉宽!这个矩形越宽,节省的主机处理时间就越多。

        由上我们知道了TCP虚拟流主要是提高了带宽的利用率以及避免了拥塞误判,最后在高速 络上,它还可以减少主机延时。

关于实现的Tips

说实话这个实现起来比抄一个拥塞控制算法简单多了,而且在2014年迷茫的时间跟经理的一次谈话也让我明白,善于组装东西的人可能比善于制作构件的人更加优秀,但大多数情况下会被人觉得没有制作构件更酷。
        这个TCP虚拟流明显就是组装行为,没啥技术含量,对于Linux协议栈而言,只需要把tcp_sock结构体里面的跟拥塞控制相关的字段改成数组即可,比如snd_cwnd就改成snd_cwnd[2]…就这样。然后独立地根据ACK应答来去控制这些变量值的变化,可能还需要为TCP控制块结构体加一个虚拟序 字段,保存该skb目前由哪个虚拟流所持有。我就不明白为什么有人会觉得这样是“乱改内核”!
        不想改内核也可以,那就用Netfilter的HOOK来做,该IP层HOOK完全接管TCP层传下来的所有数据,直接回复ACK给上层,造成一种数据已经送达的假象,事实上数据只是被缓存在这个HOOK的缓冲区里而已,每一个CPU核心启动一个内核线程来独立处理拥塞窗口的AIMD,然后各个虚拟流自行从缓冲区里取数据并发送之。很简单的一个模型,这个Netfilter HOOK事实上只是一个代理,把这个HOOK单独抽出来做到一台设备里,这就是可以卖钱的加速 关了,而且真的可以卖钱!但是如果你不把这个装到盒子里,而只是作为一个温州老板从卖皮鞋改成卖内核模块,根本无法卖钱,或者说,起码很难。

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2016年5月26日
下一篇 2016年6月1日

相关推荐