在分布式,事件源系统中维护一致性的模式?


12

我最近一直在阅读事件源,非常喜欢它背后的想法,但是仍然遇到以下问题。

假设您有N个并发进程,它们接收命令(例如Web服务器),结果生成事件并将它们存储在集中存储中。我们还假设通过依次应用来自存储的事件,所有瞬态应用程序状态都保留在各个进程的内存中。

现在,我们有以下业务规则:每个不同的用户必须具有唯一的用户名。

如果两个进程收到一个针对相同用户名X的用户注册命令,则它们都将检查X是否不在其用户名列表中,该规则将对这两个进程进行验证,并且它们都会在存储中存储“具有用户名X的新用户”事件。

现在,由于违反了业务规则(存在两个具有相同用户名的不同用户),我们进入了不一致的全局状态。

在传统的N服务器<-> 1 RDBMS样式系统中,数据库被用作同步的中心点,这有助于防止此类不一致。

我的问题是:事件源系统通常如何解决此问题?它们是否只是简单地按顺序处理每个命令(例如,将可写入存储的处理量限制为1)?


1
这样的限制是由代码控制的还是数据库约束?N个事件可能会或可能不会按顺序分派处理... N个事件可能会同时通过验证,而不会相互争论。如果订单很重要,则需要同步验证。或者使用队列使事件入队,请按顺序进行分派
-Laiv

@莱夫对。为简单起见,我假设没有数据库,所有状态都保留在内存中。通过队列顺序处理特定类型的命令将是一种选择,但感觉可能很复杂,因为它决定哪些命令可能对其他命令有因果关系,而我最终可能会将所有命令放在同一队列中,这相当于只有一个进程处理命令:/例如,如果我有一个在博客文章上添加评论的用户,则“删除用户”,“暂停用户”,“删除博客文章”,“禁用博客文章评论”等应全部放在同一队列中。
奥利维尔·拉隆德

1
我同意您的说法,使用队列或信号灯并不简单。不能使用并发或事件源模式。但是基本上所有解决方案都以系统协调事件的流量为最终。但是,这是一个有趣的范例。还有面向像元组这样的元组的外部缓存,可以帮助管理节点之间的流量,例如缓存实体的最后状态,或者是否正在处理该实体。在这种开发中,共享缓存非常普遍。它看起来似乎很复杂,但是却没有放弃;-)很有意思
Laiv

Answers:


6

在传统的N服务器<-> 1 RDBMS样式系统中,数据库被用作同步的中心点,这有助于防止此类不一致。

在事件源系统中,“事件存储”扮演相同的角色。对于事件源对象,您的写操作是将新事件追加到事件流的特定版本中。因此,与并发编程一样,您可以在处理命令时获得对该历史记录的锁定。事件源系统采取更乐观的方法更为常见-加载以前的历史记录,计算新的历史记录,然后进行比较和交换。如果其他命令也已写入该流,则比较和交换将失败。在这里,您要么重新运行命令,要么放弃命令,甚至将结果合并到历史记录中。

如果所有N个带有M命令的服务器都试图写入单个流,则争用将成为一个主要问题。通常的答案是为模型中的每个事件源实体分配一个历史记录。因此,User(Bob)与User(Alice)会有不同的历史记录,并且向一个写入操作不会阻止对另一个写入操作。

我的问题是:事件源系统通常如何解决此问题?他们是否只是按顺序处理每个命令?

格雷格·杨(Greg Young)关于布景验证

有没有一种优雅的方法来检查域对象属性上的唯一约束,而又不将业务逻辑移入服务层?

简短的回答,在许多情况下,对需求进行更深入的调查后发现,要么(a)它是对某些其他需求的理解不足的代理,要么(b)如果可以发现违反“规则”的情况,则可以接受(例外报告) ,在某个时间窗口内缓解或频率较低(例如:客户端可以在分派使用该名称的命令之前检查名称是否可用)。

在某些情况下,您的事件存储擅长设置验证(即关系数据库),那么您可以通过在持久化事件的同一事务中写入“唯一名称”表来实现要求。

在某些情况下,您只能通过将所有用户名发布到同一流中来强制执行该要求(这允许您评估内存中的一组名称,作为域模型的一部分)。-在这种情况下,两个进程将更新尝试更新“流”历史记录的尝试,但是比较和交换操作之一将失败,并且重试该命令将能够检测到冲突。


1)感谢您的建议和参考。当您说“比较并交换”时,是否表示该过程在存储事件时会检测到自从开始处理命令以来已经发生了新事件?我想这将需要一个支持“比较并交换”语义的事件存储,对吗?(例如,“仅在最后一个事件的ID为X时才编写此事件”)?
奥利维尔·拉隆德

2)我也喜欢接受临时不一致并最终修复它们的想法,但是我不确定如何以可靠的方式对此进行编码...也许有专门的过程可以顺序验证事件并在检测到事件时创建回滚事件出了些问题?谢谢!
奥利维尔·拉隆德

(1)我会说“历史的新版本”而不是“新事件”,但是您有主意;如果它是我们期望的,则仅替换历史记录。
VoiceOfUnreason

(2)是的。批量读取商店中的事件是一种逻辑,并且在批量结束时广播异常报告(“我们有太多名为Bob的用户”)或调度命令来弥补问题(假设正确的响应是无需人工干预即可计算)。
VoiceOfUnreason

2

听起来您可以为用户注册实现业务流程(saga在的上下文中Domain Driven Design),其中将用户视为CRDT

资源资源

  1. https://doc.akka.io/docs/akka/current/distributed-data.html http://archive.is/t0QIx

  2. “带有Akka分布式数据的CRDT” https://www.slideshare.net/markusjura/crdts-with-akka-distributed-data了解更多

    • CmRDTs-基于操作的CRDT
    • CvRDT基于状态的CRTD
  3. 在Scala代码示例 https://github.com/akka/akka-samples/tree/master/akka-sample-distributed-data-scala。也许“购物车”是最合适的。

  4. 游览Akka群集– Akka分布式数据 https://manuel.bernhardt.io/2018/01/03/tour-akka-cluster-akka-distributed-data/
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.