我正在尝试构建一个包含即时消息模块的应用程序,主要挑战之一是无论用户数量或交换的消息如何,都要保持应用程序的可扩展性。
在一篇文章中,我读到可以使用带有"订阅"的 GraphQL 构建实时应用程序,除此之外,它是一种简单易用的协议,并且具有最大限度地减少往返对象检索的优点,因此减少了资源使用。
但是,如果我们需要向系统添加新的服务器/节点以便水平扩展,该怎么办?使用 GraphQL 可以做到这一点吗?
以允许水平扩展的websockets实现为例,有SocketCluster。我想知道单独由 GraphQL 开发的应用程序是否可以跨多个节点/机器进行扩展,或者必须与另一个框架(如 SocketCluster(一起使用才能实现这一目标。
很快 - 是的。我们已经做到了,而且效果很好。
诀窍是,在水平扩展方面,您必须更深入地考虑不仅仅是 API worker 应用程序。如果你想要推送架构,它需要从一开始就是异步的。
为了实现它,我们使用了队列系统,即RabbitMQ。
想象一下生成报告的场景,最多可能需要 10 分钟:
- 客户端通过 WebSocket 连接到我们的 GraphQL API(实例 1(
- 客户端发送命令以通过 WebSocket 生成报告 API 为命令生成令牌,并将用于
- 生成报告的命令放入 CommandQueue(在 RabbitMQ 中(,并将令牌返回给客户端。
- 客户端使用令牌订阅其命令结果的事件
- 某些后端工作线程选取命令并执行报告生成过程
- 在此期间,GraphQL API(实例 1(死亡
- 客户端自动重新连接到 GraphQL API(实例 2(
- 客户端使用以前获取的令牌续订订阅
- 工作线程已完成,结果在事件队列 (RabbitMQ( 上
- 我们所有的 GraphQL 实例都会接收有关
ReportGenerationDoneEvent
的信息,并检查是否有人在监听其令牌。 - GraphQL API(实例 2(看到客户端正在等待结果。通过网络套接字推送结果。
- GraphQL API(实例 3-100(忽略
ReportGenerationDoneEvent
。
它相当广泛,但是通过简单的抽象,您不必考虑所有这些复杂性,也不必为此路由的新进程跨多个服务编写~30行代码。
它的精彩之处在于,您最终会获得良好的水平缩放、事件可重放性(重试(、关注点分离(客户端、api、worker(,尽快将数据推送到客户端,并且正如您提到的,您不会在are we done yet?
请求上浪费带宽。
另一个很酷的事情是,每当用户在我们的面板中打开报告列表时,他都会看到当前生成的报告,并且可以订阅他们的更改,因此他们不必手动刷新列表。
对套接字集群的良好思考。它将优化上述场景中的步骤 10,但目前,我们没有看到将ReportGenerationDoneEvent
广播到整个 API 集群的任何性能问题。对于更多的实例或多区域架构,这将是必须的,因为它将允许更好的扩展和分片。
重要的是要了解 SocketCluster 在通信层 (WebSockets( 上运行,但逻辑 API 层 (GraphQL( 高于此层。要进行 GraphQL 订阅,您只需要使用允许您向用户推送信息的通信协议,而 WebSockets 允许这样做。
我认为使用 SocketCluster 是一个不错的设计选择,但请记住迭代实现。仅当您计划在任何单个时间点打开多个套接字时,才使用 SocketCluster。此外,应仅在必要时进行订阅,因为 WebSocket 是有状态的,需要管理和检测信号。
如果你对我上面使用的异步后端体系结构进一步感兴趣,请阅读 CQRS 和事件溯源模式。