事件溯源:同时创建冲突事件

Geo*_*rge 6 concurrency event-sourcing apache-kafka

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

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

关于如何解决这个问题的任何想法?

谢谢。

更新:

特别是,我想验证以下是否是解决问题的可接受方法:

  1. 使用用户名作为用户 ID(限制性)
  2. 将事件发送到按用户名分区的主题,并在验证完成后将事件发送到另一个主题

gor*_*kem 3

在大多数有约束的情况下,针对物化视图的初始验证是不够的。总有一些相关事件尚未实现。有两种主要的并发控制方法来确保生成正确的结果:

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

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

2. 乐观方法:在这种方法中,您在发布事件后执行验证。为了实现这一点,您需要实施反馈机制。使用事件并执行验证的流程应该能够发布验证结果。如果可能,您可以在内存中执行验证。否则,您可以依赖您的物化数据存储。

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