Go 有几个流行的 WebSocket 库,可以轻松地向应用程序添加实时通信。让我们来看看一些最常用的库以及它们的比较。
使用 WebSocket 时,最大的挑战之一是管理内存利用率。在传统的 HTTP 连接中,每个 HTTP 编写器和读取器通常使用 4KB 的内存。使用 WebSockets,WebSocket HTTP 编写器需要额外的 4KB 内存,每个 goroutine 最多可以使用 8KB 的内存。这意味着对于一百万个连接,内存利用率可以达到 20GB。下图显示了存在的不同组件:
第一个解决方案是使用 Linux 系统调用 Epoll。Epoll 是一种可扩展的 I/O 事件通知机制,可监控 I/O 的多个文件描述符。通过实施 Epoll,内存利用率可以降低大约 30%。以下是使用 Epoll 的示例代码:
WebSocket 是一种协议,用于客户端和服务器之间通过长期的双向连接进行双向通信。因此,WebSocket 设计用于异步通信,其中服务器和客户端都可以随时相互发送消息。但是,可能存在需要同步请求/响应通信的用例。例如:一个服务器向客户端发送请求,需要立即响应另一个消费者服务器。
实现与 WebSocket 同步通信的一种方法是对每个请求消息使用唯一标识符,例如 UUID。服务器可以向客户端发送包含唯一标识符的请求消息以及任何其他必要的数据。然后,客户端可以处理请求,并使用相同的唯一标识符将响应消息发送回服务器。然后,服务器可以使用此标识符将响应消息与原始请求进行匹配,并相应地处理响应。
在 Go 中,通道可用于实现同步请求/响应方法,即使服务器发起请求也是如此。服务器可以为每个请求创建一个通道,并使用 UUID 作为通道的密钥。当服务器向客户端发送请求时,它可以创建具有唯一 UUID 的通道,通过 WebSocket 连接发送请求,并在通道上阻止,直到收到响应。同时,客户端可以侦听传入的请求,处理请求,并使用与原始请求相同的 UUID 通过 WebSocket 连接发送回响应。当服务器收到具有匹配 UUID 的响应时,它可以取消阻止通道并继续处理响应。下图详细介绍了此流程:
WebSocket 服务器可以水平扩展,通过采用允许动态服务器生成和服务器注册表的体系结构来处理大量客户端连接。一种可能的方法是使用 AWS Auto Scaling Groups (ASG) 根据连接需求动态生成新服务器。然后,每个生成的服务器都可以向 AWS Route 53 DNS 注册自身,以确保客户端可以连接到它。
为了维护连接的客户端及其连接到的服务器的状态,可以在MySQL数据库之上实现服务器注册表。此注册表可以跟踪每个客户端连接到哪个服务器,并可用于将传入消息路由到正确的服务器。
当客户端发起连接请求时,可以将请求发送到负载均衡器,负载均衡器可以将请求分发到可用服务器之一。然后,服务器可以检查服务器注册表以查看客户端是否已连接到另一台服务器,如果是,则将连接路由到相应的服务器。如果客户端尚未连接,服务器可以将客户端的连接添加到注册表,并开始处理传入的消息。
如果服务器过载,ASG 可以自动生成新服务器来处理额外的负载,并且可以相应地更新服务器注册表以反映客户端应连接到的新服务器。
在这篇文章中,我们探讨了在扩展 WebSocket 以进行涉及持久连接的大规模通信时面临的挑战和可能的解决方案。
我们深入研究了WebSockets的Go(Golang)生态系统,讨论了各种可用的库以及它们的比较。
通过实施优化的缓冲区分配和使用 EPoll,我们将内存利用率降低了多达 97%。此外,我们还介绍了同步通信的需求,以及如何在 Go 中使用消息 UUID 和通道来实现同步通信。
最后,我们研究了如何水平扩展系统,方法是使用 AWS ASG 动态生成服务器,维护基于 MySQL 构建的服务器注册表,并使用 AWS Route53 DNS 启用新服务器的动态注册。
总体而言,通过正确的架构设计和实现,WebSocket 可以有效地扩展,与传统的 TCP 连接相比,占用空间很小,并允许数百万规模的实时通信。