使用 WebSocket

本页面介绍有关在 Cloud Run 上运行 WebSocket 或其他流式传输服务的指南以及编写此类服务的客户端和最佳做法。

Cloud Run 支持 WebSocket 应用,无需进行其他配置。但是,WebSocket 流为仍受为 Cloud Run 服务配置的请求超时限制的 HTTP 请求,因此您需要确保此设置适合您使用 WebSocket,例如在您的客户端中实现重新连接

即使您在 Cloud Run 上使用会话亲和性(提供尽力而为亲和性),WebSocket 请求仍然可能因不同的内置均衡而最终出现在不同的实例上。如需解决此问题,您需要在实例之间同步数据

请注意,如果您使用的是 Cloud Load Balancing,则也支持 Cloud Run 上的 WebSocket。

部署示例 WebSocket 服务

使用 Cloud Shell 快速部署搭配使用 WebSocket 和 Cloud Run 的示例白板服务:部署示例

或者,如果要手动部署该示例白板服务,请按以下步骤操作:

  1. 使用 git 命令行工具在本地克隆 Socket.IO 代码库:

    git clone https://github.com/socketio/socket.io.git
    
  2. 导航到示例目录:

    cd socket.io/examples/whiteboard/
    
  3. 使用 Google Cloud CLI 从源代码构建服务,以部署新的 Cloud Run 服务:

    gcloud run deploy whiteboard --allow-unauthenticated --source=.
    
  4. 部署服务后,打开两个不同的浏览器标签页,然后导航到服务网址。因为,通过 WebSocket 连接到同一实例后,您在一个标签页中绘制的任何元素都应传播到另一个标签页(反之亦然)。

WebSocket 聊天示例完整教程

如果您想查看完整的代码演示,请参阅教程:为 Cloud Run 构建 WebSocket 聊天服务主题中的更多代码示例。

最佳做法

在 Cloud Run 上创建 WebSocket 服务最难的部分是在多个 Cloud Run 实例之间同步数据,这是因为实例具有自动扩缩和无状态性质,并且存在并发请求超时的相关限制。

处理请求超时和客户端重新连接

WebSocket 请求在 Cloud Run 中被视为长时间运行的 HTTP 请求。它们受请求超时(目前最长为 60 分钟,默认为 5 分钟)的限制,即使您的应用服务器未强制执行任何超时。

如果客户端保持连接打开状态的时间长于为 Cloud Run 服务配置的超时值,客户端会在请求超时时断开连接。

因此,如果请求超时或服务器断开连接,连接到 Cloud Run 的 WebSocket 客户端应重新连接到服务器。您可以通过使用诸如 reconnecting-websocket 的库或处理“断开连接”事件(如果使用 SocketIO 库),在基于浏览器的客户端中实现此目的。

使用 WebSocket 时产生的费用

任何具有打开的 WebSocket 连接的 Cloud Run 实例都被视为活跃,因此 CPU 分配并计费

最大限度地提高并发性

WebSocket 服务通常设计成同时处理多个连接。由于 Cloud Run 支持并发连接(最多每个容器 1000 个连接),如果服务能够处理给定资源的负载,则 Google 建议您将容器的最大并发设置值增加到大于默认值的值。

关于粘性会话(会话亲和性)

WebSocket 连接是有状态的,因此客户端会在连接期间与 Cloud Run 上的同一容器保持连接。这样可以在一个 WebSocket 连接中自然地实现会话粘性。

对于多个和后续 WebSocket 连接,您可以将 Cloud Run 服务配置为使用会话亲和性,但这会提供“尽力而为”亲和性,因此 WebSocket 请求仍可能会最终出现在不同的实例上。连接到 Cloud Run 服务的客户端最终可能由不协调或共享数据的不同实例提供服务。

为了缓解此问题,您需要使用外部数据存储来同步 Cloud Run 实例之间的状态,下一部分会对此进行介绍。

在实例之间同步数据

您需要同步数据,以确保连接到 Cloud Run 服务的客户端从 WebSocket 连接接收相同的数据。

例如,假设您使用 WebSocket 构建聊天室服务并将最大并发设置为 1000,如果同时有超过 1000 个用户连接到此服务时,他们将由不同的实例提供服务,而且他们因此将无法在聊天室中看到相同的消息。

如需在您的 Cloud Run 实例之间同步数据,例如接收所有实例上发布到聊天室的消息,您需要一个外部数据存储系统,例如数据库或消息队列。

如果您使用 Cloud SQL 等外部数据库,则可以向数据库发送消息,并定期从数据库进行轮询。但请注意,当容器不处理任何请求时,Cloud Run 实例没有 CPU。如果您的服务主要处理 WebSocket 请求,只要至少有一个客户端连接,容器就会分配 CPU。

消息队列可以更好地在 Cloud Run 容器之间实时同步数据,因为外部消息队列无法寻址每个实例以“推送”数据。您的服务需要与消息队列建立连接,以从消息队列“拉取”新消息。

Google 建议您使用外部消息队列系统,例如 Redis Pub/Sub (Memorystore) 或 Firestore 实时更新,以通过容器实例启动的连接向所有实例提供更新。

使用 Redis Pub/Sub

WebSocket 聊天室服务架构

您可以通过从 Memorystore 创建 Redis 实例来使用 Redis Pub/Sub 机制。如果您使用 WebSocket 的 Socket.IO 库,则可以使用其 redis 适配器

在这种基于 Redis 的架构中,每个 Cloud Run 实例都会建立与 Redis 通道的长期连接,后者包含收到的消息(使用 SUBSCRIBE 命令)。容器实例在该渠道上收到新消息后,可以通过 WebSocket 实时将其发送至客户端。

同样地,当客户端使用 WebSocket 发出消息时,接收消息的实例会将消息发布到 Redis 通道(使用 PUBLISH 命令),订阅此通道的其他实例将收到此消息。

如果您想查看完整的代码演示,请参阅教程:为 Cloud Run 构建 WebSocket 聊天服务主题中的更多代码示例。