处理CQRS读取端的无序事件

时间:2016-11-20 11:15:20

标签: cqrs event-sourcing event-store

我已经阅读了Jonathan Oliver关于处理乱序事件的好文章。

http://blog.jonathanoliver.com/cqrs-out-of-sequence-messages-and-read-models/

  

我们使用的解决方案是将消息出列并将其置于“保持表”中,直到所有具有先前序列的消息为止   接收。收到所有以前的消息后,我们全部收到   消息从保持表中出来并按顺序运行它们   适当的处理程序。一旦所有处理程序都被执行了   成功之后,我们从保留表中删除消息并提交   阅读模型的更新。

     

这适用于我们,因为域发布事件并标记它们   具有适当的序列号。没有这个,解决方案   下面会更加困难 - 如果不是不可能的话。

     

此解决方案使用关系数据库作为持久性存储   机制,但我们没有使用任何关系方面的   存储引擎。与此同时,所有这一切都有一个警告。   如果消息2,3和4到达但消息1从未到达,则我们不适用   任何一位。只有在出现错误时才会出现这种情况   处理消息1或消息1以某种方式丢失。幸好,   它很容易纠正我们的消息处理程序中的任何错误   重新运行消息。或者,在丢失消息的情况下,重新构建   直接从事件存储中读取模型。

特别提到了几个问题,他说我们总是可以向活动商店询问缺失事件。

  1. CQRS的写入方是否必须公开读取服务 一边“要求”重播事件?例如,如果事件1不是 收到但是2,4,3我们可以通过一个问问题 服务从1开始重新发布事件?
  2. 此服务是否由CQRS的写入方负责?
  3. 我们如何使用此功能重新构建读取模型?

4 个答案:

答案 0 :(得分:1)

如果您有序列号,那么您可以检测当前事件发生故障的情况,例如: currentEventNumber!= lastReceivedEventNumber + 1

一旦您检测到这一点,您就会抛出异常。如果您的订阅者具有“重试”机制。它将尝试在一秒左右再次处理此事件。很有可能在此期间处理早期事件并且序列将是正确的。如果无序事件很少发生,这是一个解决方案。

如果您经常遇到这种情况,则需要实现全局锁定机制,这将允许按顺序处理某些事件。 例如,我们在MSSQL中使用sp_getapplock来实现全局"关键部分"在某些情况下的行为。当分布式应用程序的多个部分需要的不仅仅是简单的锁定时,Apache ZooKeeper提供了一个框架来处理更复杂的场景。

答案 1 :(得分:1)

基于时间戳的解决方案:

收到的消息是:

{
 id: 1,
 timestamp: T2,
 name: Samuel
}
{
 id: 1,
 timestamp: T1,
 name: Sam,
 age: 26
}
{
 id: 1,
 timestamp: T3,
 name: Marlon Samuels,
 contact: 123
}

我们希望看到的与数据库中的ORDER无关是:

{
 id: 1,
 timestamp: T3,
 name: Marlon Samuels,
 age: 26,
 contact: 123
}

对于每条传入的消息,请执行以下操作:

  1. 获取持久记录并评估时间戳。
  2. 目标是更大的时间戳。

现在让我们看一下消息:

  1. T2首先到达:因为它是第一个,所以将其存储在数据库中。
  2. T1接下来到达:持久性(T2)和传入(T1),因此T2是目标。
  3. T3到达:持久性(T2)和传入(T1),因此T3是目标。

以下deepMerge(src,target)应该能够为我们提供结果:

public static JsonObject deepMerge(JsonObject source, JsonObject target) {
    for (String key: source.keySet()) {
        JsonElement srcValue = source.get(key);
        if (!target.has(key)) { // add only when target doesn't have it already
            target.add(key, srcValue);
        } else {
            // handle recursively according to the requirement

        }
    }
    return target;
}

在评论中让我知道您是否需要DeepMerge()的完整版本

答案 2 :(得分:0)

您在这里描述的是事件采购(ES)。将命令模型发出的事件存储到持久存储。 按事件类型,命令模型ID(聚合根id),命令模型类型(聚合根类型)重播存储的事件。这是ES的优势。您甚至可以稍后重播这些事件以生成新类型的查询模型。 使用ES方法也可以用于具有UnitOfWork范围的应用程序事务。在提交时,发出的事件被持久化并分发给事件侦听器(QM维护服务)。提交阶段的验证应包含按db。中的序列号检查并发访问。

答案 3 :(得分:0)

另一种选择是从(S1)提供您的阅读事件的服务,使其只能为您的服务生成有序事件(S2)。

例如,如果您有许多不同会话的事件加载,请在前端订购服务(O1)负责订购。它确保每个会话只有一个事件传递给(S1),并且只有当(S1)和(S2)都成功处理时(O1)才允许该会话的新事件传递给(S1)。为了提高性能,也要排队等待。