CQRS-乱序消息

时间:2018-11-12 22:04:41

标签: microservices distributed-computing cqrs event-sourcing

假设我们有3个不同的服务来生成事件,每个服务都发布到其自己的事件存储中。

这些服务中的每一个都消耗其他生产者服务事件。 这是因为每个服务都必须处理另一个服务的事件并创建自己的投影。每个服务都可以在多个实例上运行。

(对我而言)最直接的方法是在每个ES前面放置“内容”,以选择事件并将其发布(发布/订阅)在其他所有服务的队列中。

这是完美的,因为每个服务都可以订阅自己喜欢的每个主题,而事件发布者正在执行任务,并且如果服务不可用,事件仍会传递。在我看来,这是为了保证高可伸缩性和可用性。

我的问题是队列。我无法获得一个可轻松扩展的队列来保证消息的顺序。它实际上保证了至少一次交付的“轻微故障”:显然,它是AWS SQS。

因此,订购问题是:

  • 不能保证来自同一事件流的所有事件的顺序。
  • 不能保证来自同一ES的事件之间的顺序。
  • 不能保证来自不同ES(不同服务)的事件之间的顺序。

尽管我可以通过跟踪来自同一ES的事件的“序列号”来解决前两个问题。 这可以通过跟踪我们正在消费事件的每个主题的最后序列号来完成 对于事件做出反应并建立我们的预测应该很容易。 然后,当我从队列中弹出事件时,如果eventSequenceNumber > previousAppliedEventSequenceNumber + 1我重新排队(或使其在特定时间内不可见)。

但是事实证明,使用此解决方案,当事件发生率很高时,它将破坏性能(我可以使用可见性超时或其他方法,结果应该相同)。

这是因为当我期望事件10并且暂时忽略事件11时,我也应该忽略所有事件(来自ES),其序列号在该事件11之后,直到事件11再次出现并得到有效处理

其他困难是:

  • 在哪里跟踪事件的序列号以构建投影。
  • 如何跟踪事件的序号以构建投影,以便在应用投影时具有一致的lastSequenceNumber

我缺少什么?

P.S。:对于第三个问题,请考虑以下情形。我们有一个UserService和一个CartServiceCartService有一个投影,每个用户可以在其中跟踪购物车中的产品。每个购物车的投影还必须具有来自UserCreated发布的UserService事件的用户名和其他信息。如果UserCreatedProductAddedToCart之后,则正常流程将引发异常,因为该用户尚不存在。

2 个答案:

答案 0 :(得分:2)

  

我缺少什么?

您缺少流-消费者从源头 pull 消息,而不是让消息源将消息推送给消费者。

当我醒来时,我会检查我的书签以找出我上一次阅读的邮件,然后问您是否有此后发。如果有,我会按顺序从您那里检索它们(请考虑“文档消息”),并写下新的书签。然后我回去睡觉。

推送通知的主要目的是中断睡眠时间(从而减少等待时间)。

以SQS作为队列,其思想是您一次读取排队的消息的 all 。如果没有差距,则可以订购集合,然后开始处理它们并确认它们。如果存在间隙,则可以等待(将消息保留在队列中),也可以转到事件存储来获取丢失消息的副本。

没有魔术-如果消息管道承诺“至少一次”传递,那么消费者必须采取措施以识别重复的消息。

  

如果UserCreated在ProductAddedToCart之后出现,则正常流程需要引发异常,因为该用户尚不存在。

评论Race Conditions Don't Exist,作者Udi Dahan:“时间上的微秒差异不应改变核心业务行为。”

答案 1 :(得分:0)

基本问题是假设我们可以按顺序收到消息... 这是分布式计算中的一个谬论。 我建议您设计为在系统中不安排任何消息。

对于您的问题,请尝试在发起者创建的消息正文/标题中使用UTC时间,然后尝试解决此数据点。除非您具有中央确定性序列创建者(这将是不可扩展的单点故障),否则序列号将失败。

使用Sagas /状态机是一条有助于理解(业务)事件排序的路径。