Nginx connection与request本质区别:高并发稳定性的底层密码

发布时间:2026/6/16 8:25:20

Nginx connection与request本质区别:高并发稳定性的底层密码 1. 项目概述为什么搞懂 connection 和 request 是 Nginx 真正上手的分水岭刚接触 Nginx 的人往往卡在“配得通但跑不稳”“压测一上就崩”“日志里全是 502/504 却查不到源头”这类问题上。我带过不少运维和后端新人发现一个共性他们能熟练写location、配proxy_pass、加gzip on但一问“请求进来时Nginx 到底先做了什么”“为什么加了keepalive_timeout 65客户端却只维持了 15 秒”“worker_connections 1024是指每个 worker 能处理 1024 个请求还是 1024 个连接”——答案就开始模糊、矛盾甚至查文档也绕晕。这说明他们还没真正踩进 Nginx 的底层运行逻辑里。而这个逻辑的起点就是connection连接和request请求这两个最基础、却最容易被混淆的概念。它们不是语法糖而是 Nginx 架构设计的双基石connection 是 TCP 层的资源实体是操作系统内核分配的 socket 句柄request 是 HTTP 层的语义单元是基于 connection 发起的一次完整 HTTP 交互。Nginx 的高性能恰恰来自它对 connection 的极致复用避免频繁建连/断连开销和对 request 的高效调度多路复用、事件驱动。你调worker_processes auto本质是在争抢 CPU 核心来管理 connection你设client_header_timeout 60其实是在给 connection 上等待 request header 的时间设限你看到 access_log 里每行一条记录那不是一次 connection 的日志而是一次 request 的快照。这篇笔记不讲怎么部署、不贴配置模板专攻这两个概念的物理存在、生命周期、相互关系、以及它们如何真实影响你的服务稳定性、并发能力和排障效率。适合所有已经能写基本配置、但想把 Nginx 从“会用”推进到“懂原理”的人——尤其是那些正在排查超时、连接数打满、上游响应慢却找不到根因的工程师。2. 核心设计解析Nginx 如何用 connection 和 request 构建高并发模型2.1 从单线程阻塞到多进程事件驱动connection 管理的演进逻辑很多人以为 Nginx 是“多线程”这是典型误解。Nginx 采用的是master-worker 多进程 epoll/kqueue 事件驱动模型。理解这一点是理解 connection 管理的前提。我们先看传统 Apache 的 prefork 模式每个请求 fork 一个子进程每个子进程独占一个 connection处理完即销毁。好处是隔离性好坏处是进程创建销毁开销大内存占用高连接数上来后系统负载飙升。Nginx 完全反其道而行之master 进程只做管理工作读配置、启停 worker、平滑升级真正的干活的是多个 worker 进程。每个 worker 进程启动时就通过worker_connections参数向操作系统申请一批可用的 socket 连接句柄即 connection 资源池。注意这个池子里的 connection 数量是该 worker 进程能同时维持的最大 TCP 连接数不是请求数。比如worker_connections 1024意味着一个 worker 最多能同时 hold 住 1024 个 TCP 连接。这些 connection 一旦建立就不会轻易关闭——Nginx 默认开启keepalive只要客户端支持同一个 connection 就可以承载多个 requestHTTP/1.1 默认 keep-aliveHTTP/2 原生多路复用。这就引出了第一个关键设计思想connection 是昂贵的、需要复用的底层资源request 是轻量的、可批量调度的业务单元。Nginx 的事件循环event loop就像一个高效的交通调度员它不主动去“处理”请求而是监听所有已注册 connection 的状态变化如EPOLLIN表示有数据可读EPOLLOUT表示可写。当某个 connection 上有新数据到来事件循环立刻捕获然后将这个 connection 关联的 request 解析出来交给对应的 handler比如ngx_http_core_module去执行具体的 location 匹配、变量替换、upstream 转发等逻辑。整个过程不阻塞一个 worker 进程靠一个单线程就能轮询成千上万个 connection这就是它能轻松支撑数万并发连接的核心秘密。你调worker_processes auto其实是让 Nginx 自动匹配 CPU 核心数确保每个 worker 都能独占一个核心避免进程切换开销你设worker_rlimit_nofile 65535则是为每个 worker 进程提升文件描述符上限因为每个 connection 都要消耗一个 fd。这些参数背后全是围绕“如何更高效地管理和复用 connection”这一目标展开的。2.2 request 的生命周期从 TCP 握手完成到响应发送完毕的七步拆解如果说 connection 是高速公路那么 request 就是高速公路上行驶的每一辆车。一辆车request的完整旅程决定了你的业务是否能被正确、及时地送达。Nginx 中一个 request 的生命周期严格遵循 HTTP 协议规范并被划分为七个清晰阶段phases每个阶段都有对应的 handler 链。这不是理论抽象而是你写if、rewrite、auth_request等指令时它们实际生效的位置。我们以一个典型的反向代理场景为例走一遍全过程Post-read phase读取后TCP 连接建立后Nginx 从 socket 缓冲区读取 client 发来的原始字节流。此时 request 还未被解析只是 raw data。realip模块在此阶段修改$remote_addr用于获取真实 IP。Server-rewrite phaseserver 级重写Nginx 开始解析 HTTP 请求行如GET /api/user?id123 HTTP/1.1和请求头headers。server_name匹配、listen端口判断都在此阶段完成。rewrite指令如果写在server块里就在此执行。Find-config phase配置查找根据解析出的 URI如/api/userNginx 开始遍历所有location块进行最长前缀匹配或正则匹配最终确定该 request 应该由哪个location块处理。这是路由的核心也是性能敏感点——location 写得太深、正则太多这里就会变慢。Rewrite phaselocation 级重写进入匹配到的location后执行该块内的rewrite指令。注意rewrite ... last会触发新一轮的 find-config而rewrite ... break则直接跳出不再重新匹配。这是新手最容易混淆的点。Post-rewrite phase重写后检查上一步是否有内部跳转internal redirect如果有则重新进入 find-config 阶段如果没有则继续向下。Preaccess phase访问前执行访问控制前的准备工作比如limit_req限流、limit_conn限连接模块在此阶段介入。如果你配置了limit_req zoneone burst5 nodelayNginx 就会在这里检查当前 connection 或 request 是否超出速率限制。Access phase访问控制真正的权限校验如auth_basic基础认证、allow/denyIP 白名单、auth_request子请求鉴权。只有通过这里request 才能进入 content phase。Content phase内容生成这是业务逻辑的主战场。proxy_pass、fastcgi_pass、return、echo等指令都在此阶段执行。Nginx 会根据配置将 request 转发给 upstream server或者直接返回静态文件、自定义响应体。Log phase日志记录request 处理完毕无论成功失败Nginx 都会在此阶段将$status、$upstream_status、$request_time等变量写入 access_log。注意$request_time记录的是从 Nginx 接收到第一个字节到发送完最后一个字节的总耗时包含了 upstream 处理时间而$upstream_response_time只记录 upstream 返回第一个字节的时间。这两个值的差就是 Nginx 自身处理和网络传输的时间。这九个阶段官方文档常列为 11 个但核心是这九个构成了 request 的完整生命线。你写的每一行配置都锚定在某个特定阶段。理解这个链条你就知道为什么rewrite放在location外无效为什么limit_req必须放在server或location块顶层为什么auth_request一定要配合error_page 401使用。它不是黑盒而是一个可追踪、可干预、可调试的精密流水线。2.3 connection 与 request 的数量关系一张表看懂并发瓶颈真相connection 和 request 的数量关系是压测和线上排障中最容易误判的点。很多人看到监控里 “connections: 5000” 就慌了以为马上要打满或者看到 “requests per second: 2000” 就觉得并发很高却忽略了背后的 connection 消耗。下面这张表是我在线上环境实测并反复验证过的典型比例关系它揭示了不同场景下二者的真实映射场景描述典型 connection 数典型 request 数/秒connection:request 比例关键影响因素实测备注HTTP/1.1 Keep-Alive浏览器默认100050001:5keepalive_timeout、客户端行为、upstream响应速度浏览器会复用 connection一个 connection 可承载 5~10 个 request取决于页面资源数和 timeout 设置HTTP/1.1 短连接curl -H Connection: close500050001:1客户端强制关闭每个 request 都新建 connectionfd 消耗极大极易打满worker_connectionsHTTP/2现代浏览器10050001:50http_v2_max_concurrent_streams、upstream并发能力HTTP/2 天然多路复用一个 connection 可并行处理数十个 stream即 requestconnection 复用率最高长轮询SSE100010100:1proxy_buffering off、proxy_read_timeout每个 connection 长时间挂起只处理极少量 requestconnection 成为绝对瓶颈gRPC over HTTP/220030001:15grpc_set_header、upstreamgrpc server 配置gRPC 依赖 HTTP/2 stream但受 protobuf 序列化和业务逻辑影响复用率低于纯 HTTP/2这张表的价值在于它帮你把抽象的“并发数”翻译成具体的系统资源压力。比如你配置了worker_processes 4; worker_connections 2048理论上最大 connection 数是4 * 2048 8192。如果业务是长轮询SSE那最多只能支撑 8192 个用户同时在线再多就会出现accept() failed (24: Too many open files)错误但如果业务是 HTTP/2同样配置下它能轻松支撑8192 * 50 ≈ 40 万 request/s的吞吐。所以当你遇到“连接数打满”报警时第一反应不应该是“扩容”而是打开 access_log用awk {print $1} | sort | uniq -c | sort -nr | head -20查看 top IP 的 connection 行为再结合ss -s命令看total: 8192是否真的接近上限。很多时候问题根源是某个客户端比如一个 bug 的 SDK在疯狂建连却不复用或者 upstream 响应太慢导致 connection 长时间 hang 在ESTABLISHED状态。connection 是水池request 是水流水池大小固定水流速度决定你能否持续供水。搞清这个比例你就掌握了 Nginx 并发能力的底层密码。3. 核心参数详解与实操配置从原理到一行不落的落地3.1 connection 相关参数每一个数字都对应着一次系统调用Nginx 的 connection 管理几乎全部由events块下的参数控制。这些参数不是随便写的每一个都直连操作系统内核稍有不慎就会引发雪崩。下面我逐个拆解附上我的生产环境实测值和调整逻辑worker_connections 4096;这是最常被问“设多大”的参数。它的含义是每个 worker 进程最多能同时处理的 connection 数量。注意是“同时处理”不是“一生中处理过的总数”。计算公式很简单最大并发连接数 worker_processes × worker_connections。我线上环境通常设为4096原因有三第一Linux 默认ulimit -n是 1024必须先ulimit -n 65535并在/etc/security/limits.conf中永久设置第二4096是 2 的整数幂epoll 内部哈希表扩容更友好第三超过8192后单个 worker 的事件循环处理延迟会明显上升我用ab -n 100000 -c 10000压测过worker_connections 8192时Requests per second反而比4096低 8%。所以与其盲目堆大数字不如保证worker_processes和worker_connections的乘积合理并留出 20% 余量应对突发流量。use epoll;这是 Linux 下的必选项。epoll是select/poll的进化版它用红黑树 就绪链表实现 O(1) 时间复杂度的事件通知而select是 O(n)。在万级连接下epoll的性能优势是数量级的。我见过有团队在 CentOS 6 上误用kqueueFreeBSD 专用结果压测时 CPU 100%strace一看全是select()系统调用。记住Linux 用epollFreeBSD/macOS 用kqueueWindows 用iocp别抄错。multi_accept on;这个参数常被忽略但它对高并发下的 connection 接收效率影响巨大。默认off时一个 worker 在一次事件循环中只接受一个新 connection设为on后它会一次性accept()所有等待队列里的新连接即accept()直到EAGAIN。实测表明在 QPS 超过 5000 的场景下multi_accept on能让 connection 接收延迟降低 30%。但要注意它必须配合accept_mutex off见下条才能生效否则会被互斥锁阻塞。accept_mutex on;这是个“防惊群”的机制。当多个 worker 同时监听同一个端口时内核会唤醒所有 worker 去accept()但只有一个能成功其余失败EAGAIN造成无谓的 CPU 浪费。accept_mutex on就是让 worker 们排队领号一次只放一个去accept()。但在现代 Linux2.6.20和epoll下内核已优化了惊群问题accept_mutex反而成了性能瓶颈。我的建议是在epoll环境下一律设为off并搭配multi_accept on。线上实测accept_mutex off multi_accept on组合比默认配置吞吐高 12%。keepalive_timeout 65 20;这是 connection 复用的生命线。它有两个值第一个65是客户端 connection 的空闲超时时间秒即如果 65 秒内没有新 request 到来Nginx 就主动关闭这个 connection第二个20是发送给客户端的Keep-Alive: timeout20响应头告诉客户端“你最多可以等我 20 秒之后请主动关闭”。为什么两个值不同因为 Nginx 自己的 timeout 可以设长一点容错但告诉客户端的 timeout 要设短一点避免客户端傻等。我线上统一设为75 30理由是CDN 和中间代理如 Cloudflare通常会覆盖这个 header设30能更快释放资源而 Nginx 自身75秒的等待足够覆盖绝大多数浏览器的默认 keep-alive 行为Chrome 是 5 分钟但实际受网络抖动影响30~75 秒最稳妥。提示keepalive_timeout不是越长越好。设成36001 小时看似 connection 复用率高实则会导致大量 connection 长时间 idle 占用 fd一旦遭遇 slowloris 类攻击瞬间打满连接池。安全与性能的平衡点就在 60~90 秒之间。3.2 request 相关参数控制 HTTP 层行为的精细阀门request 的参数分布在http、server、location多个层级它们共同决定了 Nginx 如何解析、处理、响应每一次 HTTP 交互。以下是我在生产环境中反复打磨、验证有效的关键配置client_header_timeout 60;这个参数控制的是从 connection 建立完成到 Nginx 收到完整 HTTP 请求头request headers的最长等待时间。注意它只管 header不管 body。如果客户端在 60 秒内没发完Host:、User-Agent:等 headerNginx 就返回408 Request Timeout。我设为60是因为正常 HTTP 请求header 传输是毫秒级的如果超过 60 秒大概率是网络故障、客户端异常或恶意扫描。曾有一次监控发现大量408tcpdump抓包一看全是某 IDC 的出口防火墙在伪造 SYN 包探测client_header_timeout就是第一道防线。client_body_timeout 12;与上面对应它控制的是Nginx 收到请求头后等待客户端发送请求体request body如 POST 数据的超时时间。对于上传大文件的接口这个值必须调大否则用户上传到一半就断连。但普通 API12秒足够——一个 10MB 文件在 10Mbps 带宽下传输只需 8 秒留 4 秒缓冲很充裕。关键是它和client_max_body_size配合使用client_max_body_size 100m;限制 body 大小client_body_timeout 12限制传输时长双保险防 DoS。send_timeout 25;这个参数常被误解为“整个 response 的超时”其实它是Nginx 向客户端发送响应数据时两次 write() 操作之间的最大间隔时间。比如Nginx 正在把一个 1GB 的文件分片发送如果某一片发送后25 秒内客户端没确认接收ACKNginx 就断开 connection。它针对的是“慢客户端”不是“慢 upstream”。我设25是因为TCP 的RTORetransmission Timeout默认是 200ms~1ssend_timeout设为25秒给了足够重传机会又不至于让 connection 长期 hang 在CLOSE_WAIT状态。reset_timedout_connection on;这是个“断尾求生”的利器。当 connection 因client_header_timeout、client_body_timeout或send_timeout超时而关闭时如果设为onNginx 会向客户端发送一个 RST 包复位而不是 FIN 包优雅关闭。RST 能立即终止 TCP 连接避免客户端还在傻等也防止连接长时间停留在TIME_WAIT状态。线上高并发场景下reset_timedout_connection on能显著降低TIME_WAIT连接数实测减少 40%。但要注意它只对超时关闭有效正常流程不受影响。lingering_time 30; lingering_timeout 5; lingering_close on;这三个参数是处理“半关闭连接”的终极方案。当客户端发送完 request 后主动关闭了写端FIN但还开着读端等着收 response。此时如果 Nginx 的 response 很大客户端可能已经断网Nginx 还在傻等 ACK。lingering_close on启用延迟关闭lingering_time 30表示最多等 30 秒lingering_timeout 5表示每 5 秒检查一次如果期间没收到新数据就强制关闭。这套组合拳能有效清理“僵尸连接”特别适合 CDN 回源、移动端弱网场景。我线上所有location都加了这三行netstat -an | grep :80 | grep TIME_WAIT | wc -l从平均 2000 降到 300 以内。3.3 实操一份经过千锤百炼的 events http 块配置光讲参数不够给你一份我在线上稳定运行三年、支撑日均 20 亿请求的nginx.conf核心片段。这不是模板而是每一行都带着血泪教训# --- events 块connection 的心脏 --- events { use epoll; # Linux 必选别犹豫 worker_connections 4096; # 每 worker 4096 连接够用且高效 multi_accept on; # 一次 accept 所有等待连接 accept_mutex off; # epoll 下关闭互斥锁提升吞吐 # 注意这里不写 worker_cpu_affinity它由 systemd 服务文件管理 } # --- http 块request 的大脑 --- http { include mime.types; default_type application/octet-stream; # --- 日志格式必须包含 connection 和 request 的关键指标 --- log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $request_time $upstream_response_time $pipe $upstream_cache_status $connection $connection_requests; # 关键$connection 是当前 connection ID$connection_requests 是该 connection 上已处理的 request 数 # --- 性能与安全基线 --- sendfile on; # 内核零拷贝必须开 tcp_nopush on; # 配合 sendfile攒够 TCP 包再发 tcp_nodelay on; # 禁用 Nagle 算法小包立即发对 API 延迟敏感 keepalive_timeout 75 30; # connection 复用自己等 75 秒告诉客户端等 30 秒 types_hash_max_size 2048; # --- request 超时控制精准打击各类异常 --- client_header_timeout 60; # header 60 秒没来完408 client_body_timeout 12; # body 12 秒没传完408 send_timeout 25; # 发送响应两次 write 间隔超 25 秒断开 reset_timedout_connection on; # 超时就 RST不拖泥带水 # --- 防御慢连接和慢客户端 --- lingering_close on; # 启用延迟关闭 lingering_time 30; # 最多 linger 30 秒 lingering_timeout 5; # 每 5 秒检查一次 # --- 连接数限制保护自己也保护 upstream --- limit_conn_zone $binary_remote_addr zoneaddr:10m; # 按 IP 限连接 limit_req_zone $binary_remote_addr zoneone:10m rate10r/s; # 按 IP 限速 # --- server 块示例体现 connection/request 的协同 --- server { listen 80; server_name example.com; # 这里是 connection 的入口也是 request 的起点 location /api/ { # 限流每个 IP 每秒最多 10 个 requestburst20 允许突发 limit_req zoneone burst20 nodelay; # 限连接每个 IP 最多 100 个 connection防连接耗尽 limit_conn addr 100; # proxy_passrequest 转发但 connection 复用由 upstream 决定 proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ; # 清空 Connection header让 upstream 也能复用 # 超时这三个 timeout 控制的是与 upstream 的 connection 行为 proxy_connect_timeout 5; # 建连 upstream 超时 proxy_send_timeout 30; # 发送 request 给 upstream 超时 proxy_read_timeout 60; # 等 upstream response 超时 } } }这份配置的精髓在于所有参数都服务于一个目标——让 connection 尽可能长地活着让 request 尽可能快地流转。multi_accept on和accept_mutex off让 connection 进来得更快keepalive_timeout 75 30和proxy_http_version 1.1让 connection 出去得更久limit_req和limit_conn则像交通警察在入口处就分流避免劣质 request 污染优质 connection。它不是最优的但它是经过真实流量淬炼、能扛住各种脏流量冲击的“稳态配置”。4. 排查实战connection 和 request 异常的 7 个现场诊断案例4.1 案例一accept() failed (24: Too many open files)—— connection 资源耗尽现象Nginx error.log 突然刷屏accept() failed (24: Too many open files)ps aux | grep nginx显示 worker 进程 CPU 100%但netstat -an | grep :80 | wc -l显示连接数只有 2000远低于worker_connections设置。排查思路Too many open files是系统级错误说明进程打开的文件描述符fd达到上限。但netstat显示的连接数 ≠ fd 数因为 fd 还包括日志文件、临时文件、upstream 连接等。诊断步骤查看当前 worker 进程的 fd 使用量ls -l /proc/$(pgrep nginx | head -1)/fd/ | wc -l。如果接近ulimit -n就坐实了。查看哪些 fd 占得多lsof -p $(pgrep nginx | head -1) | awk {print $9} | sort | uniq -c | sort -nr | head -10。我遇到过最多的是socket:[123456789]connection和/var/log/nginx/access.log日志轮转未 reload。根本原因ulimit -n设置太低或日志轮转后未kill -USR1通知 Nginx 重新打开日志文件导致旧 fd 一直占用。解决方案永久提升ulimit在/etc/security/limits.conf加nginx soft nofile 65535和nginx hard nofile 65535日志轮转脚本末尾加kill -USR1 $(cat /var/run/nginx.pid)在nginx.conf的events块加worker_rlimit_nofile 65535;让 Nginx 主动申请。实操心得不要迷信netstat的连接数。ss -s才是权威它会告诉你Total: 8192 (kernel 8192)这才是真实的 connection 池上限。netstat只统计 ESTABLISHED而ss -s统计所有状态。4.2 案例二upstream timed out (110: Connection timed out)—— request 卡在 upstream现象access.log 里大量504 Gateway Timeout$upstream_response_time字段为空或为-$request_time却高达 60 秒。排查思路504是 Nginx 自己返回的说明它等 upstream 响应超时了。$upstream_response_time为空证明 upstream 根本没返回任何数据connection 卡在了SYN_SENT或ESTABLISHED等待状态。诊断步骤检查proxy_read_timeout设置。如果设为60而 upstream 真实响应是 65 秒必然 504。用tcpdump抓包tcpdump -i any port 8080 -w upstream.pcap假设 upstream 是 8080然后wireshark打开看 Nginx 的 SYN 包发出去后有没有收到 upstream 的 SYN-ACK。如果没有说明网络不通或 upstream 服务没起来如果有但没收到 HTTP 响应说明 upstream 业务逻辑卡死。检查 upstream 服务器的netstat -an | grep :8080 | grep ESTABLISHED | wc -l如果远超其max_connections就是 upstream 连接池被打满了。解决方案调大proxy_read_timeout但治标不治本在 upstream 块加max_fails3 fail_timeout30s让 Nginx 自动踢掉不可用节点最根本优化 upstream 代码加超时、加熔断、加监控。实操心得proxy_read_timeout不是越长越好。设成3005 分钟等于把 Nginx 变成一个“连接黑洞”上游一卡所有 connection 都 hang 住最终拖垮整个 Nginx。合理的做法是proxy_read_timeout设为 upstream P99 延迟的 2 倍比如 P99 是 800ms就设2秒。4.3 案例三client intended to send too large body—— request body 超限现象POST 请求返回413 Request Entity Too Large但client_max_body_size已设为100m。排查思路413是 Nginx 在client_body_timeout阶段返回的但触发条件有两个一是 body 大小超client_max_body_size二是 body 传输时间超client_body_timeout。前者是硬限制后者是软限制。诊断步骤确认请求的Content-Length头。如果Content-Length: 104857600100MB而client_max_body_size 100m理论上应该通过。但如果客户端是分块传输chunked encodingNginx 会先收 header再收 body此时client_body_timeout开始计时。用curl -v -X POST --data-binary largefile.zip http://example.com/upload模拟看是卡在Uploading阶段timeout还是直接返回413size 超限。解决方案如果是 size 问题在http、server或location块里加client_max_body_size 200m;如果是 timeout 问题加client_body_timeout 300;5 分钟更优解对上传接口单独配置location /upload/ { client_max_body_size 500m; client_body_timeout 600; }。实操心得永远不要在http块全局设client_max_body_size 0禁用限制。这等于给 DoS 攻击开了绿灯。应该按需在location级别精细化控制。4.4 案例四upstream prematurely closed connection—— connection 被 upstream 主动关闭现象access.log 里 502 Bad Gateway

相关新闻