事件采购:同时创建冲突事件

时间:2017-05-12 18:59:48

标签: concurrency apache-kafka event-sourcing

我正在尝试使用Kafka实现事件采购系统,并遇到了以下问题。在新用户注册期间,我想检查用户提供的用户名是否已被占用。但是,请考虑2个用户同时尝试注册以提供相同用户名的情况。

在我理解ES如何工作时,处理注册请求的控制器将检查请求是否有效,然后它将向Kafka发送一个新事件(例如NewUser),最后该事件将由另一个控制器拾取,将其保存在物化视图中(例如Postgres DB)。问题是请求的验证是针对物化视图完成的,但实际的持久性发生在以后。因此,由于2个请求是并行处理的(由不同的服务实例),它们可能都会通过验证,从而产生2 NewUser条消息。但是,当第二个控制器尝试在数据库中保留这2条NewUser消息时,由于违反了用户名的唯一性约束,第二个事件将失败。

有关如何解决此问题的任何想法?

感谢。

更新

特别是,我想验证以下问题是否已被接受:

  1. 使用用户名作为userId(限制性)
  2. 将事件发送到按用户名分区的主题以及验证时 已完成将事件发送到另一个主题

2 个答案:

答案 0 :(得分:1)

  

在新用户注册期间,我想检查用户提供的用户名是否已被占用。

您可以在Set Validation上查看Greg Young的文章。

  

在我理解ES如何工作时,处理注册请求的控制器将检查请求是否有效,然后它将向Kafka发送一个新事件(例如NewUser),最后该事件将由另一个控制器将它保存在物化视图中(例如Postgres DB)。

这与通常的安排略有不同。 (您可能还想查看Greg在polyglot data上的演讲。)

假设我们从两个作家开始;这很好,但是如果有一个单一的真实点,那么你将需要在某处进行同步。

通常的安排是使用乐观并发的形式;处理请求时,保留原始状态的副本,然后进行计算,最后发送记录簿“replace(originalState,newState)'”。

所以在这一点上,我们有两篇写作对着记录的书

replace(red,green)
replace(red,blue)

在记录簿中,写入是按顺序处理的。

[...,replace(red,blue)...,replace(red,green)]

因此,当记录处理书replace(red,blue)时,它执行检查是,状态当前是红色,并且交换为蓝色。之后,当记录簿试图处理replace(red,green)时,记录簿会执行检查,但由于状态不再是红色而失败。

所以其中一个写入成功,另一个失败;后者可以将失败向外传播,或者重试,或......,恰恰取决于所讨论的具体机制。当然,重试应该意味着重新加载原始状态",此时模型会发现之前的某些编辑已经声明了用户名。

  

有关如何解决此问题的任何想法?

每个流的单个编写器通过消除模型的多个内存副本引入的歧义,使问题的其余部分变得非常简单。

使用同步写入耐用存储的多个编写器可能是最常见的设计。它需要一个事件存储,它能够理解写入流中特定位置的想法 - 又称"期望版本"。

您可以执行异步写入,然后开始执行其他工作,直到您收到写入成功的确认(或不成功,或直到您超时,或者......)....

没有魔力 - 如果你想要唯一性(或任何其他类型的不变执法,那么),那么每个人都需要就一个权威达成一致,任何想要提出改变的人都会赢得&如果没有从权威机构回复,就知道它是否被接受,并且需要为被拒绝的提案做好准备。

(注意:这不应该是一个惊喜 - 如果您使用的是传统设计,当前状态存储在RDBMS中,那么您的权限将是数据库中的用户表,其唯一性约束用户名列,并且竞争将在两个插入语句之间尝试首先完成其事务....)

答案 1 :(得分:1)

对于物化视图的初始验证在您有约束的大多数情况下都不够。总有一些相关的事件尚未实现。有两种主要的concurrency control方法可以确保生成正确的结果:

<强> 1。悲观的方法: 如果要在发布事件之前验证约束,则需要锁定相关资源(实体,聚合或数据集)。锁定意味着您的服务必须无法在这些资源上发布事件。在此之后,要获取数据的当前状态:

  • 您可以等到锁定前发布的所有事件都已实现。
  • 您可以从数据库中读取当前状态,并在单独的过程中对其应用事件。

<强> 2。乐观的方法: 在此方法中,您在发布事件后执行验证。为此,您需要实现反馈机制。使用事件和执行验证的过程应该能够发布验证结果。您可以在可能的情况下在内存中执行验证。否则,您可以依赖物化数据存储。

Martin Kleppman谈到针对完全相同的问题herehis book的两步解决方案。在此解决方案中,有两个主题:&#34;声明&#34;和#34;注册&#34;。首先,您发布声明以获取用户名,然后尝试将其写入数据库,最后将结果发布到注册主题。在概念层面,它遵循您提到的第二种方法中的相同步骤。在验证步骤中,它避免了实现验证逻辑并通过依赖数据库将二级索引保留在内存中。