如何从事件驱动架构中错过的集成或通知事件中恢复?

How to recover from missed integration or notification events in event driven architecture?

情况如下。共有三种服务,一种服务是事件源的,使用事件总线(如 Azure 服务总线或 ActiveMQ)将集成或通知事件(发件箱模式)发布到其他两种服务(订阅者)。

此设计灵感来自 .NET microservices - Architecture e-book - Subscribing to events

我想知道如果这些事件之一由于错误而无法传递,或者事件处理只是没有正确实施,会发生什么。

Should I trust my message bus in case of an application error?

是的。

(编辑:阅读此回答后,请阅读@StuartLC 的回答了解更多信息)

您描述的系统是一个 eventually consistent 系统。它的工作原理是假设如果每个组件都完成其工作,所有组件最终都会收敛到一个一致的状态。

发件箱的工作是确保事件源微服务保存的任何事件持久可靠地传递到消息总线(通过事件发布者)。一旦发生这种情况,事件源和事件发布者就完成了——它们可以假设事件将最终 传递给所有订阅者。然后消息总线的工作就是确保发生这种情况。

消息总线及其订阅可以配置为“至少一次”或“最多一次”传递。 (请注意,“恰好一次”的传递通常是无法保证的,因此应用程序应该能够防止重复或丢失的消息,具体取决于订阅类型)。

“至少一次”(Azure 服务总线称为 "Peek Lock")订阅将保留该消息,直到订阅者确认它已被处理。如果订阅者给出确认,消息总线的工作就完成了。如果订阅者以错误代码响应或没有及时响应,消息总线可能会重试传递。如果多次传递失败,消息可能会被发送到毒消息或死信队列。无论哪种方式,消息总线都会保留消息,直到确认已收到消息。

On republishing events, should all messages be republished to all topics or would it be possible to only republish a subset?

我不能代表所有消息系统,但我希望消息总线仅重新发布到失败的订阅子集。无论如何,所有订阅者都应该准备好处理重复和无序的消息。

Should the service republishing events be able to access publisher and subscriber databases to know the message offset?

我不确定我是否理解“知道消息偏移量”的意思,但作为一般准则,微服务不应共享数据库。共享数据库模式是一种契约。合同一旦建立,就很难更改,除非您可以完全控制其所有消费者(包括他们的代码和部署)。通常最好通过应用程序 API 共享数据以提供更大的灵活性。

Or should the subscribing microservices be able to read the outbox?

消息总线的要点是将消息订阅者与消息发布者分离。让订阅者明确知道发布者会破坏该目的,并且随着发布者和订阅者数量的增长可能难以维护。相反,依靠专用监控服务 and/or 消息总线的监控功能来跟踪传递失败。

补充一下@xander 的出色回答,我相信您可能对事件总线使用了不合适的技术。您应该会发现 Azure Event Hubs or Apache Kafka 更适合事件发布/订阅架构。与旧的服务总线方法相比,专用事件总线技术的优势包括:

  • 每个事件消息只有一个副本(而 Azure 服务总线或 RabbitMQ 为每个订阅者制作每个消息的深度副本)
  • 消息在任何一个订阅者消费后都不会被删除。相反,消息会在定义的时间段内保留在主题上(在 Kafka 的情况下可以是不确定的)。
  • 每个订阅者(消费者组)将能够跟踪其提交的偏移量。这允许订阅者在丢失消息时重新连接和倒带,独立于发布者和其他订阅者(即隔离)。
  • 新消费者可以在消息发布后订阅,并且仍然能够接收所有可用消息(即倒回到可用事件的开始)

考虑到这一点,:

Should I trust my message bus in case of an application error?

是的,由于 xander 提供的原因。一旦发布者确认事件总线已接受事件,发布者的工作就完成了,不应再发送相同的事件。

挑剔,但由于您处于发布订阅架构中(即 0..N 订阅者),因此无论使用何种技术,您都应该将总线称为事件总线(而不是消息总线)。

Is this a usecase for dead letter queues?

死信队列通常是点对点队列或服务总线交付体系结构的产物,即其中有一条命令消息(事务性地)用于单个或可能有限数量的收件人。在发布-订阅事件总线拓扑中,发布者期望它监控所有订阅者的交付是不公平的。

相反,订户应承担弹性交付的责任。在 Azure 事件中心和 Apache Kafka 等技术中,每个消费者组的事件都是唯一编号的,因此可以通过监视消息偏移量提醒订阅者错过消息。

On republishing events, should all messages be republished to all topics or would it be possible to only republish a subset?

不,事件发布者永远不应重新发布事件,因为这会破坏到所有观察者订阅者的事件链。请记住,每个已发布事件可能有 N 个订阅者,其中一些订阅者可能在您的组织外部/您无法控制。事件应该被视为在某个时间点发生的 'facts'。事件发布者不应该关心事件有零个还是 100 个订阅者。如何解释事件消息由每个订阅者决定。

例如不同类型的订阅者可以对事件执行以下任一操作:

  • 仅记录事件以用于分析目的
  • 将事件转换为命令(或 Actor 模型消息)并作为特定于订阅者的事务处理
  • 将事件传递到规则引擎以推理更广泛的事件流,例如如果特定客户正在执行异常大量的交易,则触发反欺诈行动
  • 等等

因此您可以看到,为了一个古怪订阅者的利益而重新发布事件会破坏其他订阅者的数据流。

Should the service republishing events be able to access publisher and subscriber databases to know the message offset?

正如 xander 所说,系统和微服务不应该共享数据库。但是,系统可以公开 APIs(RESTful、gRPC 等)

事件总线本身应该跟踪哪个订阅者读取了哪个偏移量(即每个消费者组、每个主题和每个分区)。每个订阅者都将能够监控和更改其偏移量,例如以防事件丢失并需要重新处理。 (同样,一旦确认事件已被总线接收,生产者就永远不应重新发布该事件)

Or should the subscribing microservices be able to read the outbox?

事件驱动的企业架构至少有两种常用方法:

  • 'Minimal information' 事件,例如Customer Y has purchased Product Z。在这种情况下,许多订阅者会发现事件中包含的信息不足以完成下游工作流,并且需要丰富事件数据,通常通过调用接近发布者的 API 来检索他们需要的其余数据。这种方法具有安全优势(因为 API 可以验证对更多数据的请求),但会导致 API.
  • 上的高 I/O 负载
  • 'Deep graph' 事件,其中每个事件消息都包含任何订阅者应该希望需要的所有信息(这在未来证明是非常困难的!)。尽管事件消息的大小会变得臃肿,但它确实节省了大量触发 I/O,因为订阅者不需要从生产者那里执行进一步的充实。