事件溯源:何时(何时不)应该使用消息队列?

14
我正在使用Java和Cassandra从头开始构建一个项目,采用事件溯源的方式。
我的应用程序将基于微服务架构,并且在某些使用情况下,信息将以异步方式进行处理。我想知道在这种环境下,消息队列(如RabbitMQ、ActiveMQ Artemis、Kafka等)将在技术栈中扮演什么角色,以及如果我不使用它,我是否理解这些场景。
3个回答

33

我会建议将消息基础设施(如RabbitMQ)与事件流/存储/处理(如Kafka)分开。这些是为两个(或更多)不同目的而设计的两个不同的东西。

关于事件溯源,你必须有一个地方来存储事件。这个存储必须是追加式的,并支持基于标识的非结构化数据的快速读取。这种持久性的一个例子是EventStore

事件溯源与CQRS一起使用,这意味着你必须将你的更改(事件)投影到另一个存储中,以便进行查询。这是通过将事件投影到该存储中完成的,这是事件被处理以更改域对象状态的地方。重要的是要理解,使用消息基础设施进行投影通常是一个坏主意。这是由于消息的本质和两阶段提交问题。

如果你看一下事件的持久化方式,你会发现它们作为一个事务保存到存储中。如果你需要发布事件,那么这将是另一个事务。由于你正在处理两个不同的基础设施部分,事情可能会出错。

消息传递的问题在于通常保证消息“至少传递一次”,但消息的顺序通常不被保证。而且,当你的消息消费者失败并NACK了消息时,它将被重新传递,但通常会晚一些,从而再次破坏顺序。

然而,对于事件流服务器(如Kafka),排序和重复问题并不适用。此外,如果您使用catch-up订阅,EventStore将保证有序地交付事件一次。

根据我的经验,消息用于发送命令并实现事件驱动架构以以反应式方式连接独立服务。另一方面,事件存储库用于持久化事件,并且只有到达那里的事件才会被投影到查询存储库并发布到消息总线。


谢谢Alexey,我有一个快速问题:为什么“排序和重复问题在事件流服务器(如Kafka)中不适用”?如果您有并发订阅者,则不可避免地会出现顺序错误,如果这些并发订阅者是相互依赖的,则应根据特定应用程序的业务规则在它们之间实施一些自定义同步。我的理解正确吗? - IlliakaillI
我个人对Kafka没有经验,但我观看了Martin Kleppmann在DDDU 2016(https://dddeurope.com/2016/martin-kleppmann.html)上的演讲,他解释了当设计Kafka时他们的目标是严格的事件顺序。我还知道,当使用catch/up订阅时,EventStore保证事件顺序。但是,当在事件流上设置竞争性消费者时,这个保证必然会被打破,你无法保证顺序。异步处理也会打破顺序。 - Alexey Zimarev
我明白了。我问这个问题是因为在任何高可用系统中,你必须有竞争消费者来实现容错,而看起来没有机制可以自动处理这种系统的乱序情况。 - IlliakaillI
@IlliakaillI 是的,我相信在竞争消费者通用排序方面,在理论上是不可能的,必须通过系统使用传入消息和进程内通信上的某些属性来处理。 - Alexey Zimarev

7
请确保您清楚发送(send)命令和发布(publish)事件之间的区别。Udi Dahan在他关于总线和代理的文章中涉及了这个主题。
在大多数情况下,当您进行事件溯源时,您不希望从已发布的事件中重构状态。如果您需要状态,则从技术权威/记录簿查询历史记录,并从历史记录中重构状态。
另一方面,基于消息队列的事件驱动活动应该是可以的。当单个事件(加上订阅者的状态)拥有您需要的一切时,那么通过总线运行就可以了。
在某些情况下,您可能会同时执行两种操作。例如,如果您要更新缓存视图,则会订阅各种BobChanged事件以了解何时您的缓存数据已过期;为重建过期的视图,您将重新加载历史记录的表示并将其转换为更新后的视图。

6
在事件溯源应用程序的世界中,消息队列通常允许您在生产者和消费者之间实现发布-订阅模式的通信。此外,它们通常可以帮助您提供交付保证:哪些消息已传递给哪些订阅者,哪些消息没有传递。
但是它们不会无限期地存储所有消息。您需要拥有一个事件存储库来进行任何类型的事件溯源。
问题不是“排队还是不排队”,而更像是:
- 这个东西能否无限期地存储大量事件? - 它是否具有发布-订阅功能? - 它是否提供至少一次交付保证?
因此,您应该使用类似于KafkaEventStore这样的东西,以便获得所有开箱即用的功能。或者,您可以手动将事件存储库与消息队列结合使用,但这将涉及更多工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接