Skip to content

Commit

Permalink
posts(timewait): connection table slot
Browse files Browse the repository at this point in the history
  • Loading branch information
Octobug committed Mar 10, 2024
1 parent 859b55f commit 4879450
Showing 1 changed file with 48 additions and 2 deletions.
50 changes: 48 additions & 2 deletions posts/trans-tcp-time-wait-state-linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ tags:
- NAT
- Network
- sysctl
footnote: 脚注
draft: true
---

# 译:在忙碌的 Linux 服务器上如何处理 TCP TIME-WAIT
# 译:在忙碌的 Linux 服务器上处理 TCP TIME-WAIT

这篇文章是我在处理一个困扰我们很久的故障时通过 Google “偶然”找到的,而它真的把问题解决了。

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 服务器监听额外的端口(818283……)来使用***更多的服务器端口***;
- 通过给负载均衡器配置额外的 IP,并以轮询方式使用它们来使用***更多的客户端 IP***;[^balancer_ips]
- 通过给 Web 服务器配置额外的 IP 来使用***更多的服务器 IP***。[^web_ips]

最后的解决方案是调整 `net.ipv4.tcp_tw_reuse` 和 `net.ipv4.tcp_tw_recycle`。但是先别这样做,稍后会详细介绍这两个配置。

### 内存 (Memory)

## 总结

0 comments on commit 4879450

Please sign in to comment.