如何从事件驱动架构中错过的集成或通知事件中恢复?
c#
情况如下。共有三种服务,一种服务是事件源,并使用事件总线(如 Azure 服务总线或 ActiveMQ)向其他两种服务(订阅者)发布集成或通知事件(发件箱模式)。
此设计的灵感来自.NET 微服务 - 架构电子书 - 订阅事件。
我想知道如果这些事件之一由于错误而无法传递,或者事件处理只是没有正确实现,会发生什么。
- 如果出现应用程序错误,我应该信任我的消息总线吗?
- 这是死信队列的用例吗?
- 在重新发布事件时,是否应该将所有消息重新发布到所有主题,还是只能重新发布一个子集?
- 服务重新发布事件是否应该能够访问发布者和订阅者数据库以了解消息偏移量?
- 或者订阅微服务是否应该能够读取发件箱?
回答
如果出现应用程序错误,我应该信任我的消息总线吗?
是的。
(编辑:阅读此答案后,请阅读@StuartLC 的答案以获取更多信息)
你描述的系统是一个最终一致的系统。它的工作假设是,如果每个组件都完成其工作,则所有组件最终都会收敛到一致的状态。
发件箱的工作是确保事件源微服务持久化的任何事件都持久可靠地传递到消息总线(通过事件发布者)。一旦发生这种情况,事件源和事件发布者就完成了——他们可以假设事件最终将被传递给所有订阅者。然后消息总线的工作就是确保这种情况发生。
消息总线及其订阅可以配置为“至少一次”或“最多一次”传递。(请注意,“恰好一次”交付通常是无法保证的,因此应用程序应该能够抵御重复或丢失的消息,具体取决于订阅类型)。
“至少一次”(Azure 服务总线称为“Peek Lock”)订阅将保留该消息,直到订阅者确认它已被处理。如果订阅者给出确认,消息总线的工作就完成了。如果订阅者以错误代码响应或没有及时响应,消息总线可能会重试传递。如果多次传递失败,消息可能会被发送到有害消息或死信队列。无论哪种方式,消息总线都会保留消息,直到它确认收到消息为止。
在重新发布事件时,是否应该将所有消息重新发布到所有主题,还是只能重新发布一个子集?
我不能代表所有消息传递系统,但我希望消息总线只重新发布到失败的订阅子集。无论如何,所有订阅者都应该准备好处理重复和无序的消息。
服务重新发布事件是否应该能够访问发布者和订阅者数据库以了解消息偏移量?
我不确定我是否理解“知道消息偏移量”的意思,但作为一般准则,微服务不应该共享数据库。共享数据库模式是一种契约。合同一旦建立,就很难更改,除非您完全控制其所有使用者(包括他们的代码和部署)。通常最好通过应用程序 API 共享数据以提供更大的灵活性。
或者订阅微服务是否应该能够读取发件箱?
消息总线的重点是将消息订阅者与消息发布者解耦。让订阅者明确知道发布者违背了这个目的,并且随着发布者和订阅者数量的增长可能难以维持。相反,依靠专用的监控服务和/或消息总线的监控功能来跟踪传递失败。
回答
只是为了补充@xander 的出色回答,我相信您可能对您的事件总线使用了不合适的技术。您应该会发现Azure 事件中心或Apache Kafka更适合事件发布/订阅架构。与旧的服务总线方法相比,专用事件总线技术的优势包括:
- 每个事件消息只有一个副本(而 Azure 服务总线或 RabbitMQ 为每个订阅者制作每个消息的深层副本)
- 任何一位订阅者消费后都不会删除消息。相反,消息会在特定的时间段内保留在主题上(在 Kafka 的情况下可以是不确定的)。
- 每个订阅者(消费者组)将能够跟踪其提交的偏移量。这允许订阅者在丢失消息时重新连接和回滚,独立于发布者和其他订阅者(即隔离)。
- 新消费者可以在消息发布后订阅,并且仍然可以接收所有可用消息(即倒回可用事件的开始)
考虑到这一点,:
如果出现应用程序错误,我应该信任我的消息总线吗?
是的,因为 xander 提供的原因。一旦发布者确认事件总线已接受该事件,发布者的工作就完成了,并且不应再次发送相同的事件。
挑剔,但由于您处于发布订阅架构(即 0..N 个订阅者)中,因此无论使用何种技术,您都应该将总线称为事件总线(而不是消息总线)。
这是死信队列的用例吗?
死信队列通常是点对点队列或服务总线交付体系结构的产物,即其中有一条命令消息(事务性地)用于单个或可能有限数量的接收者。在发布-订阅事件总线拓扑中,期望发布者监控所有订阅者的交付对发布者来说是不公平的。
相反,订户应承担弹性交付的责任。在 Azure 事件中心和 Apache Kafka 等技术中,每个消费者组的事件都是唯一编号的,因此订阅者可以通过监视消息偏移量来收到丢失消息的警报。
在重新发布事件时,是否应该将所有消息重新发布到所有主题,还是只能重新发布一个子集?
不,事件发布者永远不应该重新发布事件,因为这会破坏所有观察者订阅者的事件链。请记住,每个发布的事件可能有 N 个订阅者,其中一些订阅者可能在您的组织之外/在您的控制之外。事件应被视为在某个时间点发生的“事实”。事件发布者不应该关心事件的订阅者是 0 还是 100。由每个订阅者决定如何解释事件消息。
例如,不同类型的订阅者可以对事件执行以下任何操作:
- 只需记录事件以进行分析
- 将事件转换为命令(或 Actor 模型消息)并作为特定于订阅者的事务处理
- 将事件传递到规则引擎以对更广泛的事件流进行推理,例如,如果特定客户正在执行异常大量的交易,则触发反欺诈操作
- 等等。
因此,您可以看到,为了一个 flakey 订阅者的利益而重新发布事件会破坏其他订阅者的数据流。
服务重新发布事件是否应该能够访问发布者和订阅者数据库以了解消息偏移量?
正如 xander 所说,系统和微服务不应该共享数据库。但是,系统可以公开 API(RESTful、gRPC 等)
事件总线本身应该跟踪哪个订阅者读取了哪个偏移量(即每个消费者组、每个主题和每个分区)。每个订户将能够监视和更改其偏移量,例如在事件丢失并需要重新处理的情况下。(同样,一旦确认事件已被总线接收,生产者就不应该重新发布事件)
或者订阅微服务是否应该能够读取发件箱?
事件驱动的企业架构至少有两种常见的方法:
- “最少信息”事件,例如
Customer Y has purchased Product Z。在这种情况下,许多订阅者会发现事件中包含的信息不足以完成下游工作流,并且需要丰富事件数据,通常是通过调用靠近发布者的 API,以便检索其余数据他们需要。这种方法具有安全优势(因为 API 可以验证对更多数据的请求),但会导致 API 上的高 I/O 负载。 - “深度图”事件,其中每个事件消息都包含任何订阅者应该希望需要的所有信息(这在未来证明非常困难!)。尽管事件消息的大小会膨胀,但它确实节省了大量触发 I/O,因为订阅者不需要从生产者那里执行进一步的扩充。