从一次 TIME_WAIT 调优说起

keep-alive 连接

在 http 1.0 中,如果客户端在发起请求时加入 Connection:keep-alive 首部,服务端在响应的时候也加入 Connection:keep-alive 首部,则此次 http 请求将使用持久连接,即请求结束之后 TCP 连接不会关闭以供后续请求继续使用。不难看出,持久连接必须是客户端和服务器都同时支持才可能会发生,因为 TCP 协议是双向通信。

实现持久连接的 http 请求必须要保证报文可以正确判断起始和结束,这在 http 中是通过 content-length 首部以及 chunk 传输来保证的,具体可以参见《http 权威指南》“连接管理”章节,文章 HTTP协议头部与Keep-Alive模式详解 也对 keep-alive 作用和意义有详细介绍。

总得来说利用 keep-alive 可有效利用 TCP 连接来提高 web 请求的性能。

一般 web 服务的架构

以简单的 Python web 服务为例,其架构组织如下:

其中 Python app 大多数是 web framework + gunicorn 的组合,gunicorn 是一个 web server。

一个来自 user 的请求会经过如下处理用户最终才能获得响应:

其中 user 连接 nginx 的 TCP 和 nginx 连接 app 的 TCP 是不同的连接,也就是说要完成一个完整的请求 nginx 必须维系着自身与 user 和 app 两端的连接,nginx 作为代理会负责对 user 的请求进行中转处理然后发起与 app 的新的 TCP 连接,这就是 nginx 的角色。

从线上后端 app 服务大量的 TIME_WAIT 说起

默认情况下的,大多数现代浏览器以及其他 web 终端与 nginx 的连接默认都会开启 keep-alive,但是 nginx 与后端 app 则不一定。

以我遇到的场景为例,在后端 app 服务所在的服务器,通过查看其和 nginx 端的连接通过 netstat 查看能够看到

1
netstat -n | grep 10.10.93.110   | grep TIME_WAIT | wc -l

处于 TIME_WAIT 的 TCP 是 1000 多个,但处于 ESTABLISHED 是 20 多个,按照 TCP 协议的描述,具体见 Transmission Control Protocol,处于 TIME_WAIT 的 TCP 连接在等待 2MSL 时间之后才会真正关闭,但是关闭之前其所占用端口和内存资源都不会释放,也就是说 TIME_WAIT 的变多说明服务器的处理能力会受到影响,甚至在极端情况下不响应。

linux 内核 MSL 是 60s,2MSL 就是 120s,每秒有 20 TCP 连接,假设响应时间很短,断开之后等待 2MSL,则 120s 之后差不多会有 1000 多个 TIME_WAIT,基本符合 TCP 的状态变迁。

为什么后端 app 和 nginx 的连接会有如此多的 TIME_WAIT?

nginx 和 gunicorn 的默认配置

默认 nginx 和后端 APP 的连接是短连接,即请求结束之后就会关闭,无论是 nginx 主动关闭还是后端 APP 主动关闭都会产生大量的 TIME_WAIT。

Gunicorn 作为一个 web server 已经很早就开始支持 keep-alive 了,在本文开始说过要保持持久连接,连接的两端都必须支持 keep-alive,所以这里我们主要修改 nginx 和上游 upstream 的连接来支持 keep-alive。

修改 nginx 让 upstream keep-alive

在未修改 nginx 之前可以很明显看到

1
netstat -n | grep 10.10.18.126 | grep ESTABLISHED | sort

后端 app 和 nginx 的连接中端口 nginx server 的端口一直在改变,修改 nginx 支持 upstream 的 keep-alive 之后,端口改变的频率降低,甚至一段时间内都不在变化,说明请求复用了连接。

nginx 的 keep-alive 的支持需要在 upstream 配置 keepalive 300; 指令,并且在 location 中配置

1
2
3
4
location /  {
proxy_http_version 1.1;
proxy_set_header Connection "";
}

告诉后端 app 请求支持 keep-alive。

具体 keepalive 指令的理解详见支持keep alive长连接 ,尤其是需要注意对 keepalive 指令数量的控制,需要对 keepalive 有非常详细的了解。

内核参数对 TIME_WAIT 的影响

有很多内核参数决定了服务器 TIME_WAIT 在单位时间内产生的数量,比如

  • tcp_tw_recycle
  • tcp_tw_reuse
  • tcp_max_tw_buckets
  • net.ipv4.tcp_fin_timeout

在配置服务器需要对不同内核参数进行特定场景的优化,为此就需要理解当前服务器的服务在整个 TCP 链接的角色和位置,比如是作为 client 还是 server。举例来说 nginx 对于下游用户来说是个 server,但是对于后端 app 来说是一个 client,nginx 扮演的角色的不同对其配置差异也很大。

优化内核参数需要认真研读理论部分,仔细就行测验,在结合他人的实践经验针对自己的业务调整。详见

三月沙 wechat
扫描关注 wecatch 的公众号