过去几天我一直在绞尽脑汁,试图找到以下问题的解决方案:
在我们的数据中心,我们在 BigIP 硬件上运行了 F5,它充当来自全国各地办公地点的客户端计算机的 HTTPS 请求的单一入口点。F5 终止 TLS,然后将所有请求转发到两个 Traefik 负载均衡器,后者将请求路由分发到各个服务实例(Traefik 节点在 Red Hat Enterprise 上的 Docker 中运行,但我认为这与我的问题无关)。从吞吐量、CPU 和内存的角度来看,这三个网络组件完全有能力处理大量请求和流量,并且有足够的容量。
然而,我们注意到客户端发出的 HTTP(S) 请求经常出现 1000 毫秒的延迟,尤其是在高负载时。我们追踪到该问题的根本原因如下:
- 在高负载时,F5“客户端”会以高频率(可能每秒 100 个以上)向 Traefik“服务器”节点发起新的 TCP 连接。
- 当 HTTP 响应返回时,这些连接将在 Traefik“服务器”端终止。
- 每个关闭的连接在 Traefik 主机上保持 TIME_WAIT 状态 60 秒。
- 当 F5 发起新连接时,它会从其临时端口范围中随机选择一个可用端口。
- 有时(通常在高负载期间),Traefik 中已经存在一个处于 TIME_WAIT 状态的连接,其源 IP + 端口、目标 IP + 端口组合相同。发生这种情况时,Traefik 主机上的 TCP 堆栈 (?) 会忽略第一个 SYN 数据包。注意:RFC 6056称之为实例 ID 冲突。
- 1000 毫秒后,重传超时 (RTO) 机制在 F5 上启动并重新发送 SYN 数据包。这一次,Traefik 主机接受了连接并正确完成了请求。
显然,这1000毫秒的延迟是绝对不可接受的。因此,到目前为止,我们考虑了以下解决方案:
- 降低 F5 中的 RTO 以实现更快的重传,例如降低至 200 毫秒。
- 减少 net.ipv4.tcp_fin_timeout 以关闭弃
TIME_WAIT连接速度更快。
更新:这仅适用于对方放弃的连接,即未返回 FIN。它对处于 TIME_WAIT 状态的连接没有任何影响。 - 启用 net.ipv4.tcp_tw_reuse:对于传入连接无用。
- 启用 net.ipv4.tcp_tw_recycle:据我所知,如果客户端发送随机 TCP 时间戳,则禁用此功能。关于此功能是否已从 Linux 中删除的信息(包括经验证据)存在矛盾。此外,一般建议不要乱用。
- 添加更多源 IP 和/或让 Traefik 监听多个端口以增加 IP/端口元组中的排列数量。
我将放弃 #1,因为它只是权宜之计。延迟仍然会发生,只是不那么明显。#3 无论如何都不会产生任何影响,#4 很可能会使系统无法运行。剩下#2和 #5。
但根据我读过几十篇帖子和技术文章后了解到的情况,它们最终都只能减少这些“碰撞”的可能性。因为,最终是什么阻止发送方 F5(伪)随机选择临时端口、源 IP 和目标端口的组合,而这些组合在目标 Traefik 主机上仍处于 TIME_WAIT 状态,无论 fin_timeout 设置有多短(无论如何都应该保持在几秒范围内)?我们只会降低碰撞的可能性,而不是消除它。
在我进行所有研究之后,在大型 Web 应用程序时代,我真的很惊讶这个问题在网络上没有得到更多的讨论(也没有可用的解决方案)。我非常感谢您的想法和意见,即在 TCP 领域是否有更好、更系统的解决方案,可以将冲突的发生率降至接近零。我正在考虑一种 TCP 配置,允许 Traefik 主机立即接受新连接,尽管旧连接处于 TIME_WAIT 状态。但到目前为止,还没有找到。
随机想法和观点:
- 目前,将我们的各种内部应用程序更改为使用运行时间更长的 HTTP(S) 连接来减少每秒的请求/连接数是不可行的。
- F5和Traefik的网络架构不是可以讨论的,也无法改变。
- 我最近调查了 Windows 客户端上的临时端口选择。该算法似乎是顺序的,而不是随机的。最大限度地延长了端口重用时间,降低了安全性。
- 在对空闲系统进行负载测试时,我们每秒生成约 100 个 HTTP 请求/连接。尽管 F5 配置为使用超过 60k 个临时端口,但第一次冲突在几秒钟后就发生了(也就是说在总共 2000 个请求之前)。我认为这是由于端口选择算法的伪随机性质造成的,该算法似乎在避免实例 ID 冲突方面做得相当差。
- Traefik 主机在 SYN 数据包重传时接受 TCP 连接,这可能是特征TCP 实现。RFC6056 提到TIME_WAIT 暗杀,这可能与此有关。
更新:每明星实验,net.ipv4.tcp_fin_timeout 设置不会影响 TIME_WAIT 状态,只会影响 FIN_WAIT_2 状态。并且根据萨米尔·贾费拉里在 Linux 系统(包括我们的 Red Hat Linux)上,TIME_WAIT 周期在源代码中是硬编码的,无法配置。根据源代码,在 BSD 上它是可配置的,但我尚未验证这一点。
答案1
在我们的数据中心,我们有一个在 BigIP 硬件上运行的 F5,它作为单身的入口点对于 HTTPS 请求来自我们全国各地办公地点的客户端机器。
如果这个单点(前端)在将连接传递到后端时保持单一,那么您为什么会担心出现问题呢?特别是如果连接强度“可能每秒 100+”。
您的设置基本上是将一个基数较高的集合挤压到另一个基数明显较低的集合中。
最终只会减少这些“碰撞”的机会
这是分组交换网络工作原理的基础。比如,在以太网层面也存在冲突。随机性是不可避免的,TCP/IP 正在处理它。实际上,IP 协议本身并不是为 LAN 构建的(但在那里仍然运行良好)。
因此,“添加更多源 IP 和/或让 Traefik 监听多个端口”是非常合理的方法。
答案2
虽然我也认为添加更多 IP 地址是最简单的方法,但您是否考虑过探索重用 F5 和 Traefik 节点之间的 TCP 连接,而不是为每个外部请求创建一个新的连接?
我不确定 F5 如何支持这一点,但也许就像在 F5 和 Traefik 节点之间切换到 http2 一样简单。请参阅https://developers.google.com/web/fundamentals/performance/http2#one_connection_per_origin
答案3
原来曾是毕竟,这个问题的解决方案非常简单,这是我们与 Traefik 供应商合作一段时间后找到的。事实证明,我们在 Docker 中运行 Traefik做问题。问题和解决方案非常特定于我们的设置,但我仍然想在这里记录它,以防其他人遇到同样的情况。尽管如此,这确实不是由于实例 ID 冲突是一个真正的问题,因此使其他更通用的建议无效。
长话短说:所有 Traefik 实例都配置为在 Docker Swarm 集群中运行的主机受限容器(即绑定到特定主机)。Traefik 实例需要在主机级别公开一个端口,以便从 F5 访问它们,而 F5 显然不是 Docker Swarm 参与者。这些公开的端口已在入口模式,这不仅是不必要的(不需要通过 Docker Swarm 入口网络路由流量),也是丢弃/忽略 SYN 数据包的原因。一旦我们将端口模式切换为主持人,延迟现象就消失了。
前:
ports:
- target: 8080
published: 8080
protocol: tcp
mode: ingress
后:
ports:
- target: 8080
published: 8080
protocol: tcp
mode: host