简介: 解决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团队技术小编-小凌
原文链接
本文为阿里云原创内容,未经容许不得转载