我来说个大家没有讲到的方面吧……
当然,链路带宽、滑动窗口这些都对,甚至可能是主因。
但还有一个不太能想到的问题是,其实服务器不一定只有一台……
但是每个TCP连接通常只能连到确定的一台服务器。你多开几个线程,其实可能多了几台服务器给你服务……
这也是我喜欢客户多开几个线程下载的原因……
这要根据网络实际情况分别讨论。胡乱归咎于滑动窗口是不负责任的。
根据我的经验,这个问题起码要分六个大类分别讨论——这六大类讨论完毕,还得再说说流控算法的公平性问题。
1、低延迟网络
低延迟网络指的是相对于网络最高带宽,信息传输延迟很低、不至于影响数据最高传输率的网络。
一般来说,这类网络无需考虑滑窗算法问题。
这是因为,除非局域网内部之类点对点传输的场景,一旦经过了路由器之类网络设备,数据传输就一定会被途中每个设备的缓冲区影响;一旦触发流控就说明缓冲区已经爆了、主动丢包了;那么此时“滑窗大小主动减半然后线性增长”其实起了“等待缓冲区清空”的作用,并不会严重影响设备吞吐率。
而点对点传输场景呢,正常来说链路层都有个码率协商过程;一旦协商成功,那就是“只要你能放到线路上对方就一定能接的住”,不涉及缓冲区也不会丢包(除非线路稳定性不足)——此时TCP的流控基本不起作用,设备压根就是一直压着线路最高传输速率跑。
1.1 无限速
实际网络上,你往往需要和其他用户共用一段链路(共用路由、共用小区光纤等等);此时多线程下载主要和资源竞争相关。
无限速条件下,你要和其他用户竞争使用同一条链路。当你传输信息时,其他用户就不得不等待;其他用户传输时,你也不得不等待。
大多情况下,路由器或其他智能网络设备公平调度每一条链路;当你多线程下载时,你名下的链路数就会增多,于是你得到调度的几率就按照你开的链路数线性增加——比如十个用户十条链路全部跑满的话,每个用户可以占有线路容量的1/10;但如果你开了三条链路,那么你一个人就占用了线路容量的3/12。这样你的下载速度自然就高了很多。
但这种情况会引起恶性竞争,使得每个人都盲目的多开链路,很快耗尽网络设备资源。
1.2 有限速
实际网络上可能存在很多限速策略。不同的限速策略、限速位置也会影响多线程下载的种种表现。
比如,服务器可能针对每用户限速;那么你开一百条链路也无法增加下载速度。
但这个策略太过复杂,实际上很难做到;所以网络供应商往往会为每个链接限速。那么你开N个链接自然就得到了N倍下载速率——但这实际上使得服务器为你一个人用了N倍的缓冲区空间,这是极其招人恨的。
因此,现在大多下载站限制每IP连接数,一般不允许超过3个。多开连接甚至可能被ban掉ip。
类似的,路由器也可以针对每个用户限速(此时多开连接并不增加下载速率);当你同时看视频并下载大文件时,这两个链接也会相互挤占带宽(这种挤占可能发生在你的PC终端以及途中的每个节点上)——所以当你BT下载同时看视频/玩游戏就可能卡顿。
总之,大多情况下,多开连接的确能挤占到更多的线路带宽;这或许就是很多人盲目多开连接搞多线程下载的原因所在——也是很多答案盲目的把“多开连接提高下载速率”归结于“滑动窗口”这个基础算法的原因。
但是,多开连接未必真能多挤占带宽。事实上,因为多开连接会过快消耗服务器资源,互联网工程师们反而花了很大精力去限制用户“借助多开连接得利”。
2、高延迟网络
随着网速提高以及跨国通讯业务增加,很多网络的延迟已经足以影响链路数据传输速率上限了。
这是因为,TCP需要借助“滑窗”这个缓冲区里面的数据实现重传、从而保证可靠通讯。具体说就是发送端必须收到接收端的确认报文(ACK)后,才敢废弃缓冲区里相应的数据。
设滑动窗口最大值为W字节,报文往返时间(发送端发送数据到收到对端返回的ACK报文所需的时间)为RTT,则数据最大传输率就等于 W/RTT。
对高延迟网络,RTT数值很大;那么如果W不够大,链路最大传输率就不可能高。
2.1 旧TCP协议的窗口大小
“滑窗”是要占用内存的;由于历史原因,TCP协议认为16位表示滑窗大小就足够用了。于是最大滑窗大小就只有64K字节。
在新兴的千兆光纤网(因为传输速率太高哪怕延迟很低也必须很大的滑动窗口才能跑满带宽上限)以及跨海电缆(延迟太高)上,这个大小实在太小了。因此后来不得不修改了协议,允许最高两个G的滑动窗口。
如果你用了很陈旧的操作系统的话,滑窗大小可能就有64K上限限制。这种情况下每条链路允许的最大传输率是远远跑不满线路容量的;此时多开链路相当于变相增大滑动窗口,传输速率自然增加。
2.2 新TCP协议的滑动窗口大小
当你的设备/操作系统的TCP协议栈不太旧时,虽然因为RTT过大导致滑动窗口大小增加不够快,但往往也只需若干秒就能跑到线路允许速率上限。此时情况和“低延迟网络”差别不大。
3、网络抖动
长距离数据传输时,尤其如3G/4G这样的公用无线信道很容易受到各种干扰,使得网络延迟(RTT)抖动。
3.1 低抖动网络
低抖动网络可按情形1、2讨论,不再重复。
3.2 高抖动网络
高抖动网络会严重影响流控算法,使得TCP滑窗大小不能按照预期增长。
当年我搞互联网链路聚合时做过实验,实验证明RTT在100~300ms间抖动时,TCP滑窗会很快停止增长,使得链路传输速率上限极低。这种抖动甚至比少量的丢包更能影响传输率(报文乱序造成大量DUP ACK时,链路传输率也会小幅下降,但影响也没有RTT剧烈抖动大)。
这种网络上,多开连接相当于人为强制加大滑窗大小,可以有效提高大文件下载速度。
4、流控算法的公平性
参与网络通讯的用户往往不止一个。那么,当不同用户使用不同流控算法时,它们是如何竞争带宽的呢?
比如说,UDP是不管报文有无丢失,只要网卡有空它就一刻不停的报文轰炸;而TCP呢,绝大部分流控算法会在报文丢失后主动降低数据传输率。那么,在一个UDP/TCP共存、且开足马力下载的网络环境里,TCP发送速率就会越来越慢。
换句话说,TCP会主动退让,而UDP不会。
类似的,不同的流控算法也有“是更倾向于侵占还是更倾向于退让”的不同“个性”。
那么不断开新连接、重复“慢启动”流程(慢启动是从很小的滑窗启动,速率是每RTT倍增的,反而是流控协议里增长最迅猛侵略性最强的阶段,增长一点都不慢)也能稍微多挤占一些资源;但后果是复杂的协商过程降低了线路上有效信息所占比率(一个TCP/IP头是带几个字节还是1K字节,带宽利用率显然是截然不同的),反而进一步恶化了通讯环境。
不仅如此,现在很多设备支持QoS优先级设置;比如游戏/视频之类延迟敏感的报文的优先级就可能高于文件下载。这种网络环境里,下载速率受到的影响就更为复杂了。
总结一下就是:
“多开线程增加下载大文件速率”大致来自于三个原因(或它们的组合):一是绕过不够严密的限速措施;二是在和其他用户/应用的竞争中挤占更多带宽;三是在高延迟高抖动网络里变相的强制增加滑动窗口大小,从而绕过TCP流控协议本身的缺陷。
除此之外,正常网络环境里,滑窗算法表现良好;多开线程反而会导致磁盘来回寻道、降低存储子系统吞吐率。在网络传输率和磁盘传输率基本匹配的应用环境里反而会造成负面影响(但大多情况下,硬盘传输带宽远大于网络带宽、再加上磁盘缓冲区的存在,因此最终影响不明显)。
从二层看, 很多链路聚合算法不能做到基于 L4 hash, 这就会导致当你有两个链路的时候, 一个 TCP 连接只能在一个链路上, 只有多个 TCP 连接的时候才能有效利用. 当然拆分一个 TCP 连接到多个链路也是有风险的, 包不按照顺序到达会有性能损失.
从三层看, 可能一个下载的资源有多个地方的镜像, 比如一个域名解析出了多个地区的 CDN, 这个时候多线程就可以最大化本地 ISP 所能提供的带宽
从四层看, 单个 TCP 连接碰到不好的拥塞控制算法, 比如 Compound TCP/Cubic 的时候, 就会因为丢包造成的「乘性减」导致速度暴死, 而多个 TCP 连接存在的时候就可以相对避免这种情况, 每个连接控制的速率都会比较低, 更容易稳定在最大带宽上. 当然如果固定丢包率高的话, 最好还是交给 BBR.
当然还有就是服务器端可能对每个 http session 的最大速率做了限制, 比方说早前百度云只允许一个免费用户创建一个连接, 然后速度又差不多是在 128KB/s, 通过 aria2c 就可以强行启动多线程下载, 这样即便每个连接都是 128KB/s, 你开了十几条线程, 速度就能获得很大提升.
也许因为,它们的事态看起来并没有那么糟糕,所以没能感同身受吧。
其实就算武汉封城的时候,我还天真的以为,这次的疫情只是屁大点事。
直到我发现,官方公布的武汉单城市确诊数量竟然高达几万人。。。这才意识到武汉短短一个多月确实传染了太多的人,这才意识到这个病毒比我想象的要可怕很多。
欧美现在单城市破万的根本没有,甚至全国加起来都没破万,所以它们觉得这不是什么大事,也挺正常。
人类,大概总会是不见棺材不落泪的,等到它们也单城市确诊过万的时候,自然就会意识到,戴口罩一点也不好笑。