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
4location / {
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 扮演的角色的不同对其配置差异也很大。
优化内核参数需要认真研读理论部分,仔细就行测验,在结合他人的实践经验针对自己的业务调整。详见