简介: 解决Tengine健康检查引发的TIME_WAIT堆积问题nginx
“服务上云后,咱们的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 时间内被用完的可能。网络
首先,若是咱们从新回顾下TCP状态机就能知道,TIME_WAIT状态的端口仅出如今主动关闭链接的一方(跟这一方是客户端或者是服务器端无关)。当TCP协议栈进行链接关闭请求时,只有【主动关闭链接方】会进入TIME_WAIT状态。而客户的顾虑也在这里。socket
一方面,健康检查使用 HTTP1.0 是短链接,逻辑上应该由后端NGINX服务器主动关闭链接,多数TIME_WAIT应该出如今NGINX侧。tcp
另外一方面,咱们也经过抓包确认了多数链接关闭的第一个FIN请求均由后端NGINX服务器发起,理论上,Tengine服务器的socket 应该直接进入CLOSED状态而不会有这么多的TIME_WAIT 。函数
抓包状况以下,咱们根据Tengine上是TIME_WAIT的socket端口号,进行了过滤。性能
虽然上面的抓包结果显示当前 Tengine 行为看起来确实很奇怪,但实际上经过分析,此类情形在逻辑上仍是存在的。为了解释这个行为,咱们首先应该了解:经过tcpdump抓到的网络数据包,是该数据包在该主机上收发的“结果”。尽管在抓包上看,Tengine侧看起来是【被动接收方】角色,但在操做系统中,这个socket是否属于主动关闭的决定因素在于操做系统内TCP协议栈如何处理这个socket。阿里云
针对这个抓包分析,咱们的结论就是:可能这里存在一种竞争条件(Race Condition)。若是操做系统关闭socket和收到对方发过来的FIN同时发生,那么决定这个socket进入TIME_WAIT仍是CLOSED状态决定于 主动关闭请求(Tengine 程序针对 socket 调用 close 操做系统函数)和 被动关闭请求(操做系统内核线程收到 FIN 后调用的 tcp_v4_do_rcv 处理函数)哪一个先发生 。spa
不少状况下,网络时延,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
一、 Tengine code analysis
从以前的抓包,咱们能够看出来多数的TIME_WAIT socket是为了后端健康检查而建立的,所以咱们主要关注 Tengine的健康检查行为,如下是从ngx_http_upstream_check_module 的开源代码中摘抄出来的关于socket清理的函数。
从这段逻辑中,咱们能够看到,若是知足如下任一条件时,Tengine会在收到数据包以后直接关闭链接。
这里,若是咱们让以上的条件变成不知足,那么就有可能让Tengine所在的操做系统先处理被动关闭请求,进行socket清理,进入CLOSED状态,由于从HTTP1.0的协议上来讲,NGINX服务器这一方必定会主动关闭链接。
二、解决方法
通常状况下,咱们对于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.0\r\n\r\n";
check_keepalive_requests 2
check_http_expect_alive http_2xx http_3xx;
理由很简单,咱们须要让以前提到的三个条件不知足。在代码中,咱们不考虑 error 状况,而need_keepalive 在代码中默认 enable (若是不是,能够经过配置调整),所以需确保check_keepalive_requests大于1便可进入Tengine的KEEPALIVE逻辑,避免Tengine主动关闭链接。
由于使用HTTP1.0的HEAD方法,后端服务器收到后会主动关闭链接,所以Tengine建立的socket进入CLOSED状态,避免进入TIME_WAIT而占用动态端口资源。
做者:SRE团队技术小编-小凌
本文为阿里云原创内容,未经容许不得转载