50 万长连接背后的技术攻坚战:WebSocket 网关高并发优化实战从单机 1 万连接频繁 Full GC,到 16 节点稳定承载 50 万长连接,这不是一次简单的“框架替换”,而是一场围绕 I/O 模型、内存模型、消息路由、发布治理和容量规划的系统性重构。一、先说结论:50 万长连接,难点从来不只是“连上”很多团队做 WebSocket 时,第一阶段都能很快跑通 Demo:客户端连上来,服务端收消息、推消息,看起来一切正常。但一旦进入直播、IM、IoT、行情推送、在线教育这类高实时场景,问题会很快从“功能正确”转向“系统是否还能活着”。真正困难的不是建立连接,而是下面这组同时成立的约束:连接数大:几十万连接长时间驻留推送频繁:消息吞吐从每秒几万到上百万延迟敏感:P99 延迟通常要求在几十毫秒内波峰明显:活动开始、发布会、开播瞬间会发生重连风暴发布频繁:网关必须支持滚动升级和弹性扩缩容成本受限:不能靠无限堆机器解决我们服务的是一个千万 DAU 的互动直播平台,核心链路包括弹幕、礼物、PK 状态、排行榜、房间控制消息等。早期方案采用Spring WebSocket + Tomcat + 业务逻辑同进程处理。平峰时问题不明显,但大促和头部直播间活动一来,单机到1.2 万连接左右就开始出现一连串症状:Full GC 频繁,STW 时间不断变长CPU 长时间接近 90%消息发送堆积,端到端延迟超过 10 秒慢客户端拖垮发送线程扩容效果不明显,新增节点无法快速分担老连接这类问题表面上是“WebSocket 撑不住”,本质上是架构分层、I/O 模型、内存分配和工程治理都还停留在单体应用思路。这篇文章会完整展开我们是如何把系统升级为一套生产级 WebSocket 网关体系的,并回答四个关键问题:为什么很多系统在 1 万连接上下就开始失稳?50 万长连接的架构应该怎么拆?Netty 网关怎样写到接近生产可用,而不是只停留在示例代码?真正上线后,如何处理背压、扩缩容、观测、故障和容量风险?二、先把问题讲透:为什么 1 万连接就容易崩2.1 长连接系统的资源消耗,不只在 Java 堆很多人做容量评估时,习惯只看 JVM-Xmx,这是第一层误区。长连接系统的内存消耗至少包括:JVM 堆:业务对象、会话元数据、缓存、消息对象堆外内存:Netty Direct Buffer、内存池线程栈:Boss/Worker 线程、业务线程、异步线程池内核 Socket Buffer:收发缓冲区文件描述符与连接控制块:TCP 状态、协议栈开销也就是说,JVM Heap 没爆并不代表进程安全,很多长连接服务的真实 OOM 是堆外或系统级内存先出问题。2.2 阻塞式思路做长连接,天然不经济如果系统的线程模型让大量连接长期占用线程,即使不是纯 BIO,线程上下文切换、线程栈内存和锁竞争也会把机器拖垮。长连接的本质是:绝大多数连接大部分时间都处于空闲活跃连接是少数,但会在短时间爆发单连接消息通常很小,但连接总量极大这意味着正确的设计必须是“少量事件循环线程驱动海量连接”,而不是“每条连接都配套一个重量级执行单元”。2.3 真正打爆系统的往往不是连接,而是发送侧背压失控很多 WebSocket 系统压测时只验证“能建立多少连接”,却没有验证“当 20% 客户端变慢、1% 客户端断网抖动、热点房间突然每秒 10 万条消息时会怎样”。一旦发送端没有背压控制,常见后果是:慢客户端的待发送队列越积越大ByteBuf 和消息对象不断堆积单个热点用户或热点房间拖垮整个 EventLoopKafka 消费速度跟不上,进一步产生全链路堆积所以高并发 WebSocket 的关键指标不能只看连接数,还要看:每连接平均内存占用单节点消息收发吞吐慢连接占比上升时的退化曲线端到端 P99 延迟Kafka Lag 与发送积压的联动关系2.4 连接是状态,网关如果“有业务脑子”,扩容就会很痛苦早期最常见的问题是把用户状态、房间路由、权限校验、推送逻辑、消息编解码都塞进一个服务实例。这样做的直接后果是:连接和业务强耦合,扩容慢发布时必须等待旧连接自然消亡业务改动导致网关频繁重启某个业务逻辑抖动会直接放大为连接层事故我们后面所有优化的核心思想,其实可以概括为一句话:把连接层做薄,把状态做轻,把业务从网关中剥离。三、目标重设:不是“单机能扛”,而是“系统能稳定扩”在立项重构之前,我们重新定义了目标,而不是简单写一句“支持 50 万连接”:3.1 功能目标支持浏览器和 App 双端 WebSocket 接入支持用户定向推送、房间广播、系统广播支持认证、续约、断线重连、踢人下线3.2 性能目标单机稳定承载8 万 ~ 10 万连接集群规模稳定支撑50 万+长连接正常流量下消息端到端 P99 50ms单房间热点广播场景不出现全局雪崩3.3 工程目标网关无状态化,可快速扩容和滚动升级可观测,可定位单连接、单房间、单节点问题慢客户端可被识别、限流和剔除故障时支持降级,不让连接层把业务层一起拖死3.4 容量目标在架构评审时,我们专门做了一版粗粒度容量估算。这个动作非常关键,因为它会直接决定节点规格、线程配置和扩容策略。以单节点 10 万连接为例,假设:连接元数据平均 0.5KBNetty 与业务附加对象平均 1KB堆外缓冲和池化切片折算平均 8KB内核收发缓冲经过调优后折算平均 6KB那么单连接的综合驻留成本约为:0.5KB + 1KB + 8KB + 6KB = 15.5KB / connection10 万连接大约需要:100000 * 15.5KB ≈ 1.48GB再加上:JVM 堆:2GB ~ 3GBDirect Memory:1GB ~ 2GB线程栈、页缓存、进程额外开销:1GB 左右单机 8C16G 才勉强有操作空间,生产上更合理的是8C32G或16C32G,并配合较低的堆配置和较高的堆外预算。这类估算不会百分之百准确,但它能帮团队在正式编码前就避开明显错误的机器规格和参数方案。四、架构重构:把系统拆成四层,而不是一个“大 WebSocket 服务”重构后的整体架构如下:+-------------------------------+ | Global DNS / SLB | +-------------------------------+ | v +-------------------------------+ | L4 LB / Ingress / Envoy | | - TLS 终止或透传 | | - 一致性哈希/会话亲和 | +-------------------------------+ | v +-------------------------------------------+ | WebSocket Gateway Cluster | | - 握手鉴权 | | - 连接管理 | | - 心跳与空闲检测 | | - 本地路由 | | - 背压与发送队列治理 | +-------------------------------------------+ | | | v v v +---------------+ +---------------+ +---------------+ | Redis Cluster | | Kafka Cluster | | Metrics/Trace | | user-node | | event bus | | logs/alerts | +---------------+ +---------------+ +---------------+ | v +-----------------------------+ | Business Services Cluster | | IM / room / live / control | +-----------------------------+4.1 四层职责拆分第一层:接入层负责网络入口、TLS、会话亲和、四层负载均衡。这里不承载复杂业务逻辑,核心目标只有两个:让连接尽量均匀落到网关节点在节点下线时尽快停止新连接流入第二层:连接网关层这是本文重点。它负责:协议升级与连接认证连接生命周期管理用户到本地 Channel 的索引用户到节点路由的注册与续约本地消息发送、房间广播、慢连接剔除与 Kafka、Redis、监控系统交互第三层:消息总线层通过 Kafka 解耦业务服务和网关:
50 万长连接背后的技术攻坚战:WebSocket 网关高并发优化实战
50 万长连接背后的技术攻坚战:WebSocket 网关高并发优化实战从单机 1 万连接频繁 Full GC,到 16 节点稳定承载 50 万长连接,这不是一次简单的“框架替换”,而是一场围绕 I/O 模型、内存模型、消息路由、发布治理和容量规划的系统性重构。一、先说结论:50 万长连接,难点从来不只是“连上”很多团队做 WebSocket 时,第一阶段都能很快跑通 Demo:客户端连上来,服务端收消息、推消息,看起来一切正常。但一旦进入直播、IM、IoT、行情推送、在线教育这类高实时场景,问题会很快从“功能正确”转向“系统是否还能活着”。真正困难的不是建立连接,而是下面这组同时成立的约束:连接数大:几十万连接长时间驻留推送频繁:消息吞吐从每秒几万到上百万延迟敏感:P99 延迟通常要求在几十毫秒内波峰明显:活动开始、发布会、开播瞬间会发生重连风暴发布频繁:网关必须支持滚动升级和弹性扩缩容成本受限:不能靠无限堆机器解决我们服务的是一个千万 DAU 的互动直播平台,核心链路包括弹幕、礼物、PK 状态、排行榜、房间控制消息等。早期方案采用Spring WebSocket + Tomcat + 业务逻辑同进程处理。平峰时问题不明显,但大促和头部直播间活动一来,单机到1.2 万连接左右就开始出现一连串症状:Full GC 频繁,STW 时间不断变长CPU 长时间接近 90%消息发送堆积,端到端延迟超过 10 秒慢客户端拖垮发送线程扩容效果不明显,新增节点无法快速分担老连接这类问题表面上是“WebSocket 撑不住”,本质上是架构分层、I/O 模型、内存分配和工程治理都还停留在单体应用思路。这篇文章会完整展开我们是如何把系统升级为一套生产级 WebSocket 网关体系的,并回答四个关键问题:为什么很多系统在 1 万连接上下就开始失稳?50 万长连接的架构应该怎么拆?Netty 网关怎样写到接近生产可用,而不是只停留在示例代码?真正上线后,如何处理背压、扩缩容、观测、故障和容量风险?二、先把问题讲透:为什么 1 万连接就容易崩2.1 长连接系统的资源消耗,不只在 Java 堆很多人做容量评估时,习惯只看 JVM-Xmx,这是第一层误区。长连接系统的内存消耗至少包括:JVM 堆:业务对象、会话元数据、缓存、消息对象堆外内存:Netty Direct Buffer、内存池线程栈:Boss/Worker 线程、业务线程、异步线程池内核 Socket Buffer:收发缓冲区文件描述符与连接控制块:TCP 状态、协议栈开销也就是说,JVM Heap 没爆并不代表进程安全,很多长连接服务的真实 OOM 是堆外或系统级内存先出问题。2.2 阻塞式思路做长连接,天然不经济如果系统的线程模型让大量连接长期占用线程,即使不是纯 BIO,线程上下文切换、线程栈内存和锁竞争也会把机器拖垮。长连接的本质是:绝大多数连接大部分时间都处于空闲活跃连接是少数,但会在短时间爆发单连接消息通常很小,但连接总量极大这意味着正确的设计必须是“少量事件循环线程驱动海量连接”,而不是“每条连接都配套一个重量级执行单元”。2.3 真正打爆系统的往往不是连接,而是发送侧背压失控很多 WebSocket 系统压测时只验证“能建立多少连接”,却没有验证“当 20% 客户端变慢、1% 客户端断网抖动、热点房间突然每秒 10 万条消息时会怎样”。一旦发送端没有背压控制,常见后果是:慢客户端的待发送队列越积越大ByteBuf 和消息对象不断堆积单个热点用户或热点房间拖垮整个 EventLoopKafka 消费速度跟不上,进一步产生全链路堆积所以高并发 WebSocket 的关键指标不能只看连接数,还要看:每连接平均内存占用单节点消息收发吞吐慢连接占比上升时的退化曲线端到端 P99 延迟Kafka Lag 与发送积压的联动关系2.4 连接是状态,网关如果“有业务脑子”,扩容就会很痛苦早期最常见的问题是把用户状态、房间路由、权限校验、推送逻辑、消息编解码都塞进一个服务实例。这样做的直接后果是:连接和业务强耦合,扩容慢发布时必须等待旧连接自然消亡业务改动导致网关频繁重启某个业务逻辑抖动会直接放大为连接层事故我们后面所有优化的核心思想,其实可以概括为一句话:把连接层做薄,把状态做轻,把业务从网关中剥离。三、目标重设:不是“单机能扛”,而是“系统能稳定扩”在立项重构之前,我们重新定义了目标,而不是简单写一句“支持 50 万连接”:3.1 功能目标支持浏览器和 App 双端 WebSocket 接入支持用户定向推送、房间广播、系统广播支持认证、续约、断线重连、踢人下线3.2 性能目标单机稳定承载8 万 ~ 10 万连接集群规模稳定支撑50 万+长连接正常流量下消息端到端 P99 50ms单房间热点广播场景不出现全局雪崩3.3 工程目标网关无状态化,可快速扩容和滚动升级可观测,可定位单连接、单房间、单节点问题慢客户端可被识别、限流和剔除故障时支持降级,不让连接层把业务层一起拖死3.4 容量目标在架构评审时,我们专门做了一版粗粒度容量估算。这个动作非常关键,因为它会直接决定节点规格、线程配置和扩容策略。以单节点 10 万连接为例,假设:连接元数据平均 0.5KBNetty 与业务附加对象平均 1KB堆外缓冲和池化切片折算平均 8KB内核收发缓冲经过调优后折算平均 6KB那么单连接的综合驻留成本约为:0.5KB + 1KB + 8KB + 6KB = 15.5KB / connection10 万连接大约需要:100000 * 15.5KB ≈ 1.48GB再加上:JVM 堆:2GB ~ 3GBDirect Memory:1GB ~ 2GB线程栈、页缓存、进程额外开销:1GB 左右单机 8C16G 才勉强有操作空间,生产上更合理的是8C32G或16C32G,并配合较低的堆配置和较高的堆外预算。这类估算不会百分之百准确,但它能帮团队在正式编码前就避开明显错误的机器规格和参数方案。四、架构重构:把系统拆成四层,而不是一个“大 WebSocket 服务”重构后的整体架构如下:+-------------------------------+ | Global DNS / SLB | +-------------------------------+ | v +-------------------------------+ | L4 LB / Ingress / Envoy | | - TLS 终止或透传 | | - 一致性哈希/会话亲和 | +-------------------------------+ | v +-------------------------------------------+ | WebSocket Gateway Cluster | | - 握手鉴权 | | - 连接管理 | | - 心跳与空闲检测 | | - 本地路由 | | - 背压与发送队列治理 | +-------------------------------------------+ | | | v v v +---------------+ +---------------+ +---------------+ | Redis Cluster | | Kafka Cluster | | Metrics/Trace | | user-node | | event bus | | logs/alerts | +---------------+ +---------------+ +---------------+ | v +-----------------------------+ | Business Services Cluster | | IM / room / live / control | +-----------------------------+4.1 四层职责拆分第一层:接入层负责网络入口、TLS、会话亲和、四层负载均衡。这里不承载复杂业务逻辑,核心目标只有两个:让连接尽量均匀落到网关节点在节点下线时尽快停止新连接流入第二层:连接网关层这是本文重点。它负责:协议升级与连接认证连接生命周期管理用户到本地 Channel 的索引用户到节点路由的注册与续约本地消息发送、房间广播、慢连接剔除与 Kafka、Redis、监控系统交互第三层:消息总线层通过 Kafka 解耦业务服务和网关: