现在, 时间就是金钱. 不像以前浏览一个网页就是一个奢侈品. 如今, 网速越来越快, 下个2GB的东西, 1分钟就好了.
那, 我现在网速很慢,应该怎么提高的我的网速呢?
提升网速的不二法门就是... 买宽带~
233333~ 当然,这只是, 给用户的建议。 对于, 我们程序员来说, 花钱就是最痛苦的事. 这里, 我们需要用我们自己的双手去提升网速.
这里,我们主要看看,在TCP这边怎么提升网络速度.
常见的TCP 延时事务
TCP是网络通信很重要的一个协议,程序员的基本功也就在这, 最出名的应该算是TCP的3次握手了. 不过,这里3次握手不是我要阐述的, 有兴趣的同学,可以看看TCP3次握手. 那还有其他能造成TCP时延的事务吗?
当然有啊. 常见能够造成的事务有以下几种.
延迟确认
nagle算法
slow-start
端口耗尽
延迟确认
当你在进行网络数据传输, 成功发送数据包时, 服务器会给你返回一个ACK进行确认。 但是为了防止,网络的堵塞,通常,服务器在接收该数据时,会对ACK进行延时, 如果在一定时间内(通常为200ms), 有另外的数据来源时, 则会将2次ACK包一起发送,减少宽带.
如下图所示:
总结一句话:
ACK every second packet, or a single packet after the Delayed ACK timer expires
即,有两个包立即到来立即发送ACK, 没有的话,等一会,实在没有则将该次ACK 单独发送.
nagle算法延时
这是nagle 创建的一个算法,和延迟确认一样也是用来解决网络堵塞问题. 不过,他针对的是Sender一端. 众所周知的TCP的大小有40 bytes. 如果你的数据包内容才1B的话, 这样传输的价值就非常小了.所以,聪明的nagle想了想,这样不行,得让小数据包在缓存里面待一段时间,等数据多了再一起发送,如果是在没有其他数据了, 超过nagle算法设置的时延后,那就只能单独发送了.
nagle 和 确认延迟共同作用
假设现在有这样一个场景, 有两个包, 一个已经发送,另外一个由于尺寸太小, 被放在缓存当中. 此时,你的nagle算法是开启的, 那么此时,你需要等到前面一个包被确认之后才能发送.那么这样算起来,在这个特殊情况下,你偶数包的延迟时间是=== nagle+确认延迟. 虽然,这种情况很特殊,但是在高并发的情况下,任何东西都是有可能出现的.
nagle的缺陷
由于nagel主要是针对小数据包, 所以对于小包的影响很大. 比如: 你使用HTTP只是发送GET请求,要求数据库进行相关的取操作,而返回的数据很小. 那么,这时候nagle会让你欲哭无泪~
解决缺陷
针对于确认时延, 这里由于是系统自己设置的.
在windows 里,确认延迟一般为200ms.(现在谁还用windows)
而在*nx里,延时被调整到15-40ms.
不过在linux里面可以使用tcp_delack_min进行修改. 不过对于15-40ms, 俺认为这个应该没有太大的影响. 没必要大动干戈进行改动。 不过,对于nagle算法延时,这个问题就比较严肃了. 在nodeJS里面我们一般使用socket.setNoDelay([noDelay])
来进行设置.
slow start为何物
slow-start 是为了解决网络延迟而被设计出来的一个算法. 用来逐步增加数据包的发送量,直到发送端和接收端能够承载的最大值为止.
在正式介绍slow-start 工作流程之前,我们需要掌握几个基本的概念。
TCP 里面的window
window是TCP报文里面的一部分. 我们看一下TCP协议内容.
就是里面的window. 他主要就是用来描述 连接能够承受的包的大小, 因为一旦你的包过大,会造成包的废弃,而导致重发。window 就是起到提高传输效率的作用.
那window 是怎么确定的呢? 首先我们需要明白, 每一方的 congestion window 的最大值,都只有自己知道,那要猜测到对方的最大值话,就只能一步一步的逼近. 但第一次的值应该怎么设置呢? 其实这是有标准的.
首先,我们需要了解另外一个名词--MSS][4. 他是congestion window 的基础值. MSS 通常为1460B(1500-20 for IP - 20 for TCP). 然后,congest control 定义了一套公式:
If SMSS > 2190 bytes:
IW = 2 * SMSS bytes and MUST NOT be more than 2 segments
If (SMSS > 1095 bytes) and (SMSS <= 2190 bytes):
IW = 3 * SMSS bytes and MUST NOT be more than 3 segments
If SMSS <= 1095 bytes:
IW = 4 * SMSS bytes and MUST NOT be more than 4 segments
所以由上面算下来, window = MSS * 3 = 4380B. 这就是sender's congestion window 的初始值. 这里的3还有另外一个名词--initcwnd(下文会有介绍).
ok~ 基本概念有了, 那 how does slow-start work?
slow-start 是怎么工作的?
在首次发送信息时, Sender 会初始化一个包, 并且该包里面包含了一个比较小的 堵塞窗口(其实就是表明窗口的容载量--bytes). 他的大小是由 MSS 决定的.比如,我们的MSS为1460B, 那么我们初始化的congeston window就可以为2920B. 即,相当于发送了2个小分组. (不理解,见window size)
接受者接受到包之后, 会返回一个ACK包,并附上 receiver's window size(通常,会比sender发送过来的大). 如果, receiver 没有响应, 那么sender 就知道自己 的包太大了, 然后会自行改小 然后再发送.
sender接受到receiver 的 ACK后,此时就在上一次发送的基础上额外加上一倍的MSS. 即, 上一次,我发送了2个MSS大小的包, 那么此时,会接受到两次ACK确认,接收到之后,我就可以增大window size, 向receiver额外再发送一倍---4个MSS大小(2920 + 1460 + 1460). 如此往复,直到两边的window size 到达上限之后,那么slow start 就已经完成了.
其实,上面的传输过程,我们可以使用一个图表来表示. 这是,在下载一个文件时抓包时的图.(抓包工具你用什么都可以,不过最常用的是fiddle和wireShark)
x-axis 是 时间, y-axis 是 发送的序列号(用来表示每个包是整个资源的哪一部分). 注意!!! 里面的一个点 就是一个包. 我们可以数一数, 第一个有2个dot, 第二个有4个dot, 第三个有8个dot... 后面肯定会有一个上限值, 那么,此时就已经达到最大传输速度了. ok~ slow-start要做的工作就已经完成了。 那么该次的Connection 应该算是最优connection, 所以对于已经建立好的connection实现重用也是TCP优化很重要的一部分.
slow start 好处
减少连接断开次数: 因为包不会由于网络堵塞而丢失
用户能够享受更快的下载速度,因为此时slow-start已经找到了最大的连接速度了
降低网络堵塞情况
但是,好处就这几个,但是对于大量连接需要建立时, slow-start 对其影响 就比较呵呵了。 那应该如果优化slow-start呢?
解决slow-start时延
上面提到过,congestion control 有一个计算机制, 用来确定初始化时的包的传输量.
If SMSS > 2190 bytes:
IW = 2 * SMSS bytes and MUST NOT be more than 2 segments
If (SMSS > 1095 bytes) and (SMSS <= 2190 bytes):
IW = 3 * SMSS bytes and MUST NOT be more than 3 segments
If SMSS <= 1095 bytes:
IW = 4 * SMSS bytes and MUST NOT be more than 4 segments
上面的数字,我们就可以称作为initcwnd(the initial congestion window parameter). 他用来规定你的sender 在初始化时,发送数据包的数量. 由于通常规定MSS 一般为1460B, 所以,initcwnd 的数量一般规定为2.(因为,要比上限值小才行, 防止丢包)
但是,这个只是对于很老的机器来说, 现在大家的笔记本基本上都是00后, 配置已经远远超过以前的台式了. 所以,一般而言,receiver window size 应该不会很低。 下图是操作系统和对应的window size的最大值.
就算是XP 他也可以接受 65 535B 大小的包. 如果你sender发送的还是 2920B 的话,这就有点 太慢了. 所以,在配置服务器的时候(特别是*nx), 我们需要对其initcwnd的值,更改一下. 减少他slow-start的时间.
在linux 下, 我们可以使用:
ip route show; //查看电脑设置的initcwnd初始值
sudo ip route change default via 192.168.1.1 dev eth0 initcwnd 10 //将initcwnd 设置为10
ip route show; //检查initcwnd 是否为10
在windows 2008 Server 下的更改请参靠: initcwnd
提升了initcwnd,有什么用?
那这样做到底有什么效果呢?我们看图说话:
可以看到,随着initcwnd的提高, 下载的时间逐渐变慢. 但是,initcwnd 也不能无限制提高, 可以看到从10到20, 时间就基本上没什么变化了. 所以,一般推荐提升到10就可以了.
另一方面,initcwnd对于CDN 的优化,也是非常重要的。 要知道CDN 本来就是以快著名. 我们先来看看CDN 是如何工作的.
HOW CDN works
CDN凭借他特有的机制,高速通道,CDN Cache,域名分片等特性. 成为了云平台上必不可少的一个特性.
但,CDN到底是怎么工作的呢?
CDN是云平台的产物,所以他必须依赖的就是众多的服务器. 通常来说, CDN 有很多 Points of Presence (PoPs) 分布在全国或者说全世界各地,用来加速文件传输的.
如果,我们的文件没有放在CDN上,那么,他的传输过程是怎样的呢?
像这里,从Russia 到 America中间经过无数的节点进行传输,可想而知,随随便便丢个包,那么, 你传输就得重头再来~ 造成的网络堵塞我就不说了,关键用户等不了~ 对于这种情况,考虑直接上CDN。 我们来看一下,带上CDN的时候.
当你的文件放在CDN上时, 用户首次使用该文件时, 这时候最近的CDN Server 会向 Origin Server 请求文件,然后该文件就会保存在该CDN Server 以备用户下一次读取. 所以, 一般而言,第一个吃螃蟹的人,贡献是最大的.
另外,如果你请求的资源是动态生成的,那么CDN Cache你的Content也是没有什么卵用的. 所以CDN 还有另外一个机制-- Super Highway. 不同于前面的without CDN 时候的传输方式, 经过独立的ISP, 用户等到花儿都谢了资源才到, 这里, CDN 会建立一个高速通道, 将动态资源高速传递.
云平台内部会自建算法,计算两点位置传输的最短路径, 然后找到对应服务器进行传输. 所以,远距离传输也是CDN的一大亮点。 不过由于距离长,稳定性和安全性的要求也会增加. CDN服务器就必须保证 自己 本身能够过滤掉一些DDOS attackers 以及 能够承受部分的DDOS攻击.
ok~ CDN 基本内容算是说完了, 通过上面的介绍,为了达到'高速'这个蜜汁weapon. 提升initcwnd 也是必不可少的一部分。 国外的CDN,通常会到10+. 这是linux kernel 内置的(3.0版本以上). 可以说, 你的initcwnd 越高, 对机器的性能要求也越高。 所以,对于自己的云平台有蜜汁自信的,他的initcwnd肯定会高。 这里,我放一份,国外的CDN 服务商的 initcwnd数.
可以看到. Cachefy 像是开挂似的, initcwnd数都到70了. 不过,initcwnd只是作为一个参考,服务商性能的优劣并不仅仅只有initcwnd这一个参数.
端口耗尽和TIME_WAIT
还记得4次挥手的时候,发生的故事么?
在双方发送一次FIN包之后,还需要经过TIME_WAIT的2MSL时延之后,才可以正式宣告结束.(MSL是报文在网络中存活的最大时间- Maximum Segment Lifetime)具体的4次回收流程如下:
只有当TIME_WAIT正式结束之后, 端口的利用才有可能被释放. 所以,TIME_WAIT的2MSL 也可算是一个时延.
这里,我们需要了解一下,在TCP通信中,很重要的一个概念就是连接唯一性. 确认唯一性一般只要4个值即可.
client IP Address
client port
server IP Address
server port
这也是TCP报文和IP报文里面必不可少的部分。
所以,由于2MSL时延的关系,会造成client的端口堵塞。有可能会造成端口耗尽的结果。 那对于server 有什么影响呢?
在普通HTTP 请求当中对于server的影响可以说没有,但如果你使用的是socket通信的话,那么影响就比较大了, 因为在断开的时候,server 的socket 就会处于TIME_WAIT状态, 有可能造成服务器的端口耗尽,在nodeJS里面可以使用setTimeout()进行 自动断开等优化操作.
但是, 为什么客户端一定要有2MSL的延时呢?
很简单,就是保证数据的正确性.
这里,我们可以这么考虑, 如果没有2MSL, TCP连接的数据有可能发生神马情况?
如图:
point_1 向point_2发送信息时,有一个包,过度延时(在2MSL内). 但此时,point_2向point_1发送断开请求,没有时延的情况下, 两者会立即断开.然后接着, point_1又向point_2 建立3次握手连接。 完成之后,延迟的包又发送过来,由于包的IP和port都是正确的,point_2当然会无条件处理,而结果就是,将现存的数据给改变--有可能造成数据bug.
所以,2MSL的时间很有必要.
但,由于2MSL的必要性,会对于某些大并发操作的连接产生巨大影响, 比如使用siege,ab进行基准测试, 大并发查询数据库等等. 所以, 对于这次,实现连接的reuse就非常必要了.