乐趣区

关于typescript:解决Tengine健康检查引起的TIMEWAIT堆积问题

简介: 解决 Tengine 健康检查引起的 TIME_WAIT 沉积问题

一. 问题背景

“服务上云后,咱们的 TCP 端口基本上都处于 TIME_WAIT 的状态”、“这个问题在线下机房未曾产生过”这是客户提交问题的形容。

客户环境是自建 Tengine 作为 7 层反向代理,后端接约 1.8 万台 NGINX。Tengine 上云之后,在服务器上发现大量的 TIME_WAIT 状态的 TCP socket;因为后端较多,潜在可能影响业务可用性。用户比照之前的教训比拟放心是否可能是接入阿里云之后导致,所以心愿咱们对此进行具体的剖析。

注:TIME_WAIT 状态的监听带来的问题在于主机无奈为往内部的连贯申请调配动静端口。此时,能够配置 net.ipv4.ip_local_port_range,减少其端口抉择范畴(能够思考 5000 – 65535),但仍然存在 2 MSL 工夫内被用完的可能。

二. TIME_WAIT 起因剖析

首先,如果咱们从新回顾下 TCP 状态机就能晓得,TIME_WAIT 状态的端口仅呈现在被动敞开连贯的一方(跟这一方是客户端或者是服务器端无关)。当 TCP 协定栈进行连贯敞开申请时,只有【被动敞开连贯方】会进入 TIME_WAIT 状态。而客户的顾虑也在这里。

一方面,健康检查应用 HTTP1.0 是短连贯,逻辑上应该由后端 NGINX 服务器被动敞开连贯,少数 TIME_WAIT 应该呈现在 NGINX 侧。

另一方面,咱们也通过抓包确认了少数连贯敞开的第一个 FIN 申请均由后端 NGINX 服务器发动,实践上,Tengine 服务器的 socket 应该间接进入 CLOSED 状态而不会有这么多的 TIME_WAIT。

抓包状况如下,咱们依据 Tengine 上是 TIME_WAIT 的 socket 端口号,进行了过滤。

图 1:一次 HTTP 申请交互过程

尽管下面的抓包结果显示以后 Tengine 行为看起来的确很奇怪,但实际上通过剖析,此类情景在逻辑上还是存在的。为了解释这个行为,咱们首先应该理解:通过 tcpdump 抓到的网络数据包,是该数据包在该主机上收发的“后果”。只管在抓包上看,Tengine 侧看起来是【被动接管方】角色,但在操作系统中,这个 socket 是否属于被动敞开的决定因素在于操作系统内 TCP 协定栈如何解决这个 socket。

针对这个抓包剖析,咱们的论断就是:可能这里存在一种竞争条件(Race Condition)。如果操作系统敞开 socket 和收到对方发过来的 FIN 同时产生,那么决定这个 socket 进入 TIME_WAIT 还是 CLOSED 状态决定于 被动敞开申请(Tengine 程序针对 socket 调用 close 操作系统函数)和 被动敞开申请(操作系统内核线程收到 FIN 后调用的 tcp_v4_do_rcv 处理函数)哪个先产生。

很多状况下,网络时延,CPU 解决能力等各种环境因素不同,可能带来不同的后果。例如,而因为线下环境时延低,被动敞开可能最先产生;自从服务上云之后,Tengine 跟后端 Nginx 的时延因为间隔的起因被拉长了,因而 Tengine 被动敞开的状况更早进行,等等,导致了云上云下不统一的状况。

可是,如果目前的行为看起来都是合乎协定规范的状况,那么如何侧面解决这个问题就变得比拟辣手了。咱们无奈通过升高 Tengine 所在的主机性能来延缓被动连贯敞开申请,也无奈升高因为物理间隔而存在的时延耗费放慢 FIN 申请的收取。这种状况下,咱们会倡议通过调整系统配置来缓解问题。

注:当初的 Linux 零碎有很多办法都能够疾速缓解该问题,例如,
a) 在 timestamps 启用的状况下,配置 tw_reuse。
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
b) 配置 max_tw_buckets
net.ipv4.tcp_max_tw_buckets = 5000
毛病就是会往 syslog 里写: time wait bucket table overflow.

因为用户应用自建 Tengine,且用户不违心进行 TIME_WAIT 的强制清理,因而咱们思考通过 Tengine 的代码剖析看看是否有机会在不改变 Tengine 源码的状况下,扭转 Tengine 行为来防止 socket 被 Tengine 被动敞开。
Tengine version: Tengine/2.3.1
NGINX version: nginx/1.16.0

1、Tengine code analysis

从之前的抓包,咱们能够看进去少数的 TIME_WAIT socket 是为了后端健康检查而创立的,因而咱们次要关注 Tengine 的健康检查行为,以下是从 ngx_http_upstream_check_module 的开源代码中摘抄进去的对于 socket 清理的函数。

图 2:Tengine 健康检查实现后清理 socket 过程

从这段逻辑中,咱们能够看到,如果满足以下任一条件时,Tengine 会在收到数据包之后间接敞开连贯。

  • c->error != 0
  • cf->need_keepalive = false
  • c->requests > ucscf->check_keepalive_requ

图 3:Tengine 中真正实现 socket 敞开的函数

这里,如果咱们让以上的条件变成不满足,那么就有可能让 Tengine 所在的操作系统先解决被动敞开申请,进行 socket 清理,进入 CLOSED 状态,因为从 HTTP1.0 的协定上来说,NGINX 服务器这一方肯定会被动敞开连贯。

2、解决办法

个别状况下,咱们对于 TIME_WAIT 的连贯无需太过关怀,个别 2MSL(默认 60s) 之后,零碎主动开释。如果须要缩小,能够思考长链接模式,或者调整参数。
该 case 中,客户对协定比拟理解,但对于强制开释 TIME_WAIT 仍有放心;同时因为后端存在 1.8 万台主机,长连贯模式带来的开销更是无奈接受。
因而,咱们依据之前的代码剖析,通过梳理代码外面的逻辑,举荐客户以下健康检查配置,
check interval=5000 rise=2 fall=2 timeout=3000 type=http default_down=false;
check_http_send “HEAD / HTTP/1.0rnrn”;
check_keepalive_requests 2
check_http_expect_alive http_2xx http_3xx;
理由很简略,咱们须要让之前提到的三个条件不满足。在代码中,咱们不思考 error 状况,而 need_keepalive 在代码中默认 enable(如果不是,能够通过配置调整),因而需确保 check_keepalive_requests 大于 1 即可进入 Tengine 的 KEEPALIVE 逻辑,防止 Tengine 被动敞开连贯。

图 4:Tengine 健康检查参考配置

因为应用 HTTP1.0 的 HEAD 办法,后端服务器收到后会被动敞开连贯,因而 Tengine 创立的 socket 进入 CLOSED 状态,防止进入 TIME_WAIT 而占用动静端口资源。

作者:SRE 团队技术小编 - 小凌
原文链接
本文为阿里云原创内容,未经容许不得转载

退出移动版