如何重播丢失的事件?

How are the missing events replayed?

我正在尝试了解有关 CQRS 和事件溯源(事件存储)的更多信息。

我的理解是消息 queue/bus 通常不会在这种情况下使用 - 消息总线可用于促进微服务之间的通信,但它通常不会专门用于 CQRS。然而,我现在看到它的方式 - 消息总线将非常有用,可以保证读取模型最终同步,从而实现最终一致性,例如当托管读取模型数据库的服务器重新联机时。

我了解 CQRS 通常可以接受最终一致性。我的问题是;读取端如何知道它与写入端不同步?例如,假设通常一天在事件存储中创建了 2,000,000 个事件,并且还有 1,999,050 个事件写入了读取存储。剩下的 950 个事件没有被写入,因为某处存在软件错误,或者因为托管读取模型的服务器离线了几秒钟等。最终一致性如何在这里发挥作用?应用程序如何知道在一天结束时重播丢失的 950 个事件或十分钟前因停机而丢失的 x 个事件?

我在过去一周左右的时间里阅读了这里的问题,其中讨论了从事件存储中重播的消息,例如这一个:CQRS - Event replay for read side,但是 none 谈谈这是如何完成的。我是否需要设置每天运行一次的计划任务并重播自计划任务上次成功之日起创建的所有事件?有没有更优雅的方法?

Greg Young 的这篇演讲可能有所帮助。

How does the application know to replay the 950 events that are missing at the end of the day or the x events that were missed because of the downtime ten minutes ago?

所以这里有两种不同的方法。

一个可能比您预期的要简单——每次您需要重建读取模型时,只需从流中的事件 0 开始。

是的,这个规模最终会很糟糕,所以你不会希望它成为你的第一个策略。但请注意它确实有效。

对于具有不太令人尴尬的缩放属性的更新,通常的想法是读取模型跟踪有关用于构建先前模型的流位置的元数据。因此,来自读取模型的查询变为 "What has happened since event #1,999,050"?

event store 的情况下,调用可能类似于

EventStore.ReadStreamEventsForwardAsync(stream, 1999050, 100, false)

应用程序不知道由于错误它还没有处理一些事件。

首先,我不明白你为什么假设写入端写入的事件数必须等于读取端处理的事件数。一些预测可能会订阅相同的事件,而一些事件可能在读取端没有订阅。

如果投影/基础设施中的错误导致某个投影无效,您可能需要重建此投影。在大多数情况下,这将是手动干预,将投影的检查点重置为 0(时间的开始),因此投影将从事件存储中从头开始获取所有事件并再次重新处理所有事件。

事件存储应该有一个跨所有事件的全局序列号,比方说,从 1 开始。

每个投影都有一个位置跟踪,它沿着序列号。投影就像 逻辑 队列。

您可以清除投影的数据并将位置重置回 0,并且应该重建它。

在您的情况下,由于某种原因投影失败,例如服务器在位置 1,999,050 下线,但当服务器再次启动时,它将从此处继续。

我在我的项目中使用了两种方法,具体取决于要求:

  1. 同步,进程中 读取模型。在事件持久化之后,在相同的请求生命周期中,在相同的进程中,Readmodels 被提供这些事件。如果 Readmodel 失败(bug 或可捕获 error/exception),错误将被记录下来,Readmodel 将被跳过,下一个 Readmodel 将被提供事件等。然后跟随 Sagas,这可能会生成生成更多事件的命令,并重复循环。

当业务可以接受 Readmodel 故障的影响时,当 Readmodel 数据的就绪性比故障风险更重要时,我会使用这种方法。例如,他们希望 UI.

中的数据立即可用

错误日志应该可以在某些管理面板上轻松访问,以便有人查看它以防客户端报告 write/commands 和 read/query 之间不一致。

如果您的 Readmodels 相互耦合,这也适用,即一个 Readmodel 需要来自另一个规范 Readmodel 的数据。虽然这看起来很糟糕,但事实并非如此,这总是取决于情况。在某些情况下,您用弹性来交换更新程序 code/logic 重复。

  1. 异步,在另一个进程中 读取模型更新程序。当我使用 Readmodel 与其他 Readmodels 的完全分离时,当 Readmodel 的失败不会导致整个读取端关闭时,就会使用它;或者当 Readmodel 需要另一种语言时,不同于单体。基本上这是一个微服务。当 Readmodel 内部发生错误时,有必要通知一些权威的更高级别的组件,即通过电子邮件或 SMS 或其他方式通知管理员。

Readmodel 还应该有一个状态面板,其中包含关于它已处理的事件的各种指标,是否存在间隙,是否存在错误或警告;它还应该有一个命令面板,管理员可以在其中随时重建它,最好不要系统停机。

在任何方法中,读取模型都应该易于重建。

How would you choose between a pull approach and a push approach? Would you use a message queue with a push (events)

我更喜欢基于拉动的方法,因为:

  • 它不使用另一个有状态的组件,如消息队列,另一个必须管理的东西,消耗资源并且可能(所以它会)失败
  • 每个 Readmodel 都以它想要的速率消耗事件
  • 每个 Readmodel 都可以随时轻松更改它消耗的事件类型
  • 通过从头开始请求所有事件,可以随时轻松地重建每个 Readmodel
  • 事件的顺序与真相的来源完全相同,因为你从真相的来源中拉出来[=44​​=]

有些情况我会选择消息队列:

  • 即使事件存储不可用,您也需要事件可用
  • 你需要competitive/paralel个消费者
  • 您不想跟踪您消费的消息;当它们被消耗时,它们会自动从队列中移除