CQRS + 微服务处理事件回滚
Posted
技术标签:
【中文标题】CQRS + 微服务处理事件回滚【英文标题】:CQRS + Microservices Handling event rollback 【发布时间】:2018-02-21 17:56:30 【问题描述】:我们正在使用微服务、cqrs、使用 nodejs cqrs-domain 的事件存储,一切都像魅力一样工作,典型的流程如下:
-
休息->2。服务->3。命令验证-> 4。命令-> 5。聚合-> 6。事件-> 7。事件存储(事务数据)-> 8。返回带有聚合 ID 的聚合-> 9. 存储在微服务本地数据库中(本质上是读取数据库)-> 10. 将事件发布到队列
上述流程的问题在于,由于事务数据保存,即事件存储的持久性和微服务读取数据的存储发生在不同的事务上下文中,如果在步骤 9 出现任何故障,我应该如何处理事件已经传播到事件存储和已经更新的聚合?
任何建议都将受到高度赞赏。
【问题讨论】:
“9.store in microservice local DB(本质上是read DB)”到底是什么? 它是读取微服务的读取数据库,例如 GET、GETALL 等。 看起来您正在将聚合状态保存在数据库中?什么是第 8 步? 【参考方案1】:上述流程的问题在于,由于事务数据保存,即事件存储的持久性和微服务读取数据的存储发生在不同的事务上下文中,如果在步骤 9 出现任何故障,我应该如何处理事件已经传播到事件存储和已经更新的聚合?
您稍后重试。
“记录簿”是事件存储。下游视图(“已发布事件”,读取模型)来自记录簿。它们通常在时间上落后于记录 (eventual consistency),并且通常不会相互同步。
因此,您可能在某个时间点将 105 个事件写入记录簿,但只有 100 个事件发布到队列中,并且您的服务数据库中的一个表示仅由 98 个构成。
更新视图通常以两种方式之一完成。当然,您可以从一个全新的表示开始,并将所有事件作为每次更新的一部分重播到其中。或者,您可以在视图的元数据中跟踪您已经获得的事件历史记录,并使用该信息来确定下一次读取事件历史记录的位置。
【讨论】:
这几乎可以回答它,实际上我缺少的是已调度和未调度的事件。在这种情况下,无论其在读取数据库上的写入如何,都会生成此处的事件,但不同之处在于它将降落在未调度的事件表中。【参考方案2】:在您的事件存储中,您可以跟踪读取端复制是否成功。 一旦第 9 步成功,您就可以将事件标记为“已复制”。
这样,您可以引入一个组件来监视未复制的事件并触发第 9 步。您还可以跟踪复制是否多次失败。
更新读取端(第 9 步)并将事件标记为已复制应该始终如一地发生。您可以在此处使用 saga 模式。
【讨论】:
事件存储是只能追加的,事件不应该知道它们在哪里被使用过。我会说它是 readmodel 应该知道有多少事件已应用于它,并且 eventstore 应该能够判断任何特定事件在流中具有哪个“序号”。因此,如果您的 readmodel 应用了 120 个事件并接收到事件 #122,它应该从 eventstore 请求丢失的事件 谢谢,这确实是更清洁的方法。 我使用的事件存储在已调度和未调度的事件之间保持了明显的区别,我完全错过了这些事件,我相信这就是问题的答案所在。我相信的 Saga 模式更多的是关于具有不同聚合的服务如何处理它们之间的业务依赖关系,而不是我相信在这种情况下要使用的模式。 . 一个事件可以被数百个读取端使用。您如何确定哪些是触发标记的重要?或者,如果您用多个较小的读取端替换一个大的读取端。我不认为这是一个好主意。正如已经说过的:只追加,从不修改。【参考方案3】:我想我现在已经更好地理解了它。 仍然会创建聚合,答案是任何类型的一致性的所有验证都应该在构建聚合之前发生,如果发生超出代码范围的故障,则在更新读取侧数据库时存在故障需要处理的微服务。 因此,在理想情况下,将创建聚合,但相关的事件将保持未调度,除非所有读取依赖项都已更新,否则它保持未调度并且可以单独处理。 Event Store 仍将拥有所有事件,并且以这种方式保持最终一致性。
【讨论】:
当您有多个读取端时,您如何处理那些(未)分派的事件? (具有自动故障转移和负载平衡,根据服务器负载启动和停止实例)。事件存储如何确定有多少事件消费者必须说“是”?以上是关于CQRS + 微服务处理事件回滚的主要内容,如果未能解决你的问题,请参考以下文章