WebSocket 常被简单理解成“后端主动推消息给前端”。真正落地时,要处理的不是 send 一行代码,而是连接生命周期、心跳、鉴权、断线重连、消息格式、广播范围和多实例部署。
WebSocket 适合什么场景
WebSocket 适合服务端需要持续向客户端推送消息的场景:
- 聊天消息。
- 订单状态变化。
- 实时大屏。
- 任务进度。
- 告警通知。
- 在线协作。
如果只是偶尔刷新状态,轮询或 SSE 可能更简单。WebSocket 适合双向通信和高频推送,但也会带来连接管理成本。
连接不是登录
WebSocket 握手通常从 HTTP 升级而来。连接建立时要做鉴权,但不要把“连接存在”当作“用户永远在线”。
常见做法:
- 握手时带 token。
- 服务端校验 token 并绑定 userId。
- 连接上下文只保存必要身份信息。
- token 过期或权限变化时主动断开或要求重连。
不要在 URL 里长期暴露敏感 token。如果必须放 query,要确保 HTTPS,并控制 token 有效期。
心跳是必须的
真实网络里,客户端关页、手机休眠、代理断开、Nginx 超时,都可能导致连接半开。服务端不做心跳,就会以为连接还活着。
常见策略:
- 客户端定期 ping。
- 服务端定期 pong 或记录最后活跃时间。
- 超过阈值关闭连接。
- 客户端指数退避重连。
心跳间隔不要太短,否则本身就会制造流量;也不要太长,否则断线发现太慢。内部系统可以从 20 到 60 秒范围试起,再结合代理超时配置调整。
消息格式要有类型
不要直接推一段裸字符串。建议统一消息结构:
{
"type": "task.progress",
"requestId": "abc",
"timestamp": 1780000000000,
"payload": {
"percent": 80
}
}
至少要有:
type:消息类型。payload:业务内容。timestamp:服务端时间。requestId或messageId:方便排查和去重。
这样前端可以按类型分发,后端日志也能追踪一条消息。
单实例和多实例差别很大
单实例里,连接保存在本机内存 Map 中就能推送:
userId -> sessions
多实例部署后,用户连接可能在任意节点上。A 节点收到业务事件,但用户连接在 B 节点,就需要消息总线:
- Redis Pub/Sub。
- MQ。
- 专门的推送服务。
- 网关层粘性会话。
小系统可以先单实例跑通,但设计消息接口时要给多实例留边界,不要把所有业务逻辑写死在某个 Controller 的静态 Map 里。
推送失败怎么处理
WebSocket 不是可靠消息队列。连接断了,消息可能发不出去。业务上要区分:
- 弱实时通知:丢了可以下次刷新看到。
- 强一致消息:必须落库、确认、补偿。
订单状态、任务结果这类关键状态,应该以数据库为准,WebSocket 只是提示用户刷新或更新 UI。聊天消息、审批通知等需要可靠性时,要有消息 ID、已读/确认和离线补偿。
最后抓住一句话
WebSocket 的核心不是“能推送”,而是把连接、身份、心跳、消息格式和多实例路由设计清楚。关键业务状态仍以数据库或消息系统为准,WebSocket 负责实时体验,不要让它单独承担可靠存储。




