速查表:WebSocket 长连接工程清单
本表把整套 WebSocket 与长连接实战教程压缩成可勾选的清单:六组表格覆盖协议、服务端、客户端、代理与部署、观测、压测,每行一句话要点。用法有两种——上线前逐项打勾,故障时按分组倒查。
一、协议层(Protocol)
| 检查项 | 一句话要点 |
|---|
| 握手请求 | GET + Upgrade: websocket + Connection: Upgrade + Sec-WebSocket-Version: 13,缺一即 400 |
| 握手响应 | 必须返回 101 Switching Protocols 与 Sec-WebSocket-Accept,返回 200 说明中间层吃掉了 Upgrade |
| Accept 校验 | base64(sha1(key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11)),服务端必须校验 |
| 帧结构 | FIN + opcode + mask + payload len + 掩码键;客户端发往服务端的数据帧必须掩码 |
| 控制帧 | ping/pong/close 的 payload 不得超过 125 字节,不能分片 |
| 消息上限 | 服务端显式限制单帧与单消息长度(如 64KB),超限直接关闭并记原因 |
| 关闭码 | 1000 正常、1001 离开、1002 协议错误、1008 策略拒绝、1011 服务端错误 |
| 子协议 | 通过 Sec-WebSocket-Protocol 协商版本,为老客户端兼容留好升级路径 |
| HTTP/2 | 标准 WebSocket 不支持 h2,网关必须按 HTTP/1.1 处理 Upgrade 请求 |
| 文本校验 | opcode=1 的载荷必须是合法 UTF-8,否则关闭连接(1007) |
二、服务端(Server)
| 检查项 | 一句话要点 |
|---|
| 读写分离 | 一条连接两个协程:读侧串行读,写侧独占写,其他协程只往写队列投递 |
| 注册与注销 | 注册成功后立刻 defer unregister,鉴权失败、超限等分支也要关闭连接 |
| 读超时 | 设置 SetReadDeadline,并在 pong 处理里续期,否则心跳正常也会整批掉线 |
| 写超时 | 每次写都设 SetWriteDeadline,避免慢客户端把写协程钉死 |
| 心跳保活 | 服务端 30s 发一次 ping,pongWait 设为 2~2.5 倍间隔;NAT/LB 空闲超时须大于它 |
| 鉴权 | 握手期校验令牌,连接级权限缓存并设过期;支持踢下线与单点登录 |
| 背压 | 写队列必须有界;队列满时按策略丢弃、降级或摘除慢客户端,禁止阻塞广播循环 |
| 推送语义 | 单播查路由、组播查房间并去重、广播分批限速且可中断 |
| 路由表 | uid -> 连接集合 带 TTL 并定期续期,避免幽灵路由造成静默丢消息 |
| 消息去重 | 全局递增 msg_id + 客户端去重表,实现至少一次投递下的幂等 |
| 扩容 | 网关无状态、连接有状态;连接数按一致性哈希分片,扩容时按比例迁移 |
| 连接上限 | 单机设置连接上限保护,达到阈值时拒绝新连接并回 503,而不是把内存耗光 |
| 优雅关闭 | 先停止 accept,再通知客户端重连,最后 drain 后关闭,配合 defer 统一回收 |
三、客户端(Client)
| 检查项 | 一句话要点 |
|---|
| 重连退避 | 指数退避 + 随机抖动(如 1s 起、上限 30s、抖动 ±20%),防止重连风暴 |
| 重连次数 | 设置最大次数与「网络恢复即重试」的事件触发,避免无脑循环 |
| 会话恢复 | 重连后带 session_id 与本地最大 seq,服务端补发缺口消息 |
| 消息补拉 | 补拉与实时推送会重叠,必须靠 msg_id 去重 |
| 心跳参与 | 客户端必须响应 ping 并主动发心跳,不能只收不发 |
| 令牌刷新 | 长连接期间令牌可能过期,重连前先刷新,避免 401 循环 |
| 页面切换 | 移动端切后台、浏览器标签页休眠都会断开,恢复时要立即重建 |
| 多标签页 | 同浏览器多标签共享一条连接(SharedWorker)或做去重,避免多端重复渲染 |
| 发送队列 | 断线期间的发送要排队并设上限,恢复后按序补发 |
| 本地时钟 | 延迟统计使用相对时间(RTT),不要依赖客户端绝对时间 |
四、代理与部署(Proxy & Deploy)
| 检查项 | 一句话要点 |
|---|
| Upgrade 透传 | Nginx proxy_http_version 1.1 + proxy_set_header Upgrade $http_upgrade + Connection "upgrade" |
| 读超时 | 代理 proxy_read_timeout 必须大于心跳间隔;默认 60s 是断连事故的常客 |
| 缓冲开关 | 关闭 proxy_buffering,否则推送会被缓冲延迟 |
| 多级链路 | LB、网关、Nginx 每一跳都要放开 Upgrade 与超时,逐跳验证 101 |
| 真实 IP | 透传 X-Forwarded-For / X-Real-IP,用于鉴权、限流与封禁 |
| TLS 终结 | 优先在接入层终结,内部用明文减少开销;在服务端终结可上 mTLS |
| 证书与会话 | 配置完整证书链;开启会话复用降低握手开销,注意证书轮换不停机 |
| 健康检查 | 检查接口不要走 WebSocket,用普通 HTTP 探针,避免探针占满连接槽 |
| 限流位置 | 建连限流放在接入层(按 IP/uid),消息限流放在应用层(按 uid) |
| 发布方式 | 灰度按实例分批,先摘流量再 drain 连接,客户端重连到新实例 |
| 回滚 | 老客户端必须能连新服务端,协议新增字段一律向后兼容 |
五、观测(Observability)
| 检查项 | 一句话要点 |
|---|
| 在线指标 | im_ws_online_connections、im_ws_online_users,分实例采集 |
| 质量指标 | disconnects_total{reason}、heartbeat_rtt_seconds、建连失败率 |
| 延迟指标 | push_duration_seconds{target} 取 P99,分单播/组播/广播 |
| 背压指标 | 写队列长度分布、dropped_total{reason} 丢弃率 |
| 资源指标 | go_goroutines、process_open_fds、fd 使用率、内存/连接 |
| 日志字段 | conn_id、uid、gateway、remote_ip、握手耗时、断开原因、在线时长 |
| 基数纪律 | uid、conn_id、room_id 不进 label,只进日志与链路追踪 |
| 大盘分层 | 总览一屏、网关分实例、客户端按版本、接入层 101 与 upstream 超时 |
| 告警阈值 | 在线数 5 分钟跌 20% → P0;建连失败率 > 1% → P1;推送 P99 > 500ms → P1;fd > 80% → P1 |
| 阈值纪律 | 带时间窗口、带流量下界、按实例分组,并对采集失败单独告警 |
六、压测(Benchmark)
| 检查项 | 一句话要点 |
|---|
| 三层分开 | 建连、保活、收发分别压,混在一起得到的数字没有意义 |
| 握手基线 | 先用 curl --http1.1 -H 'Upgrade: websocket' ... 确认返回 101 |
| 工具选型 | k6 ws 压 WebSocket;ghz 压内部推送 RPC;wrk 只能压 HTTP 接入层 |
| 台阶加压 | 逐级升到拐点,观察「连接数涨而吞吐不涨、延迟陡增」的那一刻 |
| 恢复段 | 必须包含降压段,验证 fd、协程、内存能回落到基线 |
| 压测机瓶颈 | 独立节点、提高 ulimit -n、加源 IP 或开 tcp_tw_reuse 避免端口耗尽 |
| 同步观测 | 压测时同轴采集 ss -s、sar -n DEV、pprof 三类数据 |
| 容量公式 | 单机容量 = min(内存/每连接内存, fd 上限, CPU, 带宽),实测再留 2 倍余量 |
| 报告模板 | 连接数、msg/s、P50/P95/P99、内存/连接、CPU、错误率、拐点位置 |
常用命令速查
# 握手验证:拿到 101 才算通过
curl --http1.1 -i -N -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
'http://127.0.0.1:8080/ws?token=test'
# 连接与资源:三者应互相对得上
ss -s ; ss -tan state established | wc -l
lsof -p PID | wc -l ; ls /proc/PID/fd | wc -l ; cat /proc/PID/status | grep Threads
# 泄漏定位:栈快照 + 堆差分
go tool pprof http://127.0.0.1:6060/debug/pprof/goroutine
go tool pprof -base before.heap 'http://127.0.0.1:6060/debug/pprof/heap'
# 压测
k6 run ws-bench.js
ghz --insecure -c 50 -n 200000 --proto ./im.proto --call im.PushService.Push 10.0.0.12:9090
关键内核与系统参数
| 参数 | 建议值 | 作用 |
|---|
net.core.somaxconn | 32768 | 已完成握手的 accept 队列长度 |
net.ipv4.tcp_max_syn_backlog | 65535 | 半连接队列,重连风暴时最先撞到 |
ulimit -n / LimitNOFILE | 1048576 | 单进程 fd 上限,systemd 与 shell 两处都要改 |
net.ipv4.tcp_keepalive_time | 300 | 应用层心跳失效时的内核兜底 |
net.ipv4.tcp_tw_reuse | 1 | 主动关闭方复用 TIME_WAIT,压测机受益明显 |
net.netfilter.nf_conntrack_max | ≥ 2 × 连接数 | 连接跟踪表,满了会随机丢包 |
net.core.rmem_max / wmem_max | 16MB | 大缓冲,配合每连接读写缓冲设置 |
fs.file-max | 2097152 | 系统级 fd 上限,进程上限不能超过它 |
上线前红线
- 握手失败、鉴权失败、超限拒绝三类路径都必须关闭连接并记原因。
- 所有读写都有超时,不存在「永远等待」的调用。
- 写队列必须有界且队列满有明确策略;断开原因按 reason 拆分采集,能回答「为什么掉」。
- 每一跳代理的 Upgrade 透传与超时已逐跳验证。
- 单机连接上限、fd 上限、内核参数写入配置管理而非手工生效;压测报告与容量结论已归档。
- 优雅发布与回滚演练通过,老客户端能连新服务端。
小结:协议层守握手与帧规范,服务端守读写分离、心跳、鉴权、背压、路由与优雅关闭,客户端守退避重连、补拉与去重,代理层守 Upgrade 透传与超时,观测层守在线数、质量、延迟与资源四类指标,压测层守三层分离与拐点定容——六组清单逐项打勾,长连接服务的主要事故面就被覆盖完了。