WebSocket 连接的负载均衡






3.20/5 (4投票s)
如何处理 WebSocket 连接的负载均衡。
负载均衡问题一直是管理大型系统的热门话题。负载均衡旨在优化资源利用率、最大化吞吐量、最小化响应时间,并避免任何单个资源的过载,因此解决此问题对于性能至关重要。在本文中,我们将探讨该问题的可能解决方案。
为了更好地理解 WS 负载均衡,让我们深入了解一下 TCP 套接字背景。默认情况下,单个服务器可以处理 65,536 个套接字连接,因为这是可用的 TCP 端口的最大数量。因此,由于 WS 连接具有 TCP 的性质,并且每个 WS 客户端占用一个端口,我们可以肯定地说 WebSocket 连接的数量也是有限制的。
实际上,这只说对了一半。服务器可以处理每个 IP 地址 65,536 个套接字。因此,可以通过为服务器添加额外的网络接口来轻松扩展数量。同时,跟踪服务器上有多少连接至关重要。
一旦超过限制,您可能会遇到其他 TCP 连接的许多问题(例如,无法通过 ssh 连接到服务器)。因此,最好在应用程序代码中限制每个节点的 WS 连接数。我们在处理 websocket 的应用程序中也是这样做的。
一旦我们了解了主要的限制和克服它的方法,就让我们继续讨论负载均衡。下面我将描述我们在一个项目中所尝试的 3 种方法。请注意,所有系统部分都部署在 AWS 上,并且一些技巧和窍门仅适用于 Amazon 配置。
ELB 方法
实现负载均衡的最简单方法是使用 AWS 提供的 Elastic Load Balancer。可以将 ELB 切换到 TCP 模式,从而实现包括 websocket 在内的任何类型 TCP 连接的负载均衡。这种方法提供了
- LB 的自动故障转移;
- 负载均衡节点的自动伸缩;
- 极其简单的设置。
基本上,对于大多数常见情况来说,这是一个很好的解决方案,直到您的负载出现激增。在这种情况下,ELB 建立新连接的速度会变慢。您可以联系 Amazon 并要求他们“预热”ELB,但由于我们有负载测试需求,需要快速建立数千个 WS 连接,并且我们的客户需要系统的可用性,因此这对我们来说不是一个选择。
软件负载均衡器
我们尝试过 HAProxy 作为负载均衡器。但是,要使 HAProxy 正确工作,您应该记住我们上面讨论过的端口限制问题。为了使 HAProxy 处理超过 65k 个连接,我们应该执行以下步骤
1. 创建一组私有 IP 地址。要执行此操作,请选择您的 Amazon 实例 -> 操作 -> 网络 -> 管理私有 IP 地址。即,添加了 3 个 IP 地址:192.168.1.1、192.168.1.2、192.168.1.3。请记住,IP 地址应与您的实际应用程序服务器位于同一子网内;
2. 通过 SSH 连接到您的 HAProxy 实例并运行以下命令
$> ifconfig eth0:1 192.168.1.1
$> ifconfig eth0:2 192.168.1.2
$> ifconfig eth0:3 192.168.1.3
这将为实例添加 3 个虚拟网络接口;
3. 配置 HAProxy。以下是从接受 WS 连接的 3 个 Erlang 节点 `haproxy.cfg` 文件中摘录的一部分。
listen erlang_front :8888
mode http
balance roundrobin
timeout connect 1s
timeout queue 5s
timeout server 3600s
option httpclose
option forwardfor
server erlang-1 192.168.0.1:8888 source 192.168.1.1
server erlang-2 192.168.0.2:8888 source 192.168.1.2
server erlang-3 192.168.0.3:8888 source 192.168.1.3
现在 HAProxy 可以处理超过 65,536 个 websocket 连接,并且可以通过添加虚拟网络接口轻松增加连接限制。此外,它还可以相当快地建立新连接。
尽管存在以下缺点,但这种方法似乎是可行的
- 应使用 `keepalived` 等工具手动设置 HAProxy 实例的故障转移;
- 每当您添加新的 Erlang 节点时,都必须对 HAProxy 进行重新配置;
- 随着连接数量的增长,没有选项可以水平扩展 HAProxy。我们只有垂直扩展的选项,因此当我们的活跃用户越来越多时,我们就需要为 HAProxy(和 HAProxy 镜像节点)获取更昂贵的实例。
我们对这些缺点表示满意,但实现了一个更简单的解决方案。这是可能的,因为我们已经实现了一些代码,并且我们的系统设计允许我们使用自定义方法。
自定义方法
为了继续,让我们回顾一下显示我们系统架构的以下图表。
我们有一个 JavaScript 客户端应用程序、一个负责用户授权的身份验证应用程序以及一个具有主要应用程序功能的 Erlang 应用程序。流程如下
- 客户端向身份验证应用程序发送带有凭据的 HTTP 请求;
- 身份验证应用程序检查凭据,生成令牌,并通过 HTTP 请求将其发送到 Erlang 集群;
- Erlang 应用程序确认收到了令牌,并向身份验证应用程序发送带有确认的 HTTP 响应;
- 身份验证应用程序向客户端应用程序发送 HTTP 响应。此响应包含生成的令牌;
- 客户端使用令牌通过我们的 HAProxy 负载均衡器与 Erlang 应用程序建立 websocket 连接。
这是我们的基本流程,已稍作修改。我们在 Erlang 应用程序中添加了一个简单的模块来跟踪每个 Erlang 节点上的 websocket 连接数。由于 Erlang 的“分布式”特性,每个节点都知道其他节点的连接。
因此,我们可以选择一个连接数较少的节点。我们获取该节点的公共 IP 地址,然后将其发送回身份验证应用程序。然后,身份验证应用程序将此 IP 地址与令牌一起发送回客户端。客户端使用接收到的 IP 地址和令牌建立 WS 连接。因此,最终的图表如下所示
现在我们可以
- 摆脱 WS 负载均衡器,这使我们的系统更简单;
- 添加 Erlang 节点而无需重新配置系统的其他部分。
此外
- WS 连接现在在 Erlang 节点之间平均分配;
- 系统可以轻松地水平扩展;
- 我们不必为 Erlang 节点使用 Elastic IPs。