尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Linux Nginx 怎么测试 keepalive 长连接是否成功复用
前言调优时常遇到这个问题给upstream加上keepalive 32之后怎么证明连接真的被复用了改完配置看不到任何报错但心里没底——万一每个请求还是重建 TCP 连接那这次调优等于白做。另一种情况更隐蔽压测时上游端口耗尽Cannot assign requested address或者后端连接数异常高怀疑就是 keepalive 没生效却拿不出证据。难点在于Nginx 没有「上游连接被复用了几次」这种可以直接读出来的变量。$upstream_addr只告诉你请求被转发到了哪台后端$upstream_status只告诉你后端返回了什么状态码它们都不反映「这条 TCP 连接是新的还是旧的」。所以验证只能靠间接证据。还有一点要先分清楚「客户端到 Nginx」和「Nginx 到上游」是两条独立的链路各自的 keepalive 是分开配置、分开验证的。很多人只想着上游那一侧其实下行那一侧有现成的日志变量可以直接看到复用次数。本文基于 nginx 1.24.x涉及版本差异的指令会单独标注。一、两条链路的配置与可观测性对照维度客户端 ↔ Nginx下行Nginx ↔ 上游上行开启方式默认开启keepalive_timeout控制必须在upstream块里写keepalive N且要配合两个代理头关键指令keepalive_timeout、keepalive_requests、keepalive_timekeepalive、keepalive_requests1.15.3、keepalive_timeout1.15.3、keepalive_time1.19.10日志变量$connection、$connection_requests可直接观察没有对应变量只能间接验证验证难度低看日志即可中需要抓包或跟踪系统调用下行的这两个变量值得单独说明它们是整套验证里最省事的手段$connection连接的唯一序号。每建立一条新的 TCP 连接这个序号就 1。$connection_requests当前这条连接上已经处理过的请求数包含本次。同一条连接上这个值从 1 一路涨上去就是复用发生的直接证据。二、下行验证日志变量加 curl先给一个专用日志格式只加两行配置就能拿到复用证据log_format keepalive_demo $remote_addr [$time_local] conn$connection conn_reqs$connection_requests status$status $request; server { listen 80; server_name localhost; access_log /var/log/nginx/keepalive.access.log keepalive_demo; location / { root /usr/share/nginx/html; index index.html; } }sudo nginx -t sudo systemctl reload nginxcurl 默认会在同一条连接上连续发多个请求抓-v的输出可以肉眼确认curl -sv -o /dev/null \ http://127.0.0.1/ \ -o /dev/null http://127.0.0.1/ \ -o /dev/null http://127.0.0.1/ 21 \ | grep -Ei connected to|re-using|left intact期望看到的关键行curl 版本不同措辞略有差异* Connected to 127.0.0.1 (127.0.0.1) port 80 * Re-using existing connection with host 127.0.0.1 * Re-using existing connection with host 127.0.0.1 * Connection #0 to host 127.0.0.1 left intact只有一次Connected to后面两次是Re-using说明下行复用是通的。再看日志里同一条连接上的计数tail -n 3 /var/log/nginx/keepalive.access.log # 期望形如conn 值相同conn_reqs 依次递增 # 127.0.0.1 [29/Sep/2026:10:00:00 0800] conn15 conn_reqs1 status200 GET / HTTP/1.1 # 127.0.0.1 [29/Sep/2026:10:00:00 0800] conn15 conn_reqs2 status200 GET / HTTP/1.1 # 127.0.0.1 [29/Sep/2026:10:00:00 0800] conn15 conn_reqs3 status200 GET / HTTP/1.1想要一个可量化的对比用ab的-k选项开启 keep-alive跑一轮然后统计日志里出现过多少个不同的conn值# -k 表示使用 HTTP Keep-Alive ab -k -n 1000 -c 10 http://127.0.0.1/ /dev/null # 统计不同连接的数量把输出重定向到空文件后不影响日志写入 grep -o conn[0-9]* /var/log/nginx/keepalive.access.log | sort -u | wc -l判断标准不是某个具体的最大值而是对照关系开-k时不同连接数应该远小于请求总数量级接近并发数-c而把-k去掉重跑同样的命令不同连接数会接近 1000。这两个数字都要你自己在目标环境上跑出来别人机器上的数值说明不了你的配置是否生效。想更贴近真实并发可以用wrkwrk -t2 -c10 -d10s http://127.0.0.1/三、上行验证抓包数 SYN上行这一侧没有日志变量最可靠的办法是数 TCP 的 SYN 包。真正的「新建连接」一定会有一个不带 ACK 标志的 SYN 包复用则不会。配置部分先写对这是前提upstream app_backend { server 127.0.0.1:8080; # 每个 worker 进程缓存的空闲连接数上限注意是「空闲」不是总数 keepalive 32; # 单条连接最多处理多少个请求nginx 1.15.3 keepalive_requests 1000; # 空闲连接在池子里保留多久nginx 1.15.3 keepalive_timeout 60s; # 单条连接的最长存活时间nginx 1.19.10默认 1h keepalive_time 1h; } server { listen 80; location /api/ { proxy_pass http://app_backend; # 这两个是上行 keepalive 生效的必要条件缺一不可 proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }proxy_set_header Connection 的作用是把请求里的Connection头清空。Nginx 默认会向上游发送Connection: close官方文档里明确写了默认只重定义Host和Connection两个字段带着这个头后端处理完就会关闭连接池子里永远留不住东西。验证步骤两个终端配合# 终端 1只匹配「带 SYN 且不带 ACK」的包也就是真正的建连 sudo tcpdump -i lo -n -l \ tcp port 8080 and tcp[tcpflags] tcp-syn ! 0 and tcp[tcpflags] tcp-ack 0# 终端 2发 200 个请求 for i in $(seq 1 200); do curl -s -o /dev/null http://127.0.0.1/api/health done判读方式如果复用生效终端 1 打印的建连数量会远远小于 200大致与同一时刻的并发数同量级如果每个请求都新建连接打印数量会接近 200。想看反例把proxy_http_version 1.1;和proxy_set_header Connection ;两行注释掉、reload再跑一遍同样的循环做对比。另一个观察角度是看当前的 ESTABLISHED 连接数请求发完之后池子里的空闲连接仍然保持 ESTABLISHED 状态直到keepalive_timeout到期。ss -tn state established ( dport :8080 or sport :8080 )还可以直接跟踪系统调用看 Nginx worker 到底有没有在connect()# 需要 rootRHEL 系先 dnf install strace sudo strace -f -e traceconnect \ -p $(pgrep -f nginx: worker process | head -n 1) 21 \ | grep -F :8080跟踪期间只发请求、不发新连接时应该看不到新的connect()调用。要注意两点strace本身有性能开销只在测试实例上用Nginx 有多个 worker请求可能落到没被跟踪的那个进程上所以这个方法的结论不如抓包可靠。四、最容易「以为生效了」的情况排查这类问题有一个固定顺序先确认配置在正确的上下文里再确认两个必需的代理头都在最后才去看池子大小和超时。前两步占问题的大多数尤其是第一个keepalive只在upstream块里有效写在location里 Nginx 会直接报错拒绝启动proxy_http_version 1.1与proxy_set_header Connection 必须出现在每一个用到该 upstream 的 location 里漏掉一个那条路径上的请求就全是短连接上游后端的空闲超时如果比 Nginx 的keepalive_timeout短会出现「池子里的连接其实早被后端关了」的情况Nginx 拿到一条半关闭的连接去发请求日志里就会出现upstream prematurely closed connection while reading response header from upstream并返回 502。所以 Nginx 侧的keepalive_timeout应当小于后端服务的空闲超时。常见坑点只加了keepalive参数没加两个必需的代理头❌upstream里写了keepalive 32;location 里只有proxy_pass✅ 必须同时写proxy_http_version 1.1;和proxy_set_header Connection ;。Nginx 默认向上游发Connection: close不清理这个头池子永远是空的。试图用$upstream_addr判断复用❌ 看到日志里$upstream_addr一直是同一个地址就断定连接复用了 ✅$upstream_addr只说明「路由到了哪台后端」负载均衡到同一台后端也可以每次新建连接。上行的复用只能靠抓包数 SYN或跟踪connect()验证。压测工具没开 keep-alive得出错误结论❌ 用ab -n 1000 -c 10 http://...没有-k测完发现每个请求一条连接断定 Nginx 配置有问题 ✅ab不加-k就是每次都新建连接这是工具的行为不是 Nginx 的行为。测下行复用要么加-k要么用 curl 一条命令发多个 URL。Nginx 的空闲超时比后端长❌keepalive_timeout 300s;而后端服务的空闲超时是 60 秒 ✅ 出现upstream prematurely closed connection加 502 的经典组合。让 Nginx 侧的空闲超时明显小于后端例如 Nginx 60s、后端 75s或者在后端统一调整。keepalive数值按「总连接数」估❌ 并发 500写keepalive 500;以为够用 ✅keepalive N是每个 worker 进程的空闲连接缓存上限总量还要乘worker_processes。这个值设太小不会出错只是复用率上不去设太大则会长期占着后端连接不释放需要结合后端连接数上限一起定。在location里写keepalive❌location /api/ { keepalive 32; proxy_pass ...; }✅keepalive只允许出现在upstream上下文写错位置nginx -t会直接报keepalive directive is not allowed here先过配置检查再谈调优。看到ss里连接数没变就以为没生效❌ 抓包看到请求期间没有新 SYN但ss里的 ESTABLISHED 数量也几乎没变怀疑配置没生效 ✅ 恰好相反这正是复用生效的表现连接一直在池子里待着不新建也不关闭。要看到状态变化可以观察经过keepalive_timeout之后连接数才回落。压测流量被负载均衡分散只测到一台后端❌upstream里有两台后端抓包只盯着127.0.0.1:8080另一台的 8081 没看结论不完整 ✅ 抓包表达式要覆盖全部上游端口或者临时把 upstream 缩到单台再做验证最后再恢复多台配置复测。总结验证目标方法判读依据下行复用是否生效日志里加$connection/$connection_requests同一个conn值上conn_reqs递增下行复用的量化对比ab -k与不带-k各跑一轮统计不同conn数两者数量级差异明显上行复用是否生效tcpdump数「带 SYN 不带 ACK」的包建连数远小于请求数上行复用的补充证据ss观察 ESTABLISHED 连接是否长期驻留请求结束后连接仍存在上行复用的直接证据strace -e traceconnect跟踪 worker稳定期看不到新的connect()记住三句话下行看日志变量上行只能靠抓包数 SYN上游 keepalive 生效的前提是两个代理头缺一个就等于没配Nginx 侧的空闲超时必须小于后端的空闲超时否则省下来的连接会以 502 的形式还回来。验证时一定要准备一组「配置正确」和「配置故意写错」的对照数据自己跑出来的对照关系才是可信的证据。
RELATED

相关推荐

C++中的Primer拷贝控制和资源管理详解

C++中的Primer拷贝控制和资源管理详解

贝控制和资源管理通常,管理类外资源的类必须定义拷贝控制成员,这种类需要通过析构函数来释放对象所分配的资源。一旦一个类需要析构函数,那么它儿乎肯定也需要一个拷贝构造函数和一个拷贝赋值运算符。为了定义这些成员,我们首先必…

📅 2026/10/4 14:48:09
c++中移动语义和完美转发及易错点

c++中移动语义和完美转发及易错点

C 中的移动语义和完美转发是 C11 引入的两个重要特性,它们分别用于提高性能和灵活性。移动语义(Move Semantics):移动语义允许有效地将资源(如堆上分配的内存或其他资源)从一个对象转移到另一个对象,而不是…

📅 2026/10/4 14:48:09
android.database.StaleDataException 排查:Cursor 关闭后仍被访问,TaoToken 统一 Key 通道下的复现与修复

android.database.StaleDataException 排查:Cursor 关闭后仍被访问,TaoToken 统一 Key 通道下的复现与修复

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/4 14:48:09
MORE NEWS

更多资讯

📰

stack/queue中的deque

目录 摘要: 一:stack和queue叫适配器的原因 二:stack和queue的写法固定的原因 1:stack实现代码 2:queue实现代码 3:写法固定的原因 三:deque的优缺点 1:deque的优点 2&…

📰

Trae安装后连不上模型?把Base URL改到TaoToken的排查清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Python环境变量配置全攻略:从PATH原理到虚拟环境实战

1. 先说清楚:环境变量到底是个啥说实话,我当年第一次接触环境变量,完全是被安装 JDK 的材料逼着去配的。那时候看不懂 PATH 里一串串用分号隔开的路径,只知道照着教程抄,抄错了就重来,烦得要命。后来自己给…

📰

C语言:系统开发的基石与现代编程的起点

C 简介C语言, 属于一种通用的高级语言, 它最初是由丹尼斯里奇在贝尔实验室设计的, 目的是用于开发UNIX操作系统。C语言在1972年, 首次在DEC PDP - 11计算机上被给予了实现。1978年时, 有这么两个人, 一个叫布莱恩柯林汉(Brian), 另一个叫丹尼斯里奇&…

📰

Learn X in Y Minutes 贡献指南:从文章规范、Frontmatter 配置到本地站点构建全流程

文档教程 【免费下载链接】learnxinyminutes-docs Code documentation written as code! How novel and totally my idea! 项目地址: https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs 点击查看 免费下载 Learn X in Y Minutes(learnxinyminut…

📰

基于Java的人事管理系统:从环境配置到二次开发全攻略

简介:这是一份基于Java Web技术的人事人力资源管理系统完整项目包,面向正在做毕业设计、课程设计或期末大作业的计算机专业学生,也可作为JSPMySQL入门项目的参考范例。压缩包共116个文件,包含69个JSP页面、6个JS脚本、4个CSS样式及…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬