百万级WebSocket连接需调优系统参数与I/O模型:启动前用syscall.Setrlimit提升文件描述符限制,启用epoll/kqueue非阻塞轮询替代“一连接一goroutine”,池化缓冲区(如gobwas/ws),并调大nf_conntrack_max。

靠默认的 gorilla/websocket + “一连接一 goroutine” 模式,撑不过 10 万连接;百万级必须动操作系统、换 I/O 模型、池化内存。
ulimit 和 syscall.Setrlimit 必须在 Listen 前调用
Linux 默认 ulimit -n 是 1024,连 1 万连接都卡死在 accept 阶段。光在 shell 里临时改不够——Go 进程启动后才读取软限制,不主动申请,新连接会静默失败。
- 必须在
http.ListenAndServe或net.Listen之前调用syscall.Setrlimit - 先用
syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)读当前值 - 把
rLimit.Cur设成rLimit.Max,否则软限制仍卡住 - 注意:某些容器环境(如 Docker)需加
--ulimit nofile=1048576:1048576,否则Setrlimit会因权限被拒
别让每个连接起 goroutine —— 改用 epoll/kqueue 轮询
conn.ReadMessage() 是阻塞调用,百万连接 = 百万个常驻 goroutine,栈内存和调度开销直接崩掉。正确路径是把 socket 设为非阻塞,用系统事件机制统一监听。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- Linux 下用
epoll(Go 标准库不暴露,需封装unix.EpollCreate1/unix.EpollWait) - macOS/BSD 下用
kqueue,逻辑类似但 API 不同 - 每个连接不再有专属 reader goroutine,而是由 1–2 个 epoll loop goroutine 轮询所有 fd 的
EPOLLIN - 收到数据后,再派发到固定数量(比如 32 个)worker goroutine 解析,避免 goroutine 数量随连接数线性增长
缓冲区不能每连接 malloc —— 换 gobwas/ws 或自己池化
gorilla/websocket 升级连接时会 new 出独立的 bufio.Reader/bufio.Writer,默认缓冲区 4KB+,百万连接光这部分就吃掉 4GB+ 内存。
- 换
gobwas/ws:它的ws.UpgradeHTTP返回的Conn支持复用底层[]byte缓冲区,实测内存降 60%+ - 若坚持用
gorilla,可用sync.Pool管理[][]byte,但必须确保:缓冲区只在单次读/写生命周期内复用,不跨 goroutine 传递,否则可能读到脏数据 - 缓冲区大小建议对齐 CPU cache line(通常 64 字节),比如 4096 或 8192,避免 false sharing 拖慢原子操作
别忘了 nf_conntrack_max,否则连接随机断连
内核连接跟踪表(nf_conntrack)默认只存 65536 条记录。不调它,新连接会触发 nf_conntrack: table full, dropping packet,现象是客户端 ping 不通、连接秒断,日志里却看不到明显报错。
- 查当前值:
cat /proc/sys/net/netfilter/nf_conntrack_max - 临时调高:
echo 2621440 > /proc/sys/net/netfilter/nf_conntrack_max - 永久生效需写入
/etc/sysctl.conf:添加net.netfilter.nf_conntrack_max = 2621440 - 注意:这个值不是越大越好,过大会吃光内核内存;建议设为预期并发连接数的 2–3 倍
真正卡住百万连接的,往往不是 Go 代码本身,而是 nf_conntrack_max 这种藏得深、报错隐晦的系统参数,以及 Setrlimit 调用时机这种“差一行就全挂”的细节。

















