通知在凌晨2点47分传来。我们的实时聊天应用程序在80,000个并发WebSocket连接的压力下不堪重负,用户遇到消息丢失和连接超时的问题。到早上,我们需要可靠地处理100,000多个连接。这就是我们如何解决这个问题的故事,以及在大规模WebSocket负载均衡方面来之不易的经验教训。
核心摘要
- WebSocket 负载均衡和普通 HTTP 不同,因为连接是长连接、有状态、通常还需要会话粘性。
- HAProxy 在这个案例中成功处理 100,000 多个并发连接,CPU 使用率低、内存占用小,但基于源 IP 的粘性在 NAT 场景下会造成后端倾斜。
- Nginx 能处理 100,000 个连接,并且和现有 HTTP 基础设施集成较好,但原文测试中内存使用量比 HAProxy 更高。
- 自定义 Go 代理可以基于用户 ID 做一致性哈希,解决 NAT 带来的分布问题,但开发和维护成本明显更高。
- 最终建议是:大多数团队从 HAProxy 开始;已有 Nginx 生态且能接受更高内存占用时可选 Nginx;只有在现成方案无法满足明确需求时才考虑自定义方案。
先理解几个 WebSocket 负载均衡术语
- 长连接:WebSocket 建立后会保持连接,用户会话可能持续数小时,不像普通 HTTP 请求那样很快结束。
- 会话持久性:同一个用户或同一个会话在连接期间需要稳定落到同一后端,否则状态管理会变复杂。
- NAT 问题:多个用户可能从同一个出口 IP 访问服务,如果只按源 IP 分流,可能导致大量连接集中到同一台后端。
- 一致性哈希:根据用户 ID 等稳定键做路由,让同一用户尽量落到同一后端,同时在节点变化时减少迁移范围。
问题:当简单的解决方案失效时
传统的HTTP负载均衡看似简单——将请求分发到各个服务器,就大功告成了。而WebSocket连接则完全是另一回事。它们具有持久性、有状态性和粘性。当用户连接时,在整个会话期间(有时一次长达数小时),他们会一直保持该连接。
我们最初的设置简单得有些离谱:仅用一个Node.js服务器来处理所有连接。对于最初的10000名用户,它运行得非常出色。但到了50000名用户时,我们开始察觉到内存压力。而到了80000名用户时,整个系统就变得不稳定了。
警钟敲响是在我们最大的客户——一家金融交易平台——在开市期间无法获取实时价格更新之时。系统每宕机一秒,就意味着实实在在的资金损失。
这个案例的关键不是“连接数到 100,000”这个数字本身,而是连接数、连接持续时间、业务实时性和故障成本叠加以后,原来的单点 Node.js 方案已经无法承受。
第一轮:HAProxy(老牌可靠工具)

HAProxy似乎是显而易见的选择。它久经考验,每天在互联网上处理数百万个连接,并且对WebSocket有出色的支持。我们的配置很简单:
frontend websocket_frontend
bind *:80
default_backend websocket_servers
backend websocket_servers
balance source
server ws1 10.0.1.10:3000 check
server ws2 10.0.1.11:3000 check
server ws3 10.0.1.12:3000 check
balance source 指令可确保来自同一IP地址的连接始终路由到同一后端服务器,这对于维护WebSocket会话状态至关重要。
结果:
- 成功处理了10万多个并发连接
- 我们的负载均衡器上的CPU使用率保持在15%以下
- 内存占用出奇地小(约2GB)
- 连接建立时间:平均约45毫秒
注意事项: 当用户从公司的网络地址转换(NAT)出网连接时,基于源IP的HAProxy会话持久性出现了问题。我们会看到数千个连接都集中到一台后端服务器上,而其他服务器则处于闲置状态。
解决方案需要切换到基于cookie的会话持久性,并修改我们的WebSocket握手以包含会话令牌。这增加了复杂性,但使负载分配更加均匀。
这部分可以总结为:HAProxy 本身能扛住连接规模,但生产环境的真实网络拓扑会影响负载分布。源 IP 粘性在企业 NAT 场景下并不总是可靠。
第二轮:Nginx(瑞士军刀)

Nginx的吸引力在于它的通用性。我们已经在使用它来处理HTTP流量,因此整合负载均衡似乎是顺理成章的。配置需要使用 stream 模块:
stream {
upstream websocket_backend {
ip_hash;
server 10.0.1.10:3000;
server 10.0.1.11:3000;
server 10.0.1.12:3000;
}
server {
listen 80;
proxy_pass websocket_backend;
proxy_timeout 1s;
proxy_responses 1;
}
}
结果:
- 处理了10万个连接,仍有剩余空间
- 与我们现有基础设施更好地集成
- 内置健康检查和故障转移
- 连接建立时间:平均约52毫秒
意外发现:在相同的工作负载下,Nginx 消耗的内存比 HAProxy 显著更多,大约多出 40%。我们在任何地方都没有找到相关记录,但我们的监测结果清晰地显示了这一点。每增加10,000个并发连接,Nginx 比 HAProxy 大约多使用800MB内存。
ip_hash 指令比HAProxy的默认源负载均衡提供了更好的分配效果,但我们仍然遇到了 NAT 的问题。
Nginx 的优势在于团队熟悉度和生态整合,但在 WebSocket 场景下仍然要认真验证 stream 模块配置、健康检查、超时设置和连接分布。
第三轮:定制解决方案(终极手段)
在解决了HAProxy和Nginx中的会话持久化问题后,我们决定构建自己的支持WebSocket的负载均衡器。这个想法很简单:我们不再依赖基于IP的路由,而是实现基于用户会话的应用层路由。
使用Go语言,我们构建了一个轻量级代理,该代理:
- 拦截WebSocket握手
- 提取用户身份验证令牌
- 根据用户ID的一致性哈希对连接进行路由
- 维护用户到服务器分配的实时映射
type ConnectionRouter struct {
servers []string
userMap sync.Map
hashRing *consistent.Map
}
func (cr *ConnectionRouter) RouteConnection(userID string) string {
return cr.hashRing.Get(userID)
}
结果:
- 完美的负载分配 —— 不再有NAT问题
- 连接建立时间:平均约38毫秒
- 三种解决方案中最低的内存使用量
- 内置连接跟踪与分析
成本:开发时间相当长——初始版本花了三周时间,还有持续的维护工作,以及维护关键基础设施的责任。每个漏洞都成了我们需要解决的问题。
自定义方案的性能和控制力更强,但它把负载均衡器从“成熟基础设施”变成了“自己维护的关键系统”。这类决策要把长期运维成本算进去。
性能对比:数据
在对这三种解决方案运行相同的工作负载后:

这张对比图适合用来帮助决策,但不能脱离上下文直接套用。连接模型、认证方式、消息频率、后端状态管理、网络环境和监控能力都会影响最终结果。
隐性成本
除了原始性能数据外,每种解决方案还带来了不同的运营负担:
- 要深入理解HAProxy,就必须熟悉其配置语法。相关文档很出色,但学习难度较大。调试连接问题时,常常需要深入研究HAProxy日志,并将其与应用程序日志关联起来。
- nginx 让人感觉似曾相识,但与HTTP相比,它在WebSocket处理方面存在细微差异。流模块的配置文档比HTTP模块少,这导致需要反复试验来调试。
- 定制解决方案让我们拥有完全的控制权,但也让我们要对未曾考虑到的极端情况负责。处理连接超时、服务器故障和正常关闭等问题需要仔细思考和测试。
现实世界的经验教训
三个月后,以下是在生产过程中真正重要的因素:
- 监控就是一切。 无论你选择哪种负载均衡器,你都需要了解连接数、分布情况和健康检查。我们构建了自定义仪表板,用于展示实时连接映射和服务器负载情况。
- 为故障做好规划。 这三种解决方案都能处理服务器故障,但它们的表现有所不同。HAProxy和Nginx会立即重定向新连接,但可能会中断现有连接。我们的定制解决方案可以平稳地迁移连接,但这需要额外的复杂操作。
- 网络拓扑比你想象的更重要。 企业防火墙、NAT 配置和代理服务器对 WebSocket 连接的影响与对 HTTP 请求的影响截然不同。要使用真实用户网络进行测试,而不仅仅是在开发环境中测试。
这些经验说明,WebSocket 负载均衡不能只做压测,还要做故障演练和真实网络验证。否则系统在实验室里看起来没问题,到了企业客户网络里可能马上出现连接倾斜。
建议
对于大多数面临这一挑战的团队来说,可以从HAProxy入手。它是高连接数场景下久经考验的解决方案,有出色的文档资料,并且拥有庞大的社区。其配置的复杂性与其可靠性相比是值得的。
如果您已经深度融入Nginx生态系统,并且能够接受较高的内存使用量,那么可以考虑使用Nginx。管理单一类型的负载均衡器在操作上的简便性,其优势可能超过资源成本。
只有在你有HAProxy和Nginx都无法满足的特定需求、强大的开发团队以及维护自定义基础设施的资源时,才构建自定义解决方案。性能提升是实实在在的,但这是以高昂的运营成本为代价的。
可以把选型压缩成一个简单判断:先选成熟组件,再验证真实网络;只有成熟组件的会话路由能力确实不够,并且团队能承担长期维护责任,才把自定义方案放到生产关键路径上。
六个月后
我们的HAProxy解决方案已在生产环境中运行了六个月,在重大市场活动期间能够处理180,000个并发连接的峰值负载。凌晨2点47分的警报已经停止,而且我们的交易平台客户续签了合同。
有时候,那些看似乏味却行之有效的解决方案才是正确的选择。但了解其他替代方案及其利弊,能让我们成为更出色的工程师,帮助我们做出明智的决策,而不仅仅是照着教程去做。
下次你面临类似的扩展难题时,请记住,最佳解决方案并不总是技术上最令人赞叹的那个。而是你的团队能够在生产环境中可靠地实施、监控和维护的方案。
常见问题
100,000 个 WebSocket 连接应该优先选 HAProxy 还是 Nginx?
原文建议大多数团队从 HAProxy 入手,因为它在高连接数场景下成熟、文档完善、社区大,并且在案例中资源占用较低。如果团队已经深度使用 Nginx,并且能接受较高内存使用量,也可以考虑 Nginx。
为什么基于源 IP 的粘性会有问题?
在企业网络或 NAT 场景下,很多用户可能共享同一个出口 IP。按源 IP 分流时,这些连接会被路由到同一台后端,导致负载不均衡。
自定义 WebSocket 负载均衡器什么时候值得做?
只有在 HAProxy 和 Nginx 都无法满足明确需求,并且团队有能力长期维护关键基础设施时才值得做。原文中的自定义方案提升了控制力和分布效果,但也带来了三周初始开发和持续维护成本。
WebSocket 负载均衡最容易被忽略的是什么?
网络拓扑和监控。企业防火墙、NAT、代理服务器都会影响连接分布;同时必须监控连接数、后端分布、健康检查和服务器负载,不能只看总连接数。
总结
这次 100,000 个 WebSocket 连接的实践给出的结论很朴素:成熟、可监控、可维护的方案通常比技术上更炫的方案更适合生产。HAProxy、Nginx 和自定义代理都能工作,但它们的成本不同。真正的选型标准不是单项性能,而是团队能否在真实网络、真实故障和长期运维中稳定掌控它。
原文:https://medium.com/@yashbatra11111/load-balancing-100-000-websocket-connections-haproxy-vs-nginx-vs-custom-4fe78f68c1ce