简单说,移动网络的特点是:
1、信道拥挤,严重受限于信号塔;用户进入/离开随意性极大,造成高延迟、高抖动
2、随着用户移动,随时可能进入信号微弱、高干扰区域
这和有线网络是完全不同。
先说说高抖动。
正常的网络,包括有线网、卫星网,延迟可能极高(比如200、300甚至1000ms),但延迟本身是稳定的。
不会说你敲了个ping命令,第一个包100ms返回,第二个700ms,第三个300ms,第四个50ms,第五个又成了1000ms……不可能这样。
正因为正常网络的延迟稳定,我们甚至可以在上面跑ntp协议,保持全球时间同步。
但移动网络不同,信号塔下人来人往,突发通讯太多;加上没有类似以太网的冲突监听等机制……总之各种原因加起来,就造成了极其不可控的、超大范围的延迟抖动现象。
注意不是简单的延迟。高延迟网络TCP是可以支持的,扩大缓冲区就行了;这里的关键是延迟它是不可预料的、不停变化的。
你可以用Linux下的tc命令,模拟一下高抖动网络下的通讯——是不是被限制的特别死、网速经常连1~2Mbps都上不去?
尤其当年的3G时代,这个问题极其突出。以至于我和这东西较劲了差不多半年。
到了4G、5G时代,随着热点大幅增加、网络容量提升、无线协议改善,这类问题得到了较大缓解,一般情况下抖动也就十几到几十毫秒左右;但覆盖不足或大流量出现时,延迟大幅抖动现象仍会发作,此时网速就会极度恶化。
比如,这就是网络状况最好时,5G网络的抖动现象。它大概以72ms为中心,抖动幅度-10~+21ms。而在高峰期,延迟最低60ms左右的同时,最高延迟可达1000ms以上,此时网速就会变得极慢,打开网页都可能有分钟级卡顿。
总之,我的研究结论是:在TCP协议框架里,遇到延迟大幅抖动问题时,现有拥塞控制算法统统无效。
因为它们全都依赖于RTT值,靠这个值控制缓冲区大小、限制网速,避免造成传输链路拥塞——更准确点说,链路数据传输速度 就等于 缓冲区大小 除以 RTT。
理论分析和实测都证明,移动网络的高延迟抖动影响下,TCP报文丢失、重传极少,甚至近乎为零;哪怕用几条UDP通道同时传输造成报文顺序错乱、引起大量的DUP ACK,真正的报文重传也极为罕见。但通讯双方滑窗大小不会增长,造成通讯速率极低,经常只有不到100KBPS。
这里面更深层的原因是:拥塞控制算法是通过“丢包”来判断“线路拥塞”的;但丢包……有时候一个包过了一分钟收到了,这是不是没丢?如果靠这种“极限状况”控制网速的话,对光纤链路,别说延迟1分钟才知道降低发包速度了,所有机器都迟1秒钟才知道拥塞,这一秒钟内发的包都够让整个互联网陷入混乱了。
因此,“丢包”的判断是和RTT相关的;但不同逻辑有不同利用方式。
比如,控制缓冲区大小,两个RTT收不到报文,这就是丢包,就要削缓冲区,起码是不能增加了,不然会把网络设备“撑爆”的——这里面,最极端的就是BBR,它对网速的稳定要求极高。因为它把缓冲区定在“RTT没有增长的最大临界点”:换句话说,只要你懂点皮毛,就知道BBR根本无法用于存在延迟抖动的网络。
但另一方面呢,比如报文重传,才刚刚1.5个RTT收不到你就重传报文,是不是就会平白增加许多压力?所以这个重传就比较保守,必须报文超时了才能重传;但“等到超时”需要的时间实在太长(可达分钟级,比如我在局域网都见过TTL没跑完但过了5分钟才到目的地的报文)。所以,就又有了个“快速重传”算法;这个算法是,连续收到3个“乱序”报文,也就是极端情况下只需两个RTT,发现接收端没有收到前面的报文,发送端就要当作报文丢失、开始重传。
但是,对于高抖动的无线网,RTT随时随地涨到两三倍,甚至偶尔出一个十倍延迟都是平常!
RTT突然暴涨,怎么办?
当然是当作丢包,降缓冲区大小、继而降低传输速率了。
那这网速还能看吗?
怎么应对?
答案是:想办法无视RTT抖动,强制扩大通讯双方滑窗大小。
比如,按照平均延迟-信道实际容量,强行把缓冲区大小设置为100M甚至1G,网速嗖嗖就上去了。
问题是,服务器内存实在太贵;你一个链路给1G缓冲,5k并发就得5T的内存!
这谁玩得起?
而且,拥塞控制算法是为了防拥塞;结果你玩命增加发送速率……是不是把整个网络的合作基础都给捣毁了?
这种协议会“击垮”所有网络上的其它协议、抢光它们的带宽,造成极大的不公平,是不可能被承认为标准协议的。
总之,从协议层动手,这个问题可以说无解——或者说,有解,但代价太大了。
但在用户层,这东西不难搞定。
最简单的,你可以玩多线程。一个线程一条链接,一条链接自动给你分配1M缓冲区,那你开1000个链接,是不是就得到了1G的缓冲区?
像BitTorrent协议一样,你自己想办法把数据分包、发送,然后把收到的报文组合起来,网速自然就上去了。
但问题是,这玩意儿太复杂了;而且,tcp协议栈被你抢了1G缓冲,回头你自己还得弄1G缓冲来复原数据……更何况,现在大部分的互联网服务器,一个用户只让你开3~10个连接,不然实在吃不消。
结果就是,你连别人服务器,就会被踢甚至被ban;你自己服务器不限制线程数,就可能被恶意访问者撑爆。
总之,对于临时的小项目,对于ftp类应用,这算是个挺方便挺好用的解决方案;但对于企业基础性通讯协议,这是个错误方案。
另一个方案是,走UDP协议,只要能发包,你就玩命发……
然后,在对端,你可以仿照TCP协议,自己检查丢包,自行恢复数据就是。
具体做法:
1、用UDP疯狂发包
2、对端接收,统计
3、根据一段时间(如1分钟)的统计值,确定链路最高速率
4、按照这个速率,配合1分钟内测得的平均RTT,设置滑窗大小(可以乘上某个系数)
5、你也可以把它实现为TCP流控算法,替换掉协议栈里面的原版
当然,这个做法的缺点是:
1、会浪费很多流量,而流量是要付费的(为了探测;当然,你也可以用实际数据探测带宽上限,我当年就是这样做的;但这就需要数据量足够大、来的足够紧凑;你慢慢握手、发送……探出来的带宽肯定偏低——当然,你也可以说,我知道带宽有多少,所以我直接配置进去。嗯,这就是我最初的方案,有效,但太不用户友好了;而且无法应对“理论带宽和实际可用带宽不匹配”或者“多人共享网络”等场景)
2、需要修改通讯双方流控协议,跑普通TCP拥塞控制协议的服务器是无法支持这种客户端的;不替换拥塞控制算法、而是实现为自定义UDP协议时,标准ftp服务端/客户端显然就无法配合了。
3、内存占用较大
4、网络条件变化后(比如到了另一个信号塔,5G切换到了3G甚至2G),程序不会自动跟随调整参数(实际上是可以应对“网络条件变差”问题,但应对不了“网络条件变好”问题;后者可以“每若干分钟强制重新探测”方式解决)。
如果说“高抖动”想想办法还是可以解决的话,网络信号变弱无解。
这是你的手机和信号塔之间的事——我知道有很多种协议,甚至可以用卫星通讯使用的、带自纠错的、极抗干扰的、可用于极微弱信号的通讯协议,它们当然可以大幅改善这个问题……
但问题是,这是你的手机4G/5G芯片和信号塔之间的事,是4G/5G协议要解决的问题。它们不解决——或者说,它们选择牺牲弱信号条件下的通讯速率/可靠性,换来更高速的链路、更低的延迟、更低的实现复杂性——我们只能接受。
当然,虽然我们不可能从根本上解决问题;但在体验上做一定的改善,这还是能办到的。
这就是 大宽宽 提到的“小通道”,也就是尽量利用好每一个成功传输的报文,不要轻易的断开链接、重新开始……
当然,如你所见,所有这些改动基本都涉及了协议层——要么改动内核,要么在用户态实现;但都得动协议层。
除非服务端/客户端都掌握在你手里,否则,这些改动都不是个人/组织能办到的,只能想办法通过国际组织协商、形成新的RFC并最终变成整个行业的新规范。
——这也是我们这些传统的老家伙们不喜欢搞基于web的开发,而是喜欢自己定二进制协议、玩Qt玩C/C++的根本原因。
——只有它们,才能突破这些限制,才不必依赖于乌龟一样戳几年都不肯挪一步的国际组织。
原文链接:https://www.zhihu.com/question/294088058