做实时推送、在线聊天、行情刷新、协同编辑这类功能,WebSocket 基本上是绕不开的选项。前段时间有个项目在测试环境一切正常,一上生产连接就隔几十秒断一次,客户端日志一直刷 stream disconnected before completion: websocket closed by server before res,最后查下来问题根本不在业务代码,而是 Nginx 反代的默认超时把长连接掐了。这类坑非常典型,很多人把 Nginx 当成普通 HTTP 反向代理配完就扔,结果 WebSocket 长连接一上生产就花式掉线。
这篇文章就专门聊 Nginx 下的 WebSocket 长连接及数据容量配置,包括协议升级的原理、长连接保活参数、大数据帧传输相关的容量限制,以及负载均衡场景下的会话保持。无论你是用 Spring Boot、Node.js 还是 Go 做后端,只要前面挂了 Nginx,这份配置和排查思路基本都能直接套用。
1. WebSocket 反向代理的基本原理
1.1 从 HTTP 到 WebSocket:一次协议升级的握手
要搞清楚 Nginx 为什么需要特殊配置,得先明白 WebSocket 不是独立走一个端口,而是复用 HTTP 的握手过程。客户端发一个普通的 HTTP GET 请求,带上 Upgrade: websocket 和 Connection: Upgrade 这两个头,问服务端“能不能切到 WebSocket 协议”。服务端同意后,返回 101 Switching Protocols,此后这条 TCP 连接不再按 HTTP 的“一问一答”来用,而是变成了一个全双工的、可以双向持续收发数据的通道。
类比一下就是:HTTP 是每次去窗口办事,办完就走;WebSocket 是先递申请,窗口同意后直接给你拉一根专线,这根线上双方可以随时说话,不用再排队。
在 Nginx 反代的场景里,客户端和 Nginx 之间是一条 TCP 连接,Nginx 和后端服务之间是另一条 TCP 连接。握手请求到达 Nginx 后,Nginx 需要把带有 Upgrade 的原始请求原样转发给后端,并且在后端返回 101 之后,把这两条连接“焊死”在一起,之后双向的数据都是纯透传。这个“焊死”的动作,就是 Nginx 对 WebSocket 长连接的核心处理逻辑。
1.2 为什么默认的反向代理配置连不上 WebSocket
Nginx 默认向后端转发请求时,使用的是 HTTP/1.0,并且不会自动传递 Upgrade 和 Connection 这两个头。HTTP/1.0 本身没有 Upgrade 机制,后端收到一个没有 Upgrade 头的普通请求,自然返回正常的 200 响应,客户端等不到 101,WebSocket 握手就失败了。
所以最基础的配置要做两件事。第一,把 Nginx 和后端之间的 HTTP 版本升到 1.1,使用 proxy_http_version 1.1;。第二,手动把升级相关的头传给后端:
nginx复制proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
第一行的意思是“客户端请求里如果带了 Upgrade 头,就原样透传给后端”。第二行的意思是“无论客户端原始请求里的 Connection 是什么,都强制改成 upgrade”。这两行配合起来,后端才能识别出这是一次 WebSocket 升级请求。
这里有个细节必须说清楚:Connection 头是逐跳头,HTTP/1.1 规定连接相关的头不应该被传递给下一跳,Nginx 默认会清理掉这些头。所以不能只写 proxy_set_header Connection $http_connection 了事,直接写死 "upgrade" 是目前最稳的写法。
1.3 反向代理模式下 WebSocket 连接的生命周期
握手成功后,一条 WebSocket 连接会经历从建立、维持到断开的完整生命周期。在 Nginx 反代模式下,这条生命周期里有几个容易被忽略的角色:
- 客户端与 Nginx 之间的 TCP 连接;
- Nginx 与后端服务之间的 TCP 连接;
- 应用层 WebSocket 的 Ping/Pong 心跳;
- 操作系统层面的 TCP KeepAlive;
- Nginx 自带的
proxy_read_timeout/proxy_send_timeout超时器。
很多人只关心第一层,认为只要客户端和服务端都还活着,连接就永远会在。但 Nginx 在中间做转发时,一旦在 proxy_read_timeout 配置的时间内没有从后端读到任何数据,就会主动关闭连接,客户端拿到的表现就是连接被服务端断开。反过来,长时间没往客户端写数据,proxy_send_timeout 也会起作用。
理解了这个生命周期,再回头看那些“连接频繁断开”的问题,心里就有数了:要么是超时设置太短,要么是链路里某一个角色没有发心跳,要么是 TCP 层被防火墙或操作系统回收了。后面几节我会逐个讲透。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心配置详解与逐项调优
2.1 基础代理配置:升级头与 HTTP 版本
先给一份可以跑通的最小配置,所有参数都在后面逐项解释:
nginx复制map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name ws.example.com;
location /ws {
proxy_pass http://backend_servers;
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_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
注意我用了 map 块对 Connection 头做了一次动态映射。$http_upgrade 如果有值,说明这是一个 Upgrade 请求,Connection 就设为 upgrade;如果没有值,说明是普通 HTTP 请求,Connection 设为 close,避免影响同一个 server 块里其他普通接口的请求。
map 块必须放在 server 块之外,可以放在 http 块里。这个写法的好处是,同一个 Nginx 里如果有普通 HTTP 接口,不会被强制带上 Connection: upgrade 头,减少不必要的麻烦。
2.2 长连接保活:超时时间应该怎么设
Nginx 里与 WebSocket 长连接最直接相关的超时参数有四个:
| 参数 | 默认值 | 作用 |
|---|---|---|
proxy_connect_timeout |
60s | 与后端建立 TCP 连接的超时时间 |
proxy_read_timeout |
60s | 两次从后端读取数据之间的最大间隔 |
proxy_send_timeout |
60s | 两次向后端发送数据之间的最大间隔 |
proxy_read_timeout can also apply to websocket frame |
- | 实际影响连接保活的关键项 |
前两个默认 60 秒的意思是,如果 60 秒内后端没有给 Nginx 发任何数据,Nginx 就认为连接闲置,直接断开。普通 HTTP 请求响应很快,60 秒绰绰有余;但 WebSocket 长连接允许长时间没有业务消息,比如一个聊天室里没人说话的时候,这条连接必须依然保持。
所以我通常建议把 proxy_read_timeout 和 proxy_send_timeout 都设成 3600 秒(一小时)起步,甚至更长。具体设多长,取决于业务对掉线容忍度和服务器资源占用的平衡:
- 聊天、消息推送类应用,客户端一般有自动重连机制,一小时足够;
- 行情推送这种低频但需要秒级通知的场景,不建议依赖 Nginx 超时,应该在应用层做心跳;
- 如果客户端是网页版,浏览器对空闲 TCP 连接有自己的回收策略,设太长意义也不大。
这里要提醒一句,不要以为把超时设成无限大就万事大吉。超时时间越长,Nginx 上挂着的半开连接越多,一旦出现异常断开,清理机制不够及时,会导致连接数堆积。合理做法是配合应用层心跳,把心跳间隔设置得比超时时间短,让连接一直处于活跃状态。
2.3 数据容量配置:大消息会被哪里卡住
很多人在 WebSocket 里传大消息时踩过坑:连接握手都正常,平时小消息也正常,但一旦传一个稍大的 JSON 或者文件片段,连接就断,或者消息被截断。这类问题通常不是 Nginx 的某个单一参数能解释的,得从三层来看。
第一层是 Nginx 的 HTTP 层限制。client_max_body_size 默认是 1m,它限制的是请求体大小,对 WebSocket 握手和普通 HTTP 请求起作用。WebSocket 升级后的帧数据在 Nginx 里是 TCP 层透传,Nginx 不解析帧,所以严格来说 client_max_body_size 并不会限制后续的 WebSocket 消息大小。但握手请求如果带了很多查询参数,头部会变大,这时候起作用的是 large_client_header_buffers。如果 URL 里的参数很多,或者自定义 Header 很大,Nginx 返回 400,就会看到握手失败:
nginx复制large_client_header_buffers 4 32k;
第二层是客户端的帧大小限制。浏览器和各类 WebSocket 客户端库通常有自己的最大帧限制。以 ws 类的库为例,如果收到的帧超过 maxPayload,客户端会直接报错并关闭连接。所以项目里出现大消息断开时,要先查客户端库的默认限制,别急着改 Nginx。
第三层是后端框架的缓冲区限制。Spring Boot 默认的 WebSocket 消息大小有上限,Tomcat 容器也有相关参数,Node 的 ws 库同样有 maxPayload 配置。Nginx 只是中间管道,后端框架收到超过缓冲区上限的帧时会报错断开。
所以在 Nginx 层面,数据容量配置要做的其实不多,client_max_body_size 可以根据业务适当调大,large_client_header_buffers 建议调大,真正的核心容量限制要排查前后端的帧大小配置。如果你确实需要传输很大的一帧数据,通常建议在应用层做分片,每个 WebSocket 消息控制在合理大小,这样对 Nginx 的缓冲、后端的处理压力以及网络稳定性都更友好。
2.4 实时性优先:proxy_buffering 必须关掉
WebSocket 对实时性有要求,如果 Nginx 启用了响应缓冲,会把后端发来的数据先攒到缓冲区再一次性发给客户端,这对实时推送场景是致命的。连接建立后,如果发现 Nginx 不往客户端推数据,非得攒够一定字节才发,或者要等缓冲区超时,那就是 proxy_buffering 在作怪。
在 WebSocket 的 location 里一定要显式关闭缓冲:
nginx复制location /ws {
proxy_buffering off;
proxy_cache off;
proxy_set_header X-Accel-Buffering no;
}
proxy_buffering off 让 Nginx 收到后端数据后立即转发给客户端,不攒包。proxy_cache off 避免响应被缓存层截留。X-Accel-Buffering 头是给一些后端框架看的,告诉它们不要按缓冲逻辑处理这条连接。
还有一个不太容易察觉的配置是 proxy_request_buffering,默认开启,Nginx 会先把客户端请求体缓冲完再转发给后端。对 WebSocket 升级请求来说影响不大,但对某些需要在握手阶段直接透传数据的场景,可以考虑关掉:
nginx复制proxy_request_buffering off;
2.5 一份完整的 WebSocket 反代配置模板
把上面的知识点串起来,给一份生产环境可用的配置模板,可以直接根据业务修改使用:
nginx复制map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream websocket_backend {
# 后面的 IP 换成实际后端地址
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name ws.example.com;
# 有需要就启用 HTTPS,证书配置略
# WebSocket 专用 location
location /ws {
proxy_pass http://websocket_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_connect_timeout 60s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# 关缓冲,保证实时性
proxy_buffering off;
proxy_cache off;
# 请求头大小,按业务调大
large_client_header_buffers 4 32k;
# 错误处理,可选
proxy_next_upstream off;
}
# 普通 HTTP 接口不受影响
location /api/ {
proxy_pass http://websocket_backend;
proxy_set_header Host $host;
}
}
有个地方专门说下:proxy_next_upstream off; 是我自己比较推荐的写法。默认情况下,如果 Nginx 与后端通信过程中出现错误,会把请求重试给下一个后端。但 WebSocket 是有状态的长连接,重试毫无意义,反而可能让客户端建立到一半的连接被重置。关掉这个重试行为,可以让问题以最快的速度暴露,而不是让客户端觉得连接时好时坏。
3. 长连接与负载均衡场景的高级配置
3.1 多节点下 WebSocket 会话保持:别让连接乱跳后端
WebSocket 和普通 HTTP 请求最大的区别在于它是有状态的。普通 HTTP 请求无状态,Nginx 可以把每次请求轮流分发到不同后端;WebSocket 一旦握手成功,后续所有消息都必须由同一个后端处理。如果 Nginx 默认的轮询算法把后续的数据帧分发到另一个后端节点,那个节点根本没有对应的会话,轻则消息丢失,重则直接断开。
所以在 WebSocket 的 upstream 上必须做会话保持,也就是把同一个客户端的连接始终打在同一台后端。Nginx 里最常用的是 ip_hash:
nginx复制upstream websocket_backend {
ip_hash;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
ip_hash 根据客户端 IP 的哈希值选择后端,同一个 IP 会一直命中同一台后端。适合大多数场景,配置简单。
但 ip_hash 有几个问题要注意。第一,如果客户端整体经过同一个出口 IP 访问,比如公司内网或某些网络代理环境,所有连接都会集中到一台后端,负载不均。第二,如果 Nginx 前面还有一层 LB,那 Nginx 看到的可能是上一层 LB 的 IP 而不是真实客户端 IP,这时候要设置 proxy_set_header X-Forwarded-For 并让 Nginx 用 XFF 头做哈希,但这要求 Nginx 的 hash 指令基于变量实现。
更精确的做法是使用 sticky 模块,但开源版的 Nginx 不内置,需要 Nginx Plus 或者使用 OpenResty。这里给一个基于 hash 指令的变通方案,通过 XFF 头的第一个 IP 来做会话保持:
nginx复制map $remote_addr $client_ip {
default $remote_addr;
}
server {
location /ws {
# 假设 XFF 可信,取第一个 IP
set $sticky_key $http_x_forwarded_for;
if ($sticky_key = "") {
set $sticky_key $remote_addr;
}
# 使用哈希
hash $sticky_key consistent;
}
}
这个写法需要放在 upstream 块里,实际配置时注意 hash 指令的位置。生产环境如果只用几台后端,ip_hash 基本够用;如果后端规模大、流量不均匀,可能需要引入 Redis 会话存储,或者用更专业的网关来做 WebSocket 路由,这已经超出 Nginx 本身能解决的范围了。
3.2 upstream keepalive 与 WebSocket 的微妙关系
配置里我们写了 keepalive 32;,很多人会问:WebSocket 本身就是长连接,还需要 keepalive 吗?
这里的 keepalive 是 Nginx 与 upstream 后端之间的空闲 HTTP 连接池。乍一看和 WebSocket 没直接关系,因为 WebSocket 升级成功后这条连接会一直被占用,不会回到连接池里。但实际情况是:一个 Nginx 节点可能同时处理大量 WebSocket 连接,在握手成功之前,Nginx 需要先与后端建立一条 TCP 连接。如果没有 keepalive,每次新连接都要重新建 TCP,握手时握手请求和 TCP 建立开销叠加,高并发下握手延迟会明显上升,甚至产生大量 TIME_WAIT 连接。
keepalive 32 维护的是那些闲置的普通 HTTP 连接,新的握手请求可以复用这些连接来发起升级,从而降低握手阶段的延迟。对于频繁建立 WebSocket 连接的场景,这个参数很有效果,但注意它并不会帮你维持 WebSocket 连接本身。
另外,设置 keepalive 后要配合 proxy_http_version 1.1 和 proxy_set_header Connection "" 才能让 Nginx 正确复用 upstream 连接。不过如果同一个 location 配置了 WebSocket 的 Connection "upgrade",这几个配置会有冲突,所以实际使用中要分别配置普通 HTTP 和 WebSocket 场景。这也是为什么前面配置模板里把 WebSocket location 和普通 API location 分开写的主要原因。
3.3 与后端框架的配合要点
Nginx 只是中间的转发层,后端的配合也决定长连接是否能真正持久。用 Spring Boot 做后端时,WebSocket 相关的消息缓冲区大小、心跳策略都需要显式配置。以 Spring 的 WebSocketHandler 为例,自定义配置时一般要重写 configureWebSocketTransport,设置消息大小限制和发送超时:
java复制@Configuration
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws").setAllowedOrigins("*");
}
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(4 * 1024 * 1024);
registration.setSendTimeLimit(20 * 1000);
registration.setSendBufferSizeLimit(8 * 1024 * 1024);
}
}
Node.js 用 ws 库时,创建 WebSocketServer 时也要显式配置 maxPayload,否则默认值很可能会卡住大消息的传输。Go 的 gorilla/websocket 默认读缓冲区是 4096 字节,需要在 Upgrader 里调大 ReadBufferSize 和 WriteBufferSize。
后端处理大消息时还有一个通用建议:不要在一个 handler 里同步处理超过 1MB 的消息,尽早做异步化。WebSocket 连接是串行接收帧的,如果处理太慢,后面的帧会堆积在缓冲区,最终导致缓冲区溢出而断开。
3.4 高并发下的连接数与资源调优
当单机连接数到一定量级,Nginx 本身也会成为瓶颈。先看操作系统层面,ulimit -n 决定了单进程能打开的文件描述符上限,WebSocket 每一条连接至少占用一个 fd,连接数一高,默认 1024 的上限根本不够用。需要在 Nginx 配置里设置:
nginx复制worker_rlimit_nofile 65535;
events {
worker_connections 65535;
use epoll;
}
worker_connections 65535 表示每个 worker 进程最多同时处理 65535 个连接,配合 worker_rlimit_nofile 一起调。进程数不是越多越好,一般按 CPU 核数来定,worker_processes auto; 就好。
还有一个隐蔽的参数是 proxy_socket_keepalive。在 Linux 上,TCP 层的 keepalive 默认是关闭的,可以在 location 里开启,让操作系统帮忙清理半开连接:
nginx复制proxy_socket_keepalive on;
这会启用到后端的 TCP KeepAlive,但只能在 HTTP/1.1 下工作,需要放在 location 里。它能减少半开连接占用的资源,但不能替代应用层心跳。
4. 常见问题与排查技巧实录
4.1 握手失败:迟迟等不到 101 状态码
最典型的症状是客户端报 Error during WebSocket handshake: Unexpected response code: 404 或者 400。出现 404,先确认路径对不对,比如客户端连的是 /ws,后端 handler 注册的也是 /ws,但 Nginx location 写的是 /socket,那必然 404。
出现 400 且日志里有 client sent invalid request,大概率是请求头太大,large_client_header_buffers 不够。还有一种情况是浏览器主动发了 Sec-WebSocket-Key、Sec-WebSocket-Version 等头,但 Nginx 没有把这些头传给后端,后端判断失败。默认情况下 proxy_set_header 只会覆盖指定头,其他头会原样传递,一般不会丢失,除非某个头被 Nginx 认为是逐跳头主动清掉了。
排查时最直接的方法是用 curl 手发一个握手请求,看返回状态:
bash复制curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \
-H "Sec-WebSocket-Version: 13" \
http://ws.example.com/ws
如果返回 101 Switching Protocols,说明 Nginx 和后端都正常;如果返回 502,问题在后端没有监听对应端口或者后端进程挂了;如果返回 400,检查头大小和升级头是否被正确传递。
另外要提醒一点,curl 的 -H "Connection: Upgrade" 必须在命令行里手动加上,否则 curl 默认不会发 Upgrade。看到 101 之后,curl 会保持在连接上,这时候可以输入一些原始数据测试双向转发是否正常。
4.2 连接频繁断开:stream disconnected before completion 的真相
这句报错字面意思是“流在完成之前断开了,WebSocket 在收到完整响应前被服务端关闭”。常见原因有好几类:
- Nginx 的
proxy_read_timeout默认 60 秒,连接空闲超过 60 秒就被切; - 后端应用空闲时没有发送心跳,或者心跳被 Nginx 缓冲了;
- 客户端和服务端的 Ping/Pong 周期不匹配,客户端发的 Ping 后端没回;
- Nginx 和后端之间的网络设备或者防火墙主动回收空闲连接;
- 后端进程崩溃或被重启,已有连接全部中断。
遇到这类问题,先把 Nginx 超时调大,再确认前后端的心跳机制。WebSocket 协议本身有 Ping/Pong 帧,推荐在后端实现一个空闲心跳定时器,比如每 30 秒发一次 Ping,客户端收到后回 Pong。有了协议层心跳,应用层的连接就一直是活跃的,Nginx 不会因为闲置而关闭。
排查时看 Nginx 日志里的连接关闭记录很有帮助。打开 error.log 的告警级别:
nginx复制error_log /var/log/nginx/error.log warn;
然后重点搜索 upstream prematurely closed connection、read timed out 这些关键字。前者通常是后端先关连接,后者通常是 Nginx 因超时主动关连接,能直接定位是哪一侧先动手的。
4.3 大消息传输卡顿或断开
WebSocket 上传输大消息时出现卡顿或断开,先判定卡在哪一层。客户端发送一个很大的字符串或二进制数据时,底层会被拆成多个帧传输。如果 Nginx 的 proxy_buffering 没有关闭,帧数据会被缓冲,客户端感知到的是消息迟迟不来,而不是断开。
如果消息在传输过程中直接断开,重点查三处:
- 客户端库的
maxPayload或maxMessageSize; - 后端 WebSocket 框架的消息大小限制;
- Nginx 的
client_max_body_size和large_client_header_buffers(主要影响握手阶段)。
我遇到过最离谱的一个问题,是客户端的 maxPayload 设置的是 1MB,而后端发了一条 1.2MB 的消息,客户端直接断连。日志里既没有 Nginx 的错误,也没有后端的异常,纯粹是客户端在超出限制时静默关闭了连接。这类问题最容易让人走弯路,所以排查时要先看客户端库的配置和日志,不要一上来就怀疑 Nginx。
4.4 实战排查流程:从现象到根因的完整路径
按我自己的经验,WebSocket 连接问题排查可以总结成一套固定流程:
- 先确认 Nginx 配置语法和实际生效的配置:
nginx -T查看完整配置; - 确认后端服务在线并能直接通过内网 IP 访问:绕过 Nginx 用 curl 直连后端,测试 WebSocket 握手;
- 确认 Nginx 与后端之间的网络链路:telnet 端口是否通,防火墙有没有限制空闲连接;
- 抓包看连接断开的方向:
tcpdump -i eth0 port 8080,看断开时是哪一侧先发 FIN; - 看 Nginx 错误日志中关键字:
upstream prematurely closed、timed out、no live upstreams等; - 结合客户端日志,确认是协议层断开还是业务层主动断开。
这套流程的核心思路是“从端到端逐步缩小范围”,不要一开始就盯着 Nginx 参数调来调去。很多问题是后端框架的默认心跳时间比 Nginx 超时时间还要长,导致大量连接被 Nginx 先切断,这类问题调 Nginx 调出花来都没用,得改后端心跳。
4.5 WebSocket 配置问题速查表
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| 握手返回 404 | location 路径与 WebSocket 地址不匹配 | 检查 Nginx location 和后端 handler 路径 |
| 握手返回 400 | 请求头过大或升级头丢失 | 增大 large_client_header_buffers,检查 upgrade 头透传 |
| 握手返回 502 | 后端无监听或进程异常 | 检查后端服务状态和 Nginx upstream 配置 |
| 连接隔 60 秒断 | proxy_read_timeout / proxy_send_timeout 默认值过短 |
调大超时时间,建议 3600s 起 |
| 连接偶发断,无明显规律 | 防火墙或 LB 空闲回收 | 开启应用层心跳,设置合理心跳间隔 |
| 大消息传输卡顿 | proxy_buffering 未关闭 |
关闭缓冲,确保实时透传 |
| 大消息传输直接断开 | 客户端或后端帧大小限制 | 调整 maxPayload、setMessageSizeLimit 等配置 |
| 多后端节点下连接错乱 | 缺少会话保持 | 使用 ip_hash 或 sticky |
表格能快速定位方向,但实际项目里可能存在多个问题叠加,比如 Nginx 超时太短导致连接频繁断,后端心跳又没配置,客户端重连逻辑又写得不好,一连串问题全冒出来。这时候别贪快,一个变量一个变量地改,每次只改有把握的部分。
结尾
Nginx 里的 WebSocket 配置说起来就是几个头、几个超时和几个缓冲参数,但每个参数背后都关联着连接生命周期的某一个环节,踩坑的代价往往是生产环境大面积断连。我自己的习惯是:新项目接入 WebSocket 的时候,先按文中的基础配置搭好,再结合业务场景把后端的消息大小限制和心跳机制一并设计好,把连接层的问题提前消灭在设计阶段,而不是等上线后再慢慢查。
最后再分享一个小技巧:WebSocket 的连接监控别忽略 Nginx 的 $connection 变量,你可以通过 log_format 增加一条包含 upstream_response_time 和 $connection_requests 的日志,定期观察长连接的平均生命周期和后端响应耗时。数据不会骗人,调优和定位问题的时候,这些日志往往能给最直接的答案。
