免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

WebSocket 实际开发踩坑问题(生产实战)

WebSocket 实际开发踩坑问题(生产实战) 目录1. 连接莫名断开最常见现象根因解决2. 负载均衡下会话丢失集群部署大坑现象根因两种方案3. 消息乱序问题现象根因解决4. 消息粘包 / 拆包现象根因5. 断连感知延迟很高现象根因解决6. 鉴权问题现象根因解决方案7. 消息发送失败消息丢失现象根因解决8. 浏览器跨域问题现象9. 大消息性能问题现象10. 代理、防火墙拦截 ws 协议11. STOMP 子协议额外坑Spring‑WebSocket 常用12. 并发连接数上限生产落地最小必要清单总结WebSocket 解决双向通信但网络、代理、重连、消息乱序、断连感知、粘包拆包、负载均衡都是高频坑下面结合后端 JavaNetty/Spring‑WebSocket场景整理实际问题、现象、根因、解决方案。1. 连接莫名断开最常见现象客户端 WebSocket 没有业务报错悄无声息断开前端onclose延迟很久才触发测试环境正常生产频繁断。根因中间件空闲超时Nginx、API 网关、防火墙、运营商 NAT 会话超时。Nginx 默认proxy_read_timeout 60s60s 没有数据传输就主动掐断 TCP 连接。运营商 NAT 一般 30‑120s 空闲回收。服务端主动关闭服务 GC 停顿、线程池耗尽、OOM、异常抛出未捕获。客户端休眠手机锁屏、电脑休眠、网络切换 (wifi↔4G)。解决心跳保活必须做双向心跳客户端定时发 ping服务端返回 pong或者服务端定时 ping。间隔25‑45s小于网关 / NAT 超时。区分WebSocket 协议自带ping/pong帧不要自己用业务 JSON 做心跳优先原生帧部分浏览器封装不暴露原生 ping才用业务心跳包。Nginx 配置调大超时nginxproxy_read_timeout 300s; proxy_send_timeout 300s; proxy_connect_timeout 10s;注意只是调大不能根治 NAT心跳必不可少。客户端检测onclose做指数退避重连1s、2s、4s、8s上限 30s避免瞬间大量重连打垮服务。重连时携带 token恢复会话状态。2. 负载均衡下会话丢失集群部署大坑现象客户端连上 A 实例网关下次转发到 B 实例消息收不到重连后状态丢失。根因WebSocket 是长连接TCP 绑定在某一台服务实例普通轮询负载均衡新请求落到别的节点没有该用户 session。两种方案IP 哈希sticky 会话黏连网关根据客户端 ip hash 固定转发到同一实例。 缺点IP 变化手机切换网络黏连失效流量不均衡扩容会大量重建连接。会话中心化推荐生产连接状态、用户订阅信息放到 Redis消息不直接本地发送通过 MQ (RocketMQ/Kafka) 广播所有实例消费找到持有该客户端长连接的实例发送消息。典型场景智慧充电设备上报设备 websocket 接入集群消息需要广播。3. 消息乱序问题现象业务消息到达客户端顺序和发送顺序不一致。根因WebSocket 基于 TCPTCP 本身字节流是有序但是服务端多线程发送消息多个线程往同一个 Channel 写数据线程调度不同写出顺序错乱。异步业务线程处理不同业务任务完成时间不一样调用 send 时机乱掉。⚠️TCP 字节流有序 ≠ 业务消息有序。解决同一个客户端 Channel所有业务消息单线程串行发送。Netty使用 Channel 自身 EventLoop 线程执行 write。不要随便在业务线程直接channel.writeAndFlush()。业务消息携带 sequence 序列号客户端做序号校验业务层兜底排序。4. 消息粘包 / 拆包现象多条 JSON 粘在一起一次收到一条大消息被切成两次回调 onmessageJSON 解析直接报错。根因WebSocket 帧协议本身已经处理帧分片协议层面不会粘包只有自己封装二进制流才会。使用标准 Text 帧发送完整 JSON 字符串基本不会遇到粘包拆包如果发送自定义二进制协议必须实现长度字段 payload 格式。坑点部分开发者手动拼接消息字符串没有使用 WebSocket 帧 API出现粘包。5. 断连感知延迟很高现象客户端网络直接拔掉网线服务端很久才感知连接断开资源不释放产生大量僵尸连接。根因TCP 是惰性的异常断网没有发送 FIN 包内核不会立刻通知应用。只靠onClose回调不够。解决心跳超时判定服务端记录每个连接收到 pong 更新时间戳后台定时任务扫描超过阈值比如 70s 没有心跳主动关闭 channel释放资源。Netty 开启IdleStateHandler空闲检测读空闲触发关闭。java// Netty空闲检测读空闲45s触发事件 pipeline.addLast(new IdleStateHandler(45,0,0));6. 鉴权问题现象WebSocket 握手阶段无法携带 header前端原生 new WebSocket () 不能自定义请求头。token 放 header 行不通。根因浏览器原生 WebSocket API 不支持自定义 Header。解决方案url 参数携带 tokenws://xxx/ws?tokenxxx网关拦截握手请求校验 token。 风险url 会打印日志token 泄露需要日志过滤 token 字段。先 http 登录拿到 cookiewebsocket 握手自动带上 cookie服务端 session 鉴权。适合浏览器环境。握手成功之后第一条业务消息传递 token 做鉴权鉴权失败立刻 close 连接。禁止握手之后延迟鉴权忘记关闭非法连接堆积僵尸连接。7. 消息发送失败消息丢失现象客户端已经断开服务端还调用 send 发送消息消息无声丢弃业务丢失数据。根因服务端发送的时候 channel 已经关闭但是代码没有判断可写状态缓冲区满发送失败没有捕获异常。解决send 之前判断channel.isActive()writeAndFlush 添加 FutureListener监听发送失败、失败做消息重试、落库重要业务消息消息持久化ACK 确认机制。websocket 只管传输业务做 ACK客户端收到业务消息回 ACK未 ACK 定时重发。8. 浏览器跨域问题现象WebSocket 握手 403跨域报错。WebSocket 握手是 HTTP 请求会触发 CORS 跨域校验握手成功之后通信不再走 http。Spring‑WebSocket 需要配置允许 OriginNginx 网关配置跨域。9. 大消息性能问题现象传输大文本 / 二进制内存暴涨OOM。 根因默认帧大小有限制Netty Spring websocket 默认最大帧长度有限超大消息直接抛出异常断开连接。 解决修改服务端最大帧配置业务侧大消息做分片业务拆分。10. 代理、防火墙拦截 ws 协议现象部分公司内网环境ws:// 被防火墙拦截连接失败。 兼容降级方案websocket 失败自动降级 SSE /long‑polling 长轮询。Spring‑WebSocket STOMP 支持降级策略。11. STOMP 子协议额外坑Spring‑WebSocket 常用stomp 订阅泄漏客户端断开没有发送 unsubscribe服务端订阅残留消息持续往僵尸连接推送。session 销毁没有清理订阅关系。12. 并发连接数上限现象上千长连接之后服务 CPU、FD 文件描述符打满。Linux 调大文件句柄数 ulimitJVM 调优Netty 合理设置线程数监控连接数量活跃连接、僵尸连接指标上报。生产落地最小必要清单总结✅ 心跳机制 Idle 空闲检测服务端主动关闭僵尸连接✅ 客户端指数退避重连逻辑✅ 集群部署会话中心化 RedisMQ不要单纯依赖 IP 黏连✅ 同一个连接 Channel消息单线程发送防止业务消息乱序✅ send 消息增加 Future 失败监听重要消息业务 ACK✅ 握手鉴权处理注意浏览器 ws 不能自定义 header✅ Nginx 网关超时配置心跳优先调大超时只是辅助✅ 监控指标连接总数、活跃连接、断连次数、心跳超时次数、消息发送失败数
返回列表