事件溯源CQRS不必引入最终一致性 - jamesmh


事件溯源经常会被误解。这包括自动使用事件溯源意味着您必须在系统中各处引入最终一致性的想法。
多种技术可以处理最终一致性。但是,也有一些技术和模式可以完全消除最终的一致性!
在本文中,我想重点介绍一些使用事件溯源并在一致性和可用性方面实现灵活性的模式和方法。
换句话说——你不一定需要最终的一致性。
许多人认为事件溯源必须自动在各处引入最终一致性。这是错误的。

内联投影
内联投影是当您写入流并将任何生成的域事件推送到一个或多个投影时——所有这些都在同一个原子事务中。
假设您使用 PostgreSQL 来存储您的流和事件。这使您能够在同一事务中存储任何新事件和更新的读取模型。

当然,所有这些模式都有优点和缺点。
内联投影有一些权衡:

  • 您的写入事务将需要更长的时间来处理。这将增加您引入的更多内联投影。
  • 缩放内联投影意味着必须同时缩放写入和读取存储(因为它们是同一个数据库)。
  • 必须先完成重建投影,然后才能写入事件存储(与异步重建相比——它们可以在写入仍附加到事件存储时发生)。

考虑到这些权衡,应谨慎使用内联投影。
值得注意的是,预期高写入流量和/或高写入延迟/吞吐量的用例很重要,那么内联投影可能不是最佳选择。

Read Your Writes
内联投影的一大缺点是您必须使用事件存储来扩展读取模型。
如果你真的想要两全其美怎么办?也就是说,能够独立于事件存储但立即保持一致性来扩展读取模型的能力?
一种方法是“阅读你的文章”。
这种模式通常采用以下一般形式:

  1. 成功写入事件存储。
  2. 在接下来的 X 秒内,您的读取将使用与事件存储相同的 [系统]。
  3. X 秒后,所有读取切换到一组高可用的异步复制读取模型。

“[系统]”可以是数据库、数据中心、地理区域等。
出于这个原因,这种模式可以以多种不同的方式实现。让我们看一些例子。

复制的内联投影
在这种情况下,您使用内联投影来确保您的某些读取模型立即与您的事件存储一致。

但是,您还有一组异步镜像这些读取模型的副本:

混合模式,其中高度保证读取与写入存储一致,同时扩展读取模型以实现高可用性。

复制的本地异步读取模型
异步更新的读取模型需要考虑最终的一致性。读取模型可能需要一段时间才能赶上事件存储(某些系统的“一段时间”可以用毫秒来衡量)。

您可以使用“读取您的写入”模式并使用您的读取模型的专用实例,这些实例会在您写入后的前 X 秒内快速更新。

通常,这是通过使用位于同一网络或物理硬件上的写入和读取存储来完成的。

写入事件存储后立即读取将使用“本地”读取存储,它比用于高可用性的分布式副本具有更高的最新保证。
您甚至可以将分布式副本放置到不同的地理区域:

所有写入都转到一个主节点。在写入后的给定时间段后,客户端的流量将切换到许多全球分布的副本之一。
是的,我撒了谎。这种技术确实引入了最终的一致性……但它仍然可以在您需要高写入吞吐量的情况下提供帮助,例如!

跨地域复制本地读取模型
让我们采用与我们介绍的相同类型的“读取您的写入”技术,但在多个地理区域复制整个配置.

事件存储跨地理区域复制
在此配置中,地理区域将随着时间的推移彼此异步更新。但是,用户将与对他/她来说是“本地”的商店集群进行交互。

如果给定区域出现故障,则可以将用户的流量重新路由到另一个集群。

如果集群/区域出现故障,则可以将流量重新路由到另一个区域。
此外,每个集群都可能使用一种“读你的写”技术。其他读取模型可能使用内联或动态投影。这仍然使您可以自由选择每个本地集群的运行方式。

这种方法的权衡是:

  • 运营开销非常大——必须管理和配置所有这些基础设施。
  • 某些存储技术可能不支持各个区域的写节点同步。


结论
下次您考虑使用事件溯源时,请记住有一些方法可以减少甚至消除最终一致性。
与所有软件工程模式一样,不要盲目地在任何地方应用给定的模式。
您必须根据以下考虑因素做出决定:

  • 是否有任何特定的事件流需要高吞吐量?
  • 是否有任何特定的读取模型需要立即保持一致?
  • 是否有更宽松的保证可以使用的用例?
  • 我选择的存储技术是否允许事务性写入事件存储和读取模型?
  • 哪些读取模型需要具有高可用性?
  • 我的工程团队能否管理额外的运营成本?
  • 我选择的写入存储是否支持多主复制?
  • 我真的需要这种支持吗?