ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nginx反向代理WebSocket长连接配置实战:超时、心跳与缓冲调优

Nginx反向代理WebSocket长连接配置实战:超时、心跳与缓冲调优 最近我被一个实际案例折腾得不轻。一个做实时数据推送的兄弟后端用 Django Channels 写好了 WebSocket 服务本地直连一切正常一放到服务器上用 Nginx 做反向代理连接两分钟就断一次有时候后端推送一个大点儿的 JSON 数据包前端这边干脆收不到。把配置翻来覆去看最后发现全是 Nginx 默认配置惹的祸。Nginx 的定位本来是 HTTP 反向代理和静态资源服务器设计重心放在“快速响应、短连接、低内存占用”上对 WebSocket 这种需要长时间存活、双向流式传数据的协议默认参数几乎处处是坑。这篇文章就围绕 Nginx WebSocket 长连接及数据容量配置这个主题把我实际踩过的坑和最终落地的配置一起讲透。适合正在用 Nginx 反代 WebSocket、或者准备给实时推送、在线协作、聊天室这类服务上线的同学参考也适合刚接手 WebSocket 网关维护、被各种“莫名其妙断连”折磨的运维朋友。1. 连接升级与代理转发Nginx 处理 WebSocket 的底层逻辑1.1 一次完整的 WebSocket 握手发生了什么要理解 Nginx 应该怎么配置先得搞清楚 WebSocket 和普通 HTTP 的核心区别。普通 HTTP 是“一来一回”——浏览器发请求服务器返回响应这个 TCP 连接使命就结束了要么关闭要么被 keep-alive 复用。WebSocket 不一样它的出生方式很特别底层还是 TCP但应用层协议是先发一个带升级意图的 HTTP 请求服务器同意后这个连接就从 HTTP 协议切换成 WebSocket 协议。具体到握手环节客户端会发类似这样的东西GET /ws/chat/ HTTP/1.1 Host: ws.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ Sec-WebSocket-Version: 13服务器如果同意就返回HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbKxOo注意这个 101 状态码它代表“协议切换”。从那之后双方不再按照 HTTP 请求-响应的模型对话而是在这个 TCP 连接上直接收发 WebSocket 数据帧。Nginx 作为反向代理夹在中间它的核心任务有两段。握手阶段把客户端请求里的Upgrade和Connection: Upgrade这两个头原样转发给后端拿到后端的 101 响应后原样回给客户端。握手完成后Nginx 不再解析应用层协议变成一个“透明的管道”把浏览器发来的 TCP 字节流搬运给后端把后端返回的 TCP 字节流搬运给浏览器也就是所谓的隧道模式。这里牵扯出一个容易踩的坑Nginx 默认并不会转递这两个升级头因为Connection头本身属于逐跳头hop-by-hop按 HTTP 规范代理层可以对它做任何处理。如果配置里只写了proxy_pass后端拿到的请求里没有Upgrade头应用框架就会认为这只是一个普通 GET 请求要么返回 400/426要么根本不触发 WebSocket 处理逻辑。浏览器这边表现为WebSocket connection failed或者一直停在CONNECTING状态。1.2 不加 Upgrade 头会怎样两张典型的错配配置我见过太多人卡在这第一步。第一种错配是只写了基本的反向代理location /ws/ { proxy_pass http://backend; }这种配置跑普通 HTTP 没问题但 WebSocket 握手请求到了后端Upgrade头消失后端框架可能直接拒绝或者按普通 GET 处理。Django Channels 的 ASGI 服务器 daphne 遇到这种情况会直接返回 400 错误浏览器控制台立刻报错。第二种错配更隐蔽。有人上网查了攻略写成了这样location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; }看起来没问题确实很多教程就这么写。但这个配置有个副作用Connection upgrade是写死的普通 HTTP 请求经过这个 location 时也会被强制携带Connection: upgrade后端就算不理会这个奇怪的头行为也是不可控的。更规范的做法是用map指令根据客户端实际情况动态决定map $http_upgrade $connection_upgrade { default upgrade; close; }这段配置的逻辑是变量$http_upgrade是 Nginx 内置的取客户端请求头里Upgrade字段的值。如果客户端发了Upgrade: websocket$http_upgrade就非空映射结果就是upgrade如果普通 HTTP 请求没有这个头映射结果就是close。这样一套配置既能代理普通 HTTP也能代理 WebSocket不用区分 location。至于为什么映射成close而不是keep-aliveNginx 社区标准写法就是这么定的。普通 HTTP 请求如果没有特别需要复用连接让 Nginx 和后端之间走短连接反而干净避免连接池里残留状态影响下一个请求。提示Nginx 从 1.3.13 版本开始支持 WebSocket 反向代理太老的版本不支持。现在主流发行版自带的 Nginx 基本都远超这个版本但如果用某些精简版或者自己编译的老版本需要注意升级。写完这两段读者基本掌握了连接升级的原理和最常见错误。接下来就进入长连接保活的深水区。2. 长连接保活超时参数与心跳机制的配合艺术2.1 proxy_read_timeout 的真实计算方式连接升级成功WebSocket 隧道建立这还不算完。通常 60 秒之后业务方就会发现连接断了。刚开始我还以为是后端服务崩了后来查了一圈发现是 Nginx 的proxy_read_timeout在作祟。这个参数官方文档的解释是“定义从代理服务器读取响应的超时时间”默认 60 秒。关键点在于它不是整个连接的总超时而是“两次读操作之间”的间隔。也就是说Nginx 在这段时间内只要没有从后端读到任何数据就主动判定连接空闲然后掐断。可以类比成打电话你给朋友打视频电话如果对面连续 60 秒完全不出声不出画面运营商就可能判定通话已经结束把线路挂断。WebSocket 连接建立之后如果业务上双方都不主动发数据这条 TCP 连接就处于纯空闲状态到了 60 秒这个阈值Nginx 果断断连。所以很多实时推送服务上线后前端每隔两分钟重连一次后端日志里看到大量连接被关闭大概率就是这里的问题。解决办法也简单把超时时间调大proxy_read_timeout 3600s; proxy_send_timeout 3600s;proxy_send_timeout是定义“向后端发送请求”的超时对 WebSocket 的实际影响相对小一些但为了保险一般两个一起调。我见过有人只调proxy_read_timeout结果断连问题依旧后来发现是proxy_send_timeout同时也在起作用所以这两兄弟最好一起处理。2.2 心跳间隔的设计必须小于超时时间吗调大超时能解决一时的问题但治标不治本。真正的长连接保活方案是在应用层实现 WebSocket 心跳机制也就是定时发送 ping/pong 帧作用有两个一是让链路一直有数据流动防止中间设备按空闲超时回收连接二是探测对端是否还活着比如客户端断网、进程崩溃但 TCP 连接还挂着。这里有个非常容易犯的错误就是“只管发心跳不管方向”。Nginx 的proxy_read_timeout判断的是“从上游读取数据”也就是后端到 Nginx 这个方向。如果客户端每 30 秒发一个 ping但后端不回任何数据Nginx 看到的仍然是“上游 60 秒没有发数据”照样断连。正确的心跳设计必须考虑方向至少保证后端到客户端的这条路径上每段时间间隔内有数据流动。实际操作中我一般按这样设计后端每 25~30 秒主动向客户端发送一次 WebSocket ping 帧或者业务心跳消息。proxy_read_timeout设置为 120 秒保证即使丢了一两个心跳包也有余量。客户端收到 ping 后回复 pong同时客户端侧也要做超时判断比如 60 秒没收到后端任何消息就主动发起重连。心跳间隔取超时时间的 1/3 到 1/2 是比较稳妥的区间。间隔太短会浪费带宽和 CPU太长则失去了提前发现断线的意义。如果后端框架本身就支持 WebSocket 心跳优先用框架的如果完全自己实现Django Channels 里可以用websocket.ping事件配合异步定时任务来做。2.3 多层代理下的超时传导问题生产环境的链路往往不是浏览器直连 Nginx而是浏览器 → CDN → Nginx → 后端或者浏览器 → 外层 LB → Nginx → 后端。每一层代理都有自己的超时策略只要中间某一层按自己的规则把空闲连接断了整个链路就断了。我遇到过一个月活量不小的在线客服系统浏览器到机房之间有云厂商的负载均衡后面才是 Nginx。所有超时全调成 3600 秒但每隔 90 秒还是准时断线。查了一圈才发现云负载均衡的默认空闲超时只有 90 秒而且这个参数有些产品线还不开放给用户随意改。最后没办法只能把应用层心跳改成每 40 秒一次强制让链路里每一层都能感受到数据在流动才彻底解决。这里也延伸出一个通用经验当你排查“为什么连接总是断”的时候不要只盯着当前这台 Nginx要站在整条链路上想问题。用抓包或者两端同时打日志的方式确认断开动作到底由谁发起谁发的 FIN 包谁发的 RST 包再对症下药。链路里最脆弱的那一层决定了整个连接的最长寿命。3. 数据容量与缓冲配置大帧消息失踪的真相3.1 三个 buffer 指令的管辖范围长连接稳定了又开始出现另一个怪相小消息推送得好好的一旦后端推一个几百 KB 甚至几 MB 的大数据包前端要么长时间收不到要么收到不完整的内容。很多人的第一反应是“Nginx 缓冲区太小”然后一顿乱调proxy_buffers。这里我必须先泼一盆冷水WebSocket 隧道模式下很多针对 HTTP 的缓冲配置根本不生效。先说清楚 Nginx 和缓冲相关的几个指令分别管什么指令默认值作用范围对 WebSocket 的影响proxy_buffer_size4k 或 8k上游响应头的缓冲区握手阶段有效响应头超限会直接报错proxy_buffers8 个 4k/8k上游响应体的缓冲区握手成功后隧道模式基本不生效proxy_busy_buffers_sizeproxy_buffer_size 的两倍已缓冲数据中允许发送给客户端的部分同上隧道模式下影响不大proxy_request_bufferingon是否缓冲整个客户端请求体后再转发WebSocket 数据帧会受影响重点关注意这里的关键是要理解“隧道模式”的含义。WebSocket 握手返回 101 之后Nginx 对这条连接的处理就退化成 TCP 字节流搬运不再解析 HTTP 协议自然也就谈不上 HTTP 响应体的缓冲管理。所以那种“把proxy_buffers调大到 32 个 64k 就能解决 WebSocket 大数据包问题”的说法方向其实不对。3.2 握手响应头超限与 proxy_buffer_size虽然proxy_buffers在隧道模式下线了但proxy_buffer_size在握手阶段仍然有效而且经常出来捣乱。场景是这样有些后端在做 WebSocket 握手时会在 101 响应里带比较大的自定义头比如加密后的 token、协议子协议subprotocol协商结果、时间戳签名、多个Set-Cookie等等。如果这些响应头的总体积超过了proxy_buffer_size的默认值通常 4k 或 8kNginx 就会报这个经典错误upstream sent too big header while reading response header from upstream仔细看这个报错——while reading response header说的是“读取响应头”阶段跟 WebSocket 数据帧没有关系。但是因为握手阶段属于 HTTP 响应这个错误一样会出现。结果就是 WebSocket 握手直接失败浏览器端连不上。解决办法是把proxy_buffer_size调大放在 location 块就行proxy_buffer_size 32k;需要注意proxy_buffer_size是针对每个请求分配的如果设成 128k所有经过这个 location 的请求每个都会多吃 128k 内存。并发一万连接就是 1.28GB 的额外内存消耗。所以务必要结合实际情况设置一般 16k 到 32k 足够应付绝大多数握手场景不要盲目贪大。3.3 实时转发与 proxy_request_buffering 的取舍握手过了隧道建立了还有一个隐藏的缓冲开关容易坑人proxy_request_buffering默认是on。这个指令管的是“把客户端发来的请求体完整缓冲后再转发给上游”。对普通 HTTP 来说这是合理的因为 Nginx 可以先收完整个 body然后一次性转给后端后端处理起来更省事。但对 WebSocket 来说客户端通过这个连接发送的不是 HTTP 请求体而是 WebSocket 数据帧默认的proxy_request_buffering on会导致 Nginx 先把客户端发来的帧数据缓冲起来攒够了或者等到超时再转发。实际影响是什么如果客户端在一条 WebSocket 连接里频繁发帧或者发了一个超大帧数据会先在 Nginx 里滞留实时性差。更闹心的是如果帧大小超过了请求体缓冲区的容量Nginx 会把剩余数据写到临时文件走磁盘 IO性能损耗更大而且客户端这边看起来地址数据发出去半天没反应或者干脆丢帧。对实时性要求高的场景建议显式关闭请求缓冲proxy_request_buffering off;关闭后Nginx 会边收边转收到多少数据就往上游转多少延迟大幅下降。但也要知道这个选择的反面代价如果后端处理不过来或者网络拥塞Nginx 这边没有缓冲做“蓄水池”TCP 背压会直接反馈到客户端客户端可能感觉发送变慢。总的来说对于 WebSocket 这种本来就追求低延迟、双向实时通信的协议关闭请求缓冲是更合理的选择。4. 生产级配置模板逐行解读一套可落地的方案4.1 用 map 处理 Upgrade 头的通用写法前面讲了理论现在直接给一套我实际在用的生产级配置模板每一行都说明为什么这么写。放在http块里的map配置map $http_upgrade $connection_upgrade { default upgrade; close; }这个 map 的作用前面解释过就不再重复。这里补充一个细节map指令只能放在http上下文里不能放在server或location里。所以通常把这段写在nginx.conf的http { }块里或者单独 include 一个websocket_map.conf文件保持主配置干净。4.2 完整 server 块配置下面是给一个 WebSocket 子域名做反向代理的完整配置同时照顾到 wssWebSocket over TLS和普通 http 两种场景upstream ws_backend { server 127.0.0.1:8080; server 127.0.0.1:8081; keepalive 32; } server { listen 443 ssl http2; server_name ws.example.com; ssl_certificate /etc/nginx/ssl/ws.example.com.pem; ssl_certificate_key /etc/nginx/ssl/ws.example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # WebSocket 专门的 location location /ws/ { proxy_pass http://ws_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 长连接超时调整 proxy_read_timeout 3600s; proxy_send_timeout 3600s; # 关闭请求缓冲保持数据帧实时转发 proxy_request_buffering off; # 握手响应头缓冲根据业务头大小调整 proxy_buffer_size 32k; # 禁用响应缓冲相关的临时文件降低大帧消息落盘风险 proxy_max_temp_file_size 0; tcp_nodelay on; } } server { listen 80; server_name ws.example.com; return 301 https://$host$request_uri; }逐行解读几个关键点。proxy_http_version 1.1Nginx 默认和后端通信用的是 HTTP/1.0而 WebSocket 的握手基于 HTTP/1.1。如果不显式改成 1.1后端可能因为协议版本问题不认Upgrade头握手失败。这一行不带的话返工概率极高。proxy_set_header Host $host;保留原始的 Host 头。后端如果基于域名做路由或者校验这个头不能丢。proxy_read_timeout / proxy_send_timeout调整为 1 小时配合应用层心跳实际生产中基本不会再因 Nginx 超时断连。如果业务真需要几天的长连接也可以设成43200s12 小时甚至更大但一定要有心跳机制兜底否则僵尸连接会占满资源。proxy_request_buffering off;实时转发理由前面已经详细拆解。proxy_buffer_size 32k;给握手响应头留出余量。如果你的后端握手响应头特别大再按需上调。proxy_max_temp_file_size 0;这个指令默认值 1024m意思是响应体超过缓冲容量后允许写到临时文件。这里设成 0是禁止 WebSocket 场景下产生临时文件避免大帧消息走磁盘 IO 导致性能暴跌。虽然隧道模式下这个指令管不到数据帧但防一手总没错。tcp_nodelay on;禁用 Nagle 算法。Nagle 算法会把小数据包合并后发送增加网络利用率但对 WebSocket 这种大量小帧实时交互的协议来说合并会带来明显的延迟。关闭它让数据帧尽快发出去。4.3 负载均衡与后端 keepalive 的连接池效应配置里我写了两个后端和keepalive 32这里有必要说清楚它对 WebSocket 的意义。keepalive指令在 upstream 块里定义的是 Nginx 与后端服务器之间的空闲长连接数量上限作用是让 Nginx 复用和后端之间的 TCP 连接减少频繁建连握手开销。对普通 HTTP 请求这个优化效果显著。对 WebSocket 来说一旦握手完成这条 TCP 连接就被“独占”了不可能复用到别的请求上所以 keepalive 池对已经建立的 WebSocket 连接没有影响它优化的是握手请求本身——如果上游是从 8080/8081 两个端口随机选一个处理握手Nginx 和后端之间的 TCP 连接可以复用握手更快。不过要特别注意一点WebSocket 连接一旦建立客户端就和某个后端实例绑死了。如果负载均衡策略是按轮询round-robin那么重连可能出现“上次连接在 8080这次重连路由到 8081”的问题。对于有状态的服务比如聊天室里的 session 信息存在单机内存里这就麻烦了。所以 WebSocket 场景下的 upstream 策略我一般建议这样处理如果业务无状态连接信息都放 Redis 或数据库轮询没问题。如果业务有状态优先用ip_hash让同一 IP 的客户端尽量落在同一后端upstream ws_backend { ip_hash; server 127.0.0.1:8080; server 127.0.0.1:8081; }但ip_hash也有局限比如同一出口 IP 的家庭宽带用户会被分到同一台后端。更精细的方案是用 sticky 模块按 cookie 粘滞。OpenResty 或者商业 Nginx 的 sticky 指令都能做按需选用。5. 实际踩坑案例断连、卡顿与数据截断的完整排查链路5.1 案例一定时 60 秒断连心跳也救不了真实环境里遇到“每隔 60 秒断一次”排查链路可以固化下来。第一步看后端日志和 Nginx error log确认断连动作是谁发起的。如果发现 Nginx 日志里没有明显异常但连接确实在固定间隔消失直接查配置里的proxy_read_timeout是否保持默认。如果后端有心跳但心跳方向不对参考 2.2 节的解释——客户端单方面发 ping后端不回Nginx 视角里“上游没有任何数据”超时照样触发。这种问题在代码层面怎么快速验证在后端加一条日志记录每一条 WebSocket 帧的发送时间。如果后端确实定期发帧那问题就不是 Nginx 超时而是断连的对端另有其人。更高效的方法是抓包。在服务器上执行tcpdump -i eth0 port 443 -n -A -w /tmp/ws.cap然后看抓到的包里断连前是浏览器侧发了 FIN还是 Nginx 侧发了 FIN。FIN 的发起方就是断连决定者。这个动作看起来简单但真的能绕开很多“想当然”。我有个同事排查了整整一天一直调 Nginx 参数最后抓包才发现是客户端在浏览器的beforeunload事件里主动把连接 close 了让人哭笑不得。5.2 案例二推送大 JSON 卡死前端等待超时另一个经典场景后端推一条 2MB 的 JSON 数据前端页面一直等着最后报了个超时错误。排查链路先定位——到底是握手失败、消息没发出还是消息发了前端没收到。查完发现握手成功小消息也正常问题只出在大消息上。这时候先看 Nginx 是不是把数据缓在缓冲区里迟迟不吐。如果是默认配置proxy_buffering开启WebSocket 隧道模式下虽然不会有 HTTP 响应体缓冲那么夸张但proxy_request_buffering对客户端上行链路的影响是实打实的。客户端把 2MB 数据一次性写进 WebSocket 发送Nginx 默认先收完整再转给后端这期间前端如果心态不好等待超时就崩了。按 3.3 节的方案关掉proxy_request_buffering之后大 JSON 的延迟明显下降。如果还有问题看看是不是 TCP 层面的事——比如 MTU 问题导致的分片重传或者后端在单次send调用里写了一个超大块的数据触发 TCP 层缓冲堆积。这种场景后端把大消息切分成多个小块借助 WebSocket 的分帧能力分多次写比如每个分片 64KB配合tcp_nodelay on实测效果好很多。5.3 案例三Django Channels 部署中 Nginx 配置常见的遗漏说到 Python 生态Django Channels 算是比较有代表性的 WebSocket 方案。它的部署链路是浏览器 → Nginx → daphne/uvicorn → Channels 应用。我看过不少团队把 Django Channels 部署到生产环境Nginx 配置里最常见的问题有三个。第一个是忘了proxy_http_version 1.1。daphne 是 ASGI 服务器本身支持 WebSocket但它要求 HTTP 版本至少是 1.1因为 1.0 没有 Upgrade 语义。这一行没写浏览器到 Nginx 的请求无论带什么 Upgrade 头Nginx 转给 daphne 时变成了 HTTP/1.0握手必然失败。第二个是超时时间没调导致 WebSocket 每隔 60 秒被 Nginx 掐断。Channels 应用里如果配了websocket.ping事件正常情况下会定期在通道层发心跳但心跳数据需要穿透 Nginx 到达浏览器才算真正生效。如果 Nginx 的超时参数反而小于心跳周期那就是硬生生把连接弄断。第三个是反向代理层面没有给 WebSocket 的 location 单独配置proxy_request_buffering off。Django Channels 应用可能会收到客户端频繁上报的位置、操作日志等数据请求缓冲开启会导致数据包攒批转发实时性很差。我曾经见过一个“在线轨迹回放”项目前端画的路线图滞后三到五秒就是这里的缓冲在中间攒着。6. 边角问题与新版本特性一些值得记住的细节6.1 升级 Nginx 或重载配置会不会切断长连接运维上有个高频疑问我改了 Nginx 配置执行nginx -s reload正在进行的 WebSocket 长连接会不会断Nginx 的热升级机制是reload 时master 进程重新加载配置启动新的 worker 进程旧的 worker 进程会进入 graceful shutdown 状态等待当前正在处理的请求完成后再退出。WebSocket 连接因为是长连接会一直“占着”旧 worker理论上不会中断。但这也带来一个副作用reload 后旧 worker 因为挂着一个永不结束的 WebSocket 连接迟迟无法退出。如果每次改配置都 reload旧 worker 越积越多进程数量异常增长内存也被占用。解决思路有两个一是主动踢掉长连接比如在 WebSocket 服务端实现优雅下线协议通知客户端重连二是配置worker_shutdown_timeout给旧 worker 的退出时间设个上限超过就强制退出worker_shutdown_timeout 30s;这个参数在较新的 Nginx 版本里支持强制旧 worker 在 30 秒内退出代价是挂在它上面的长连接会被断开。生产环境如果长连接业务对可用性要求极高建议配合“服务端主动通知客户端重连”的机制一起用。6.2 HTTP/2 与 WebSocket 的兼容性问题配置里我在 listen 行写了http2这里也得提醒一句。WebSocket 的握手基于 HTTP/1.1 的 Upgrade 机制HTTP/2 协议本身在标准上也有对 WebSocket 的支持但这个支持在浏览器和 Nginx 之间经历了很长一段时间的兼容阵痛。实际工作中大多数浏览器的 WebSocket API 发起的连接仍然走 HTTP/1.1 Upgrade哪怕页面整体是 HTTP/2 的。Nginx 在 443 端口同时开启 HTTP/2收到 WebSocket 升级请求时会自动按 HTTP/1.1 处理这条连接。也就是说对我们场景来说http2参数不会破坏 WebSocket可以放心写。但需要注意另一个坑如果你在一台服务器上同时跑多个域名其中一个 location 需要 WebSocket不要在 WebSocket 专属的 location 里配置奇怪的grpc_pass或者http2_push之类的指令部分指令组合在隧道模式下会有未知行为。保守起见WebSocket location 保持干净、专注其他高级特性留给普通 HTTP location。6.3 监控长连接数量不要只会看 stub_status上线之后运维想确认 WebSocket 连接数是否正常。很多人第一反应是看stub_status地址是location /nginx_status { stub_status on; access_log off; }这个页面显示的内容包括 active connections、server accepts/handled/requests 等。但注意active connections是 Nginx 当前所有活跃连接的总数包含普通 HTTP 短连接和正在建立中的连接根本分不清哪些是 WebSocket 长连接。对一个大量 WebSocket 在线的服务来说这个数字只能作为总负载参考不能用于业务监控。更精准的做法是在 access log 里增加对$http_upgrade的统计。比如在日志格式里加一个字段log_format wslog $remote_addr - $time_iso8601 $request $status upgrade$http_upgrade connection$connection_upgrade;然后把 WebSocket 业务的 access_log 单独指向这个格式用 grep 统计带upgradewebsocket的请求数。但这只是握手请求数不是实时在线数。最靠谱的在线数监控还是回到后端WebSocket 连接一旦建立就有一个对应的应用层对象。以 Django Channels 为例可以通过 channel layer 统计在线 group 人数。这种基于应用层的统计才是业务眼里真正有价值的指标。Nginx 这边顶多做辅助判断比如通过连接数异常下降推断是不是发生了大面积断连。最后再分享一个实用的小技巧。用命令行快速验证 Nginx 配置是否能让 WebSocket 握手通过不需要写完整的前端页面curl -i -N \ -H Connection: Upgrade \ -H Upgrade: websocket \ -H Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ \ -H Sec-WebSocket-Version: 13 \ https://ws.example.com/ws/chat/如果返回HTTP/1.1 101 Switching Protocols说明 Nginx 的升级转发配置是通的如果返回 502 或者 400目标就是 Nginx location 里的转发设置有问题再去按文章里的排查链路逐个对。超时、缓冲、这三大块配置都对齐之后Nginx 对 WebSocket 的“不友好”问题基本就清零了。
返回列表