5

我正在尝试使用与我所看到的各种 Greg Young 启发示例一致的实践和原则来实施事件溯源系统。

我了解版本检查逻辑是如何工作的,并且在保存聚合时,如果当前版本与预期版本不匹配,则意味着另一个会话/客户端应用程序在您之前更新了聚合。

我也明白,当并发事件被保存时,您可以采用一种追溯解决冲突的方法,这个问题与这样做无关。

我想了解的是,在使用诸如 ravendb 之类的 nosql 数据库作为事件存储的特定实现中,我将如何确保写入的事件不会由于竞争条件而与版本号重叠。

以下代码来自一个示例项目来说明:

Repository<TAggregate>存储库类中有一个保存方法

    public void Save(AggregateRoot aggregate, int expectedVersion)
    {
        if (aggregate.GetUncommittedChanges().Any())
        {
            lock (_lockStorage)
            {
                var item = new T();

                if (expectedVersion != -1)
                {
//issue if two processes get to this line of code below together 
//and both have the same 'version to be expected' then start writing together
                    item = GetById(aggregate.Id); 
                    if (item.Version != expectedVersion)
                    {
                        throw new ConcurrencyException(string.Format("Aggregate {0} has been previously modified",
                                                                     item.Id));
                    }
                }

                _storage.Save(aggregate);
            }
        }
    }

现在,当只有一个应用程序时,这基本上可以正常工作。当当前线程获取锁,检查版本,然后写入自己的事件时,锁会停止任何其他线程向事件存储写入事件。

然而,想象两个独立的客户端在不同的机器上运行。显然,这两个进程都可以一起进入锁,然后它们都可以执行该GetById()方法并且都看到相同的当前提交的版本。然后,两个进程将继续写入具有递增版本号的未提交事件。但是,这会使聚合的事件流处于可能存在具有相同版本号的不同事件的状态。

我知道我可以做一些回顾性的决议,但这不是我想要完成的。

现在很明显,这意味着需要在事件存储数据库方面进行某种锁定。谁能建议如何做到这一点?答案不必是特定于数据库的,但 ravendb 示例会很棒,因为这就是我计划用它来原型化我的事件源系统。

4

2 回答 2

1

运行在不同机器上的两个独立客户端是否有任何原因无法通过 API 与应用程序交互?这样,事件的中央处理就在一台机器上处理,避免了您描述的问题。如果您指的是部分连接的场景,那么您仍然不会在客户端上执行此逻辑。如果不可避免,您将需要在某个时候组合事件流,然后处理冲突。

如果有帮助,我确实有一篇关于处理并发问题的帖子,但它不能处理您的确切情况,但可能会给您一些解决并发冲突的想法。

于 2014-11-18T15:35:44.687 回答
1

当底层数据库已经提供支持时,不要自己实现乐观并发控制。

通常,这是通过将您希望数据所在的版本传递给数据库服务器来实现的。只有当版本与当前版本匹配时,更新才会成功。

RavenDB 使用电子标签进行乐观并发控制:

于 2015-03-21T17:18:26.243 回答