jinhailang / blog

技术博客:知其然,知其所以然
https://github.com/jinhailang/blog/issues
60 stars 6 forks source link

Nginx keepalive_requests 踩坑总结 #37

Open jinhailang opened 6 years ago

jinhailang commented 6 years ago

Nginx keepalive_requests 踩坑总结

问题起因

Waf(基于 Nginx 实现的七层防护系统)上线上后,QA 同事对系统进行大流量压测(十万级 QPS),发现部署机器处于 TIME_WAIT 状态的 TCP 连接数异常,高峰期达到几千。按理 waf 这边使用的是长连接,连接数不应该这么多的。

问题定位

首先确认下 TCP 连接处于 TIME_WAIT 原因:

TCP 连接主动关闭方会处于较长的 TIME_WAIT 状态, 以确保数据传输完毕, 时间可长达 2 * MSL, 即就是一个数据包在网络中往返一次的最长时间。
其目的是避免连接串用导致无法区分新旧连接。

这就说明确实是服务端这边主动关闭了连接。我重新 review 了一下 Nginx 配置,确实使用了长连接,并且,我上线前在本地使用 ab 工具压测过, 没有观察到连接数量异常情况

只能祭出 google 大法了,发现可能是 keepalive_requests 设置太小引起的。

keepalive_requests 参数限制了一个 HTTP 长连接最多可以处理完成的最大请求数, 默认是 100。当连接处理完成的请求数达到最大请求数后,将关闭连接。

而我并没有配置 keepalive_requests,所以,就是使用的默认数 100,即一个长连接只能处理一百个请求,然后 Nginx 就就会主动关闭连接,使大量连接处于 TIME_WAIT 状态。

我猜测很大可能就是这个原因了,为了在本地验证,我将 keepalive_requests 分别设为 10, 50, 100, 1000,分四组分别进行压测,对比结果。

压测命令:

ab -n100000 -c10 -k http://127.0.0.1:8086/test

然后使用 netstat -nat |awk '{print $6}'|sort|uniq -c 查看连接数,结果出乎意料的清晰,TIME_WAIT 数量基本等于 总请求数/keepalive_requests,这就基本证实了猜测。

问题解决

其实大部分情况下,使用默认值是没有问题的,但是对于 QPS 较大的情况,默认数值就不够了,综合考虑,在 Nginx conf 增加配置项:

keepalive_requests      1024;

可能很多人更习惯不做限制,可以设置一个最大的数来实现这个效果 -- 2^32 - 1 = 4294967295(这是在运行32位和64位系统的计算机中是无符号长整形数所能表示的最大值,亦是运行32位系统的计算机中所能表示的最大自然数),

疑问

虽然问题解决了,但是,为什么 Nginx 要对单个长连接的处理请求数进行最大限制呢?一般我们理解的,长连接是在连接池里维护的,只要连接本身没有超时等异常问题,是可以一直复用的。

关于这个疑问,讨论的似乎不多,只在 Nginx 开发者论坛上看[nginx] Upstream keepalive: keepalive_requests directive.有所讨论,开发者有模糊的表述:

Much like keepalive_requests for client connections, this is mostly
a safeguard to make sure connections are closed periodically and the
memory allocated from the connection pool is freed.

但是,为什么需要定时关闭连接,释放内存?难道这只是一种保护策略,怕极端情况下,用户配置不当,导致连接一直不断开,引起内存问题?很奇怪,从 Nginx 开发日志也没有找到特别说明。

另外,上面的讨论贴其实是讨论 Upstream keepalive_requests 设置的,最新版 Nginx (version 1.15.3.) 在 Upstream 内增加了 keepalive_requests 配置项,与 keepalive_requests client connections 类似,默认值也是 100。也就是说,之前的版本,连接上游的连接是没有次数限制的,完全由上游的服务端的 keepalive_requests 设置,但是,如果上游服务端不使用 Nginx 的话,别的系统一般也是没有这个限制的。

在大流量的场景下,Nginx 代理升级到这个版本,就可能会引起连接异常情况。关于默认值和兼容性,作者的回复也比较主观,可能这块确实很难面面俱到吧,只能满足大多数场景了。

综上,在 QPS 较高的场景下,服务端要注意将 keepalive_requests 设置的大一些,如果 Nginx 升级到最新版,还需要注意设置 upstream 的keepalive_requests,这两个数量可以不一致,一般服务端要设置的大些,因为一般来说,一个服务端可能对应多个代理。