事件存储和乐观并发

时间:2018-09-13 08:17:51

标签: cqrs optimistic-concurrency event-store

Greg Young在"Building an event storage"部分中有关CQRS的文档中,当将事件写入事件存储时,他检查了乐观并发性。我真的不明白他为什么做这张支票,任何人都可以用一个具体的例子向我解释。

谢谢。

2 个答案:

答案 0 :(得分:3)

TLDR;需要进行并发检查,因为发出什么事件取决于先前的事件。因此,如果其他进程同时发出其他事件,则必须重新做出决定。

事件存储的使用方式如下:

  1. 旧事件是从Eventstream(= Eventstore中的分区,其中包含由Aggregate实例生成的所有事件)中加载的
  2. 旧事件由拥有它们的集合按其生成顺序进行处理/应用
  3. 聚合基于这些事件建立的内部状态,决定发出一些新事件
  4. 这些新事件将附加到事件流中

因此,步骤3取决于执行此命令之前产生的先前事件。

如果将由另一个进程并行生成的某些事件附加到同一Eventstream中,则意味着所做出的决定是基于错误的前提,因此必须从步骤1开始重复进行。

答案 1 :(得分:2)

  

我真的不明白他为什么做这张支票,任何人都可以用具体的例子向我解释。

从某种意义上说,事件存储应该是持久性的,因为一旦您编写了一个事件,该事件对于以后的每次读取都是可见的。因此,数据库中的每个动作都应该是一个追加。一个有用的心理模型是考虑一个单链表。

如果数据库要支持不止一个具有写访问权的执行线程,那么您将面临“丢失更新”问题。绘制为链接列表,看起来可能像这样:

Thread(1) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(2) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(1) set(/x, [ ... <- 69726c3e <- 709726c3 <- /x.tail])
Thread(2) set(/x, [ ... <- 69726c3e <- 83b97195 <- /x.tail])

线程(2)编写的历史记录不包括线程(1)记录的event:709726c3。因此“丢失更新”。

在通用数据库中,通常使用事务来管理此事务:幕后的某些魔术可以跟踪所有数据依赖关系,并且如果在尝试提交事务时不满足前提条件,则所有工作都是拒绝了。

但是事件存储不需要使用支持一般情况的所有自由度-禁止对数据库中存储的事件进行编辑,更改事件之间的依存关系也是如此。

更改的唯一可变部分-这是唯一替换新值覆盖旧值的地方-是更改/x.tail

Thread(1) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(2) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(1) set(/x, [ ... <- 69726c3e <- 709726c3 <- /x.tail])
Thread(2) set(/x, [ ... <- 69726c3e <- 83b97195 <- /x.tail])

这里的问题很简单,就是线程(2)认为6 <- /x.tail是正确的,并用丢失事件7的值替换了它。如果我们将写从set更改为{{1 }} ...

compare-and-set

然后数据存储区可以检测到冲突并拒绝无效写入。

当然,如果数据存储以不同的顺序看到线程的动作,则 失败的命令可能会改变

Thread(1) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(2) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(1) compare-and-set(/x, 69726c3e <- /x.tail, [ ... <- 69726c3e <- 709726c3 <- /x.tail])
Thread(2) compare-and-set(/x, 69726c3e <- /x.tail, [ ... <- 69726c3e <- 83b97195 <- /x.tail]) // FAILS

更简单地说,Thread(1) [... <- 69726c3e <- /x.tail] = get(/x) Thread(2) [... <- 69726c3e <- /x.tail] = get(/x) Thread(2) compare-and-set(/x, 69726c3e <- /x.tail, [ ... <- 69726c3e <- 83b97195 <- /x.tail]) Thread(1) compare-and-set(/x, 69726c3e <- /x.tail, [ ... <- 69726c3e <- 709726c3 <- /x.tail]) // FAILS 给予我们“最后一位作家获胜”的语义,set给予我们“第一位作家获胜”的语义,从而消除了对更新丢失的担忧。