diff --git a/posts/trans-tcp-time-wait-state-linux/README.md b/posts/trans-tcp-time-wait-state-linux/README.md index 6d8700d..3d056fd 100644 --- a/posts/trans-tcp-time-wait-state-linux/README.md +++ b/posts/trans-tcp-time-wait-state-linux/README.md @@ -8,10 +8,11 @@ tags: - NAT - Network - sysctl +footnote: 脚注 draft: true --- -# 译:在忙碌的 Linux 服务器上如何处理 TCP TIME-WAIT +# 译:在忙碌的 Linux 服务器上处理 TCP TIME-WAIT 这篇文章是我在处理一个困扰我们很久的故障时通过 Google “偶然”找到的,而它真的把问题解决了。 @@ -95,7 +96,7 @@ sudo sysctl -p ::: info 关于原文 -为了确保自己完全看明白(毕竟是改生产环境的内核网络参数),我决定将它翻译成中文。 +为了确保自己理解原文(毕竟是改生产环境的内核网络参数),我决定将它翻译成中文。 - 原文:[Coping with the TCP TIME-WAIT state on busy Linux servers](https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux) - 作者:[Vincent Bernat](https://github.com/vincentbernat) @@ -170,4 +171,49 @@ TIME-WAIT 0 0 192.0.2.145:80 203.0.113.47:50685 ## 存在哪些问题 +接下来我们看一下在处理大量连接的服务器上,为什么这个状态会是个麻烦。有三个方面: + +- 连接表中使用的“插槽” (slot) 会阻止相同类型的***新连接***; +- 内核中套接字结构体占用的***内存***; +- 额外的 ***CPU 占用***。 + +`ss -tan state time-wait | wc -l` 的结果本身并不是一个问题! + +### 连接表槽 (Connection table slot) + +`TIME-WAIT` 状态的连接在连接表中会被保留一分钟。这意味着另一个具有相同*四元组*(源地址、源端口、目标地址、目标端口)的连接不能存在。 + +对于一个 Web 服务器来说,目标地址和目标端口通常是固定的。如果你的 Web 服务器位于一个 L7 负载均衡器后面,那么源地址也是固定的。在 Linux 上,默认情况下客户端的端口分配范围大约有 30,000 个(可以通过 `net.ipv4.ip_local_port_range` 调整)。这意味着 Web 服务器和负载均衡器之间的连接每分钟只能建立大约 30,000 个,即***每秒约 500 个连接***。 + +如果 `TIME-WAIT` 是位于客户端,这种本地端口不够用的情况很容易检测到。因为应用程序调用 `connect()` 时会返回 `EADDRNOTAVAIL` 错误,此时它会把相关的错误信息记录下来。在服务器端情况要复杂一些,因为服务端没有这种(主动调用接口而产生的)错误日志和统计信息可依赖。如果怀疑服务端遇到了此问题,可以尝试利用一些确定的信息来列出已使用的四元组数量: + +```sh +$ ss -tan 'sport = :80' | awk '{print $(NF)" "$(NF-1)}' | \ +> sed 's/:[^ ]*//g' | sort | uniq -c + 696 10.24.2.30 10.33.1.64 + 1881 10.24.2.30 10.33.1.65 + 5314 10.24.2.30 10.33.1.66 + 5293 10.24.2.30 10.33.1.67 + 3387 10.24.2.30 10.33.1.68 + 2663 10.24.2.30 10.33.1.69 + 1129 10.24.2.30 10.33.1.70 + 10536 10.24.2.30 10.33.1.73 +``` + +[^more_quad]: 在客户端,较老版本的内核还必须为每个主动发出的连接找到一个***可用本地元组 (free local tuple)***(源地址和源端口)。增加服务器端口或 IP 数量在这种情况下无济于事。Linux 3.2 已经版本足够新,可以为不同的目标连接共享相同的本地元组。感谢 Willy Tarreau 在这个问题上的[见解](http://marc.info/?l=haproxy&m=139315382127339&w=2)。 + +解决方案是***允许更多的四元组***。[^more_quad] 有几种方法可实现(按设置难度排序): + +[^balancer_ips]: 为了避免 `EADDRINUSE` 错误,负载均衡器在调用 [`bind()` 与 `connect()`](https://idea.popcount.org/2014-04-03-bind-before-connect/) 之前需要使用 `SO_REUSEADDR` 选项。 +[^web_ips]: 这个最后的解决方案可能看起来有点愚蠢,因为你可以直接使用更多的端口,但有些服务器无法以这种方式进行配置。倒数第二个解决方案设置起来可能也会相当麻烦,这取决于具体的负载均衡软件,但它使用的 IP 较少。 + +- 通过 `net.ipv4.ip_local_port_range` 设置更大范围来使用***更多的客户端端口***; +- 通过让 Web 服务器监听额外的端口(81、82、83……)来使用***更多的服务器端口***; +- 通过给负载均衡器配置额外的 IP,并以轮询方式使用它们来使用***更多的客户端 IP***;[^balancer_ips] +- 通过给 Web 服务器配置额外的 IP 来使用***更多的服务器 IP***。[^web_ips] + +最后的解决方案是调整 `net.ipv4.tcp_tw_reuse` 和 `net.ipv4.tcp_tw_recycle`。但是先别这样做,稍后会详细介绍这两个配置。 + +### 内存 (Memory) + ## 总结