我正在使用Pubsub(push)和Cloud Run,在那里我将部署一个使用Spring Boot构建的Java应用程序。
我有两个箱子。假设我有Service A
在Cloud Run中运行,有10个容器/实例,因为负载很高。我想:
- 推送消息(来自Cloud Function)到所有(广播)
Service A
的容器 - 将消息(来自Cloud Function)推送到
Service A
的单个任意容器
背景:我的云运行服务将使用服务器发送的事件将数据直接推送到客户端/浏览器。这当然意味着容器/实例将保持一种状态。在某些情况下,我需要将消息推送到所有容器上的所有sse/ws连接(想象一个带有公共聊天室的聊天应用程序,在那里每个人都可以看到发布的消息)。由于Cloud Run中的容器无法相互了解或看到(我想),我认为解决这个问题的正确方法是使用pubsub。
如果有更适合这种情况的工具,请告诉我正确的方向。
您只能推送到服务端点(URL),而不能推送到该服务的单个实例。
每个云运行服务只有一个容器。您可以通过每个实例的最大请求数(并发)来控制创建的实例数。
云运行实例是根据流量动态创建和销毁的。Pub/Sub是一种基于订阅的服务。每个订阅者都会收到一份消息副本。您在某个时间点看到同一消息的X个副本,在另一个时间点则看到Y个副本。这违反了Pub/Sub消息传递模型。
如果你想使用pub-sub来实现这一点,那么你必须为每个云运行实例创建一个订阅者,你可以在程序加载/启动时这样做,但当程序退出时,你必须垃圾收集相同的订阅者,或者需要有一个定期清理有很多未确认消息的订阅者的作业。
我建议你调查一下https://docs.nats.io/nats-concepts/subjects,这正是你想做的。
此外,由于您已经提到这将是一个有状态的服务,因此如果您使用应用程序引擎比云运行更好,因为云运行实例在处理活动连接之前仅处于活动状态。如果任何连接由于某种原因而中断,那么您可能会根据容器的状态释放容器。
云运行实例是独立的,正如您所说,它们不能相互查看和了解。此外,Cloud Run合约是无状态的,所以不能在推送消息中有状态并更新它。
实例可以是活动的和非活动的(处理或不处理请求),如果您当前有10个活动实例,可能会提前(启动)提供20或30个实例来吸收流量增加(如果发生)。
所有这些都表明你的设计是错误的。您不需要依赖Cloud Run实例上的状态,也不需要考虑通过推送来更新它。
例如,您需要将状态存储在Memorystore或firestore的外部,并在每次请求时获取数据。
Cloud Run支持接受WebSocket连接。虽然这些连接不是永久的长期连接(GA中有15分钟的超时,beta中有60分钟的超时),但只要给定容器中至少有一个WebSocket连接处于活动状态,它们就可以阻止谷歌终止容器实例。每个容器最多可以有250个WebSocket连接(或者在任何给定时间通常有250个HTTP连接)。
这意味着,您可以让Java应用程序在启动后立即订阅Google Pubsub的主题,并等待Pubsub消息,然后这些消息将中继到连接到特定Cloud Run实例的任何(或所有)WebSocket客户端。
Google Cloud Pubsub支持一对多订阅模式,因此您可以将发布到Pubsub主题的一条消息发布给所有订阅者,在这种情况下,这些订阅者将是具有活动WebSocket连接的每个单独的Google Cloud Run容器实例。
- Java应用程序启动时将连接到Pubsub-Topic
- Java应用程序将接受WebSocket连接
- Java应用程序将根据消息体中的内容和您的过滤逻辑,将来自Pubsub订阅的消息中继到相应的客户端
所以你的设计在Google Cloud Run上是可行的(现在支持WebSocket)&谷歌云发布。我确实有一些顾虑,所以我把它们放在这里。
我首先担心的是谷歌对谷歌云运行强加的15分钟(测试版为60分钟)HTTP超时,这意味着在该时间阈值之后,你的客户端的websocket连接将被丢弃,你需要处理重新连接。在重新连接的过程中,一些消息可能会丢失,因此很难实现100%有保证的消息传递。
我的第二个担忧(您可能会在未来担心)是,由于Pubsub的一对多扇出架构的性质,发布到Pubsub主题的单个消息将中继到所有订阅者,这意味着所有Cloud Run容器实例都将收到该消息。如果该消息只为一个WebSocket在多个容器中的一个容器中传递,这可能会浪费cpu/网络资源(成本),并且当有多个Cloud Run容器同时运行并且消息量很大时,这个问题只会变得更大。当然,您可以为每个容器或每个"容器"创建一个主题;聊天室";但它会增加复杂性,我相信谷歌对你可以拥有的主题数量以及TPS对管理操作的限制都有一些限制。
您可能还想看看Redis Pubsub,它允许您订阅特定的主题(并且没有主题创建/销毁开销)。从技术上讲,你可以为每个用户创建一个主题;聊天室";,并让您的Java应用程序根据连接的WebSockets的兴趣订阅该主题。这可能会解决我上面提到的第二个问题,因为每个容器实例只会接收到与它们相关的消息。。。但这种方法的代价是Redis实例可能会成为一个瓶颈。