微服务:没有计数器的并行计算

问题描述

背景

让我们想象一个分布式系统,在微服务架构上,并行处理队列消息。该系统有两种应用:AB

A一个 REST API 应用程序,用于获取处理请求。每个请求都被翻译成解决请求的子任务列表。因此,一旦 A 收到新请求,它将向 B 应用程序的队列中插入 N 条消息。 N 是一个动态值,它是每个输入请求的变化。 一旦处理完成(所有 N 个项目都处理成功),我们将通知客户(请求者)总结果。

可能的解决方

假设一个请求有 N 个子任务需要处理。我们的应用程序 A 将创建一个初始值为 N 的原子计数器。每次应用程序 B 处理队列中的消息时,它都会将计数器减 1 并查看其新值。

最后一次处理将在计数器上留下值“0”。一旦应用程序检测到值“0”,应用程序 B 就会通知客户处理结果。

问题

上述可能的解决方案有两个主要缺点:

  1. 你必须有一个原子计数器来同步所有的一般进度。没有这个,你就无法知道任务何时完成。
  2. 许多分布式队列系统(例如 Google PubSub)采用“至少一次”传递 (source) 的方法,这意味着同一消息可以从队列中多次出队。这最终会导致在处理完所有队列消息之前通知客户。

问题

当使用像 Google PubSub 这样的队列系统时,解决上述问题的正确方法是什么?

解决方法

应用程序 B 不能简单地将消息放在应用程序 task completion queue 使用的另一个 A 上,而不是使用原子计数器?以便只有应用程序 A 跟踪进度?

关于您的问题,您可以用来解决该问题的一种方法是在数据库表中跟踪已完成的作业。作业完成后(所有 N 个任务都已处理完毕),您将通知发送给客户端,但您也在数据库中标记作业已完成。

然后,如果您将作为已完成作业一部分的消息出列,则可以忽略它。

,

您可以使用 Cloud Pub/Sub ordering keys 来实现这种处理。然而,解决方案有点复杂,因为正如其他评论者指出的那样,队列可能不是这种任务协调的最佳抽象。 Cloud TasksDataflow 或通过某种形式的存储整合工作可能更适合此用例。

使用 Cloud Pub/Sub 执行此操作的一种方法是让 A 在单个区域中使用单个排序键发布其 N 消息,然后在相同的排序键。 Cloud Pub/Sub 将确保这些消息按顺序到达 BB 不需要维护它处理了多少消息的计数器;一旦它收到“工作完成”消息,它就知道它可以将处理结果通知客户。

这将确保 B 不会在所有子任务完成之前通知客户处理已完成。但是,请注意,由于 Cloud Pub/Sub 的至少一次消息传递保证,除非您在 B 中进行一些重复数据删除,否则 B 仍然可以多次通知客户相同的工作.

,

很高兴看到 Lauren 和 Cosmin 提供的不同观点。我认为他们已经提供了两种很好的方法,但需要制定更多细节。

为了详细说明正确的解决方案,该问题没有提供有关如何部署微服务以及高可用性要求是什么的信息。

我们设计的一个类似解决方案是使用持久存储来跟踪任务的进度(我们使用了数据库)。它将有助于解耦每个任务的处理,并提供将每个任务分配到部署了微服务 B 的集群中的不同节点的灵活性。使用持久存储来跟踪任务执行的状态(GCS Buckets 或 DB)将为处理失败提供弹性。如果一个节点在处理任务时宕机,另一个节点可以通过使用 Pubsub 重新交付来接收。

我们应该检查 DB 或 GCP 存储桶中每个任务的执行状态,以将最终通知发送给客户(当所有任务完成时)。