很快——是的。我们已经做到了,而且效果很好。
诀窍是,当涉及到水平扩展时,您必须比 API 工作者应用程序更深入地思考。如果你想要推送架构,从一开始就需要异步。
为了实现它,我们使用了队列系统,即RabbitMQ。
想象一下这种生成报告的场景,这可能需要 10 分钟:
- 客户端通过 WebSocket 连接到我们的 GraphQL API(实例 1)
- 客户端通过 WebSocket 发送命令生成报告
- API 为命令生成令牌并将生成报告的命令放入 CommandQueue(在 RabbitMQ 中),将令牌返回给客户端。
- 客户端使用令牌订阅其命令结果的事件
- 一些后端 Worker 拿起命令并执行报告生成过程
- 在此期间 GraphQL API(实例 1)死亡
- 客户端自动重新连接到 GraphQL API(实例 2)
- 客户端使用之前获取的令牌续订订阅
- Worker 完成,EventsQueue (RabbitMQ) 上的结果
- 我们所有的 GraphQL 实例都会收到关于 的信息
ReportGenerationDoneEvent
并检查是否有人在监听它的令牌。
- GraphQL API(实例 2)看到客户端正在等待结果。通过 websockets 推送结果。
- GraphQL API(实例 3-100)忽略
ReportGenerationDoneEvent
.
它相当广泛,但通过简单的抽象,您不必考虑所有这些复杂性,并跨多个服务编写约 30 行代码,用于使用此路由的新流程。
它的精彩之处在于,您最终获得了很好的水平扩展、事件可重播性(重试)、关注点分离(客户端、api、工作人员),尽可能快地将数据推送到客户端,正如您提到的那样不要在are we done yet?
请求上浪费带宽。
另一个很酷的事情是,每当用户在我们的面板中打开报告列表时,他会看到当前正在生成的报告,并且可以订阅他们的更改,因此他们不必手动刷新列表。
对 SocketCluster 的好思考。它将优化上述场景中的第 10 步,但目前,我们没有看到将其广播ReportGenerationDoneEvent
到整个 API 集群的任何性能问题。对于更多实例或多区域架构,这将是必须的,因为它可以实现更好的扩展和分片。
重要的是要了解 SocketCluster 在通信层(WebSockets)上运行,但逻辑 API 层(GraphQL)在其之上。要进行 GraphQL 订阅,您只需使用允许您向用户推送信息的通信协议,而 WebSockets 允许这样做。
我认为使用 SocketCluster 是一个不错的设计选择,但请记住迭代实现。仅当您计划在任何单个时间点打开多个套接字时才使用 SocketCluster。此外,您应该仅在必要时订阅,因为 WebSocket 是有状态的并且需要管理和心跳。
如果您对我上面使用的异步后端架构进一步感兴趣,请阅读 CQRS 和事件溯源模式。