事件源:并发创建冲突事件

2q5ifsrm  于 2021-06-08  发布在  Kafka
关注(0)|答案(2)|浏览(255)

我正在尝试使用kafka实现一个事件源系统,遇到了以下问题。在新用户注册期间,我想检查用户提供的用户名是否已被使用。但是,请考虑这样一种情况,即两个用户试图同时注册并提供相同的用户名。
根据我对es工作原理的理解,处理注册请求的控制器将检查请求是否有效,然后它将发送一个新事件(例如。 NewUser )到kafka,最后该事件将由另一个控制器拾取,该控制器将它持久化在一个物化视图中(例如postgres db)。问题是,请求的验证是针对物化视图进行的,但对它的实际持久性发生在稍后。因此,因为这两个请求是并行处理的(由不同的服务示例处理),所以它们可能都通过了验证,结果是 NewUser 信息。但是,当第二个控制器尝试持久化这两个 NewUser 数据库中保存第二个事件的消息将失败,因为违反了用户名的唯一性约束。
有什么办法解决这个问题吗?
谢谢。

更新:

特别是,我想核实以下是否是解决这个问题的公认方法:
使用用户名作为用户ID(限制性)
将事件发送到按用户名分区的主题,并在验证完成后将事件发送到另一个主题

pobjuy32

pobjuy321#

在新用户注册期间,我想检查用户提供的用户名是否已被使用。
你可能想回顾一下格雷格·杨关于场景验证的文章。
根据我对es工作原理的理解,处理注册请求的控制器将检查请求是否有效,然后它将向kafka发送一个新事件(例如newuser),最后该事件将由另一个控制器拾取,该控制器将其保存在物化视图(例如postgres db)中。
这和通常的安排有点不同(你也可以回顾一下格雷格关于多语言数据的演讲。)
假设我们从两个作家开始;这很好,但是如果有一点是真的,那么你需要在某个地方进行同步。
通常的安排是使用一种乐观并发的形式;在处理请求时,先保留原始状态的副本,然后进行计算,最后将记录簿发送到“replace(originalstate,newstate)”。
因此,在这一点上,我们有两个写比赛的记录

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

在记录簿上,写的东西是按顺序处理的。

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

所以当记录的过程 replace(red,blue) ,它将执行一个检查,以确保是,状态当前为红色,并以蓝色交换。后来,当记录簿试图处理 replace(red,green) ,记录簿执行检查,检查失败,因为状态不再为红色。
所以其中一个写操作成功了,另一个写操作失败了;后者可以向外传播失败,或者重试,或者…,这完全取决于所讨论的特定机制。当然,重试应该意味着重新加载“原始状态”,此时模型会发现一些以前的编辑已经声明了用户名。
有什么办法解决这个问题吗?
每个流只有一个writer,通过消除模型的多个内存副本带来的歧义,问题的其余部分变得非常简单。
使用同步写入持久存储的多个写入程序可能是最常见的设计。它需要一个事件存储,它能够理解写入流中特定位置的想法——也就是“预期版本”。
您可以执行异步写入,然后开始执行其他工作,直到收到写入成功的确认(或不成功,或直到超时,或)。。。。
没有什么神奇的——如果你想要唯一性(或者任何其他不变的强制执行,就这点而言),那么每个人都需要在一个单一的权威上达成一致,任何其他想要提出修改的人都不会知道它是否被接受而没有得到权威的回复,并且需要为被拒绝的建议做好准备。
(注意:这并不奇怪——如果您使用的是传统设计,当前状态存储在rdbms中,那么您的权限将是数据库中的一个用户表,对username列具有唯一性约束,并且争用将在尝试首先完成其事务的两个insert语句之间进行……)

cwxwcias

cwxwcias2#

在大多数有约束的场景中,针对物化视图的初始验证是不够的。总有一些相关的事件尚未实现。有两种主要的并发控制方法可确保生成正确的结果:
1悲观方法:如果希望在发布事件之前验证约束,则需要锁定相关资源(实体、聚合或数据集)。锁定意味着您的服务不能在这些资源上发布事件。在此之后,要获取数据的当前状态:
您可以等到所有已发布的事件都实现锁定之前。
您可以从数据库中读取当前状态,并在单独的进程中对其应用事件。
2乐观方法:在这种方法中,您在发布事件之后执行验证。为了实现这一点,您需要实现一个反馈机制。使用事件并执行验证的进程应该能够发布验证结果。如果可能,可以在内存中执行验证。否则,您可以依赖物化数据存储。
martin kleppman在他的书中谈到了一个两步解决方法,这个方法在这里和他的书中完全相同。在这个解决方案中,有两个主题:“索赔”和“注册”。首先,发布获取用户名的声明,然后尝试将其写入数据库,最后将结果发布到registrations主题。在概念层面上,它遵循您提到的第二种方法中的相同步骤。在验证步骤中,通过依赖数据库,避免了实现验证逻辑和将二级索引保存在内存中。

相关问题