bigywater.tech
微服务 · 23

长连接 keepalive 到上游:连接复用率

nginx 到上游的连接复用率由什么决定?keepalive 池应该开多大?后端先关闭空闲连接会不会导致错误?这篇用一个能数连接的后端和 nginx 1.31.6 量出来,也诚实记下了没有复现的现象。

1 对 1000
nginx 1.31.6 什么都不配与显式 proxy_http_version 1.0 时,1000 个顺序请求后端 accept 的连接数
834 对 28
3000 个请求、并发 8 / 32 时,keepalive 2 与 keepalive 32 后端 accept 的连接数(并发 8 对 keepalive 2 是 834,keepalive 32 并发 32 是 28)
0 个
后端 0.3 s 空闲就关连接、gap 在 0.3 s 附近时,60 个请求里出现 502 的数量(没有复现偶发 502)

默认就复用

实测 · 本机执行输出
  配置                                          | 状态码         | 后端 accept 的连接数 | 复用率   | nginx 发给后端的请求行与 Connection 头      
  --------------------------------------------+-------------+----------------+-------+-----------------------------------
  (a) 只有 proxy_pass                           | {200: 1000} | 1              | 99.9% | GET / HTTP/1.1 (没有 Connection 头)  
  (b) proxy_http_version 1.0                  | {200: 1000} | 1000           | 0.0%  | GET / HTTP/1.0 (没有 Connection 头)  
  (c) proxy_set_header Connection close       | {200: 1000} | 1000           | 0.0%  | GET / HTTP/1.1 + Connection: close
  (d) keepalive 16 + HTTP/1.1 + Connection "" | {200: 1000} | 1              | 99.9% | GET / HTTP/1.1 (没有 Connection 头)  
  nginx 1.31.6 里,什么都不配时就已经是 HTTP/1.1 且复用到上游的连接(教科书里“必须同时配 HTTP/1.1、Connection 空串和 keepalive”是旧版本的默认值,我没有测旧版本);只有显式退回 HTTP/1.0 或发 Connection: close 才会每个请求新建连接

nginx 1.31.6 默认 HTTP/1.1 并复用上游连接;只有显式退回 1.0 或 Connection: close 才每个请求新建。

池的大小

实测 · 本机执行输出
  配置                                    | 并发 1        | 并发 8         | 并发 32       
  --------------------------------------+-------------+--------------+-------------
  keepalive 2                           | 3 条(99.9%)  | 834 条(72.2%) | 853 条(71.6%)
  keepalive 8                           | 3 条(99.9%)  | 8 条(99.7%)   | 171 条(94.3%)
  keepalive 32                          | 3 条(99.9%)  | 8 条(99.7%)   | 28 条(99.1%) 
  keepalive 32 + keepalive_requests 100 | 30 条(99.0%) | -            | -           
  keepalive N 只缓存“空闲”连接:并发比 N 大时,多出来的请求处理完之后连接放不进池子(池子满了)就会被关掉,下一个请求又得新建;并发不超过 N 时,连接几乎全部复用

keepalive N 缓存空闲连接,并发超过 N 时多出来的连接用完就被关;keepalive_requests 限制每条连接处理的请求数。

后端先关连接

实测 · 本机执行输出
  请求间隔 gap         | client 看到的状态码(60 个) | error.log: upstream prematurely closed connection 次数 | 后端 accept 的连接数
  -----------------+---------------------+------------------------------------------------------+---------------
  0.1 s            | {200: 60}           | 0                                                    | 1             
  0.6 s            | {200: 60}           | 0                                                    | 60            
  0.27 到 0.33 s 随机 | {200: 60}           | 0                                                    | 36            
  三种 gap 下的 60 个请求都没有失败,包括 gap 在 0.3 s 附近的那一组(最后一列的连接数介于 1 和 60 之间,说明有的连接被复用、有的在被后端关掉之后重建了)。人们常说的“后端先关了空闲连接,nginx 还往里发请求,于是出现偶发 502”,在这个实验里没有复现;它需要请求恰好和对端的关闭同时发生,我没有去构造这个窗口,所以这里不下结论

没有复现偶发 502;这种竞争需要请求与关闭同时发生。

分步演示

长连接 keepalive 到上游
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。nginx 是 Homebrew 的 1.31.6(单 worker);后端是我写的原始 TCP HTTP/1.1 服务,按规矩处理 HTTP/1.0 与 Connection: close,并能设置空闲多久就关连接、能数 accept 的连接数。我最初的后端不理会 Connection: close,结果四种配置看起来都复用了连接,所以改成了守规矩的后端;第三节的空闲关闭测试每个 gap 只有 60 个请求
    • 没有验证:旧版本 nginx 的默认值(上面的“默认就复用”只针对 1.31.6);多个 worker 进程各有自己的池;keepalive_time、keepalive_timeout 的更多组合;HTTPS 上游的复用与会话恢复;偶发 502 的竞争窗口(需要精确构造请求与关闭同时到达)
    京ICP备2026067502号-1京公网安备11010502063487号