我正在构建微服务。我的MicroService之一正在使用CQRS和事件来源。集成事件在系统中引发,我将聚合保存在事件存储中,同时还更新了我的读取模型。
我的问题是,为什么要针对该聚合更新事件流时为什么需要聚合版本?我读到,为了保持一致性,我们需要这样做,并且必须按顺序重播事件,并且在保存(https://blog.leifbattermann.de/2017/04/21/12-things-you-should-know-about-event-sourcing/之前,我们需要检查版本。由于事件是按顺序引发和保存的,所以我仍然无法解决这个问题。我真的需要一个具体的例子来了解我们从版本中获得什么好处,以及为什么我们甚至需要它们。
非常感谢,
伊朗
答案 0 :(得分:1)
让我描述一个汇总版本有用的情况:
在我们的reSove framework聚合版本中,用于乐观并发控制。
我将举例说明。假设InventoryItem
集合接受命令AddItems
和OrderItems
。 AddItems
增加库存物品数量,OrderItems
-减少数量。
假设您有一个事件的InventoryItem
汇总#123-数量为5的ITEMS_ADDED
,则在#123状态下说有5件库存。
因此,您的用户界面向用户显示有5件库存。用户A决定订购3件商品,用户B-4件商品。几乎同时,这两个命令都发出OrederItems
命令,也就是说用户A首先是几毫秒。
现在,如果您在内存中具有聚合#123的单个实例,则在单个线程中,您没有问题-来自用户A的第一个命令将成功,将应用事件,状态为数量为2,因此来自用户B的第二条命令将失败。
在分布式或无服务器系统中,来自A和B的命令将在单独的进程中,如果我们不使用某些并发控制,则这两个命令将成功执行,并使聚合处于错误状态。有几种方法可以做到这一点-悲观锁定,命令队列,聚合存储库或乐观锁定。
乐观锁定似乎是最简单,最实用的解决方案:
我们说过聚合具有版本-流中的事件数。因此我们的总#123的版本为1。
当聚合发出事件时,此事件数据具有聚合版本。在我们的情况下,来自用户A和B的ITEMS_ORDERED
事件的事件聚合版本为2。显然,聚合事件的版本应随之增加。因此,我们需要做的就是添加一个数据库约束,使元组{aggregateId, aggregateVersion}
在写入事件存储时应该是唯一的。
让我们看看我们的示例如何在具有乐观并发控制的分布式系统中工作:
OrderItem
{version 1, quantity 5}
恢复OrderItem
ITEMS_ORDERED {aggregateId 123, version 2}
被写入事件存储。用户B的聚合实例执行命令,它成功,事件ITEMS_ORDERED {aggregateId 123, version 2}
尝试将其写入事件存储和fais(并发异常)。
在这种针对用户B的异常命令处理程序上,只需重复整个过程-然后聚合#123将处于{version 2, quantity 2}
状态,并且命令将正确执行。
我希望这可以清除汇总版本有用的情况。
答案 1 :(得分:0)
是的,这是正确的。您需要版本或序列号以保持一致性。
您想要两件事:
正确的订购
通常,事件本质上是幂等的,因为在分布式系统中,幂等的消息或事件更易于处理。幂等消息是即使多次应用也会产生相同结果的消息。用固定值更新寄存器(例如1)是幂等的,但是将计数器递增1则不是幂等的。在分布式系统中,当A向B发送消息时,B确认A。但是,如果B使用了该消息,并且由于某些网络错误而丢失了对A的确认,则A不知道B是否收到了该消息,因此它发送了该消息。再次。现在,B再次应用该消息,如果该消息不是幂等的,则最终状态将出错。因此,您需要幂等消息。但是,如果您无法按照产生它们的顺序来应用这些幂等消息,那么您的状态将再次错误。可以使用版本ID或序列来实现此排序。如果您的事件存储是RDBMS,则没有任何类似的排序键就无法订购事件。在Kafka中,您还具有偏移ID,客户端会跟踪消耗的偏移量
重复数据删除
其次,如果您的信息不是幂等的怎么办?或者,如果您的消息是幂等的,但使用者以不确定的方式调用某些外部服务,该怎么办?在这种情况下,您需要一次精确的语义,因为如果两次应用相同的消息,您的状态将是错误的。在这里,您还需要版本ID或序列号。如果在用户端跟踪已处理的版本ID,则可以根据该ID进行重复数据删除。然后在Kafka中,您可能希望将偏移量ID存储在用户端
根据评论的进一步说明:
相关文章的作者假定RDBMS作为事件存储。版本ID或事件序列应由生产者生成。因此,在您的示例中,“已交付”事件的顺序要比“在途”事件的顺序高。
当您要并行处理事件时,就会发生此问题。如果一个消费者获得“已交付”事件而另一消费者获得“在途”事件该怎么办?显然,您必须确保特定订单的所有事件均由同一使用者处理。在Kafka中,您可以通过选择订单ID作为分区键来解决此问题。由于一个分区只能由一个使用者处理,因此您知道在交付之前总是可以得到“运输中”的信息。但是,多个订单将分散在同一消费者组中的不同消费者之间,因此您需要进行并行处理。
关于聚合ID,我认为这是Kafka中主题的同义词。由于作者假设使用RDBMS存储,因此他需要一些标识符来区分不同类别的消息。为此,您可以在Kafka中创建单独的主题,并在每个汇总中创建消费者组。