系统性能优化-6 TCP 三次握手
系统性能优化-6 TCP 三次握手
TCP 三次握手
客户端优化
客户端发送 SYN 给服务器 此时客户端连接状态:SYN_SENT
如果服务器繁忙或中间网络不畅,客户端会重发 SYN,重试的次数由 tcp_syn_retries 参数控制,默认是 6 次,第 1 次重试发生在 1 秒钟后,接着会以翻倍的方式在第 2、4、8、16、32 秒共做 6 次重试,最后一次重试会等待 64 秒,如果仍然没有返回 ACK,才会终止三次握手。所以,总耗时是 1+2+4+8+16+32+64=127 秒,超过 2 分钟。
因此,对于内网中通讯时,就可以适当调低重试次数,尽快把错误暴露给应用程序。
sysctl net.ipv4.tcp_syn_retries
服务端优化
服务器发送 SYN+ACK 给客户端 此时服务端连接状态:SYN_RECV
客户端收到 SYN+ACK 发送 ACK+数据 到服务端 此时客户端连接状态:Established
服务器收到 SYN 报文后,自动回复 SYN + ACK,并把连接放入半连接队列,当半连接队列已满,就无法再建立新连接了
# 查询服务器因 SYN 半连接队列满而丢弃的新连接,是一个【累计值】
netstat -s | grep "SYNs to LISTEN"# 当发现这个值在一直增加时,可以适当增大半连接队列长度
## 查看当前最大半连接队伍长度
sysctl net.ipv4.tcp_max_syn_backlog
## 通过这个文件修改后 sysctl -p 即可生效
vim /etc/sysctl.conf
net.ipv4.tcp_max_syn_backlog = 1024
不过开启 syncookies 功能就可以在不使用 SYN 队列的情况下成功建立连接
。修改 tcp_syncookies 参数即可,其中值为 0 时表示关闭该功能,2 表示无条件开启功能,而 1 则表示仅当 SYN 半连接队列放不下时,再启用它。由于 syncookie 仅用于应对 SYN 泛洪攻击(攻击者恶意构造大量的 SYN 报文发送给服务器,造成 SYN 半连接队列溢出,导致正常客户端的连接无法建立),这种方式建立的连接,许多 TCP 特性都无法使用
。所以,应当把 tcp_syncookies 设置为 1,仅在队列满时再启用。
# 开启 syncookies
vim /etc/sysctl.conf
net.ipv4.tcp_syncookies = 1
sysctl -p
如果服务器发送 syn 和 ack 后一直没有回应,就会重发,修改重发次数的方法是,调整 tcp_synack_retries 参数:
# 查看 syn ack 的重发次数
sysctl net.ipv4.tcp_synack_retries# 默认值, 1,2,4,8,32 最多等待 63 秒
net.ipv4.tcp_synack_retries = 5
服务端收到 ack + 数据,将连接从半连接队列移入已连接队列 服务端连接状态进入Established
服务器收到 ack 后,就会把连接从 SYN 半连接队列 移入到 accept 队列,等待进程调用 accept 函数时把连接取出来。如果进程一直不取,accept 队列就会溢出,最终导致建立好的 TCP 连接被丢弃。可以选择向客户端发送 RST 复位报文,告诉客户端连接已经建立失败。
# 默认为 0 不开启,这样更有利于应对突发流量,因为此时客户端的状态是已连接,就会开始发送报文,只要服务器不回复 ack,就会重发,当服务器不再繁忙,再次接收到的请求报文由于含有 ACK,仍然会触发服务器端成功建立连接。
tcp_abort_on_overflow = 0
# listen 函数的 backlog 参数就可以设置 accept 队列的大小,但是同时受到 linux 系统阈值的限制
# Linux 系统级的队列长度上限
net.core.somaxconn = 128
# 查看各监听端口上的 accept 队列长度
ss -ltn# 查看有多少个连接因为队列溢出而被丢弃
netstat -s | grep "listen queue"
如果持续不断地有连接因为 accept 队列溢出被丢弃,就应该调大 backlog 以及 somaxconn 参数。
调查显示:三次握手消耗的时间,在 HTTP 请求完成的时间占比在 10% 到 30% 之间。因此,Google 提出了 TCP fast open 方案(简称TFO),客户端可以在首个 SYN 报文中就携带请求,这节省了 1 个 RTT 的时间
。
TFO 通讯分为两个阶段,第一阶段为首次建立连接,这时走正常的三次握手,但在客户端的 SYN 报文会明确地告诉服务器它想使用 TFO 功能,这样服务器会把客户端 IP 地址用只有自己知道的密钥加密(比如 AES 加密算法)
,作为 Cookie 携带在返回的 SYN+ACK 报文中,客户端收到后会将 Cookie 缓存在本地。
之后,如果客户端再次向服务器建立连接,就可以在第一个 SYN 报文中携带请求数据,同时还要附带缓存的 Cookie。很显然,这种通讯方式下不能再采用经典的“先 connect 再 write 请求”这种编程方法,而要改用 sendto 或者 sendmsg 函数才能实现。
服务器收到后,会用自己的密钥验证 Cookie 是否合法,验证通过后连接才算建立成功,再把请求交给进程处理,同时给客户端返回 SYN+ACK。虽然客户端收到后还会返回 ACK,但服务器不等收到 ACK 就可以发送 HTTP 响应了,这就减少了握手带来的 1 个 RTT 的时间消耗。
# 该功能需要客户端和服务端同时支持,第 1 个比特位为 1 时,表示作为客户端时支持 TFO;第 2 个比特位为 1 时,表示作为服务器时支持 TFO,因此该值为3时(0x11),表示完全支持 TFO
sysctl net.ipv4.tcp_fastopen
为了确保安全及用户 IP 会变(如 DHCP ),Cookie 值会隔一段时间变化一次。
[TCP Fast Open --TFO ](https://www.cnblogs.com/codestack/p/18112952)
总结一下文中出现的配置项:
- net.ipv4.tcp_syn_retries 客户端等待服务端回复 syn+ack 时的最大重试次数,默认为6,分别在 1 + 2 + 4 + 8 + 16 + 32 秒时重试,最后一次重试等待 64 秒,总耗时 127 秒
- net.ipv4.tcp_max_syn_backlog 服务端最大的 SYN 半连接队列长度,默认 1024,如果设置的过小,会导致无法建立新连接,
netstat -s | grep "SYNs to LISTEN"
指令可以获得由于半连接队列已满而引发的 SYN 丢弃个数 - net.ipv4.tcp_syncookies 服务端在半连接队列满的情况下依然可以建立连接,0 表示关闭该功能,2 表示无条件开启功能,而 1 则表示仅当 SYN 半连接队列放不下时,再启用。
这种方式建立的连接,许多 TCP 特性都无法使用,应当设置为 1
。 - net.ipv4.tcp_synack_retries 服务端等待客户端回复针对 syn+ack 的 ack 的重试次数,默认为 5,最后一次重试后等待 32 秒,总耗时 63 秒。
- net.ipv4.tcp_abort_on_overflow 当 accept 全连接队列已满,是否立即中止连接向客户端发送 RST 复位报文,默认为 0 不开启,可以更有利于应对突发流量(该连接会暂存在半连接队列,等客户端重发 ack 后续还是可以再建立连接),只有非常肯定 accept 队列会长期溢出时,才能设置为 1 以尽快通知客户端。
- net.core.somaxconn Linux 系统级的队列长度上限,与 listen 函数的 backlog 参数配合可以调整 accept 队列的长度
- net.ipv4.tcp_fastopen TFO 技术,第 1 个 bit 位表示作为客户端是否支持,第 2 个 bit 位表示作为服务端是否支持。