我经常在部署中使用kubernetes。当我们想要拥有多个读取模型生产者时,我不知道如何构建读取模型。如果我启动了一个需要重新构建其读取模型的新服务,我将订阅事件存储并从一开始就重播所有事件。然后,当服务达到速度时,我将只听取传入的新事件。当我们有这个服务的单个实例时,一切看起来都很好,但是如果我们有两个实例,那么它们都将接收事件并尝试应用它两次。经过一番搜索,我发现最常见的解决方案是对给定的读模型数据库仅使用单个订阅者/实例。这种方法在我看来有一个单一的失败点。如果订阅者由于某种原因失败,但没有立即崩溃,那么kubernetes将不会为该服务旋转新的实例。我该如何处理这种情况?
目前我看到它是这样的:CommandService(多个实例)=>EventStore =比;ReadModelProducerService(单个实例)=>ReadModel & lt; =比;QueryService(多个实例)。如果生成读模型的ReadModelProducerService的单个实例失败,那么应用程序基本上就会关闭。
至少有三个并发订阅者执行相同的投影代码。
- 在目标数据库上进行双重加载,因为它们将更新相同的记录或文档
- 如果投影代码不是幂等的(它应该是,但仍然),它将很快去地狱
- 他们会覆盖对方的检查点(这可能是最小的问题)
我想说第一个问题是最明显的,它可能会导致一些不希望的结果,如记录锁定和超时。
我不会那么担心"单点故障"。就像有许多其他的"单点"一样。事件存储本身、Kubernetes、投影接收器——所有这些组件都可能失败。
如果你更信任Kubernetes而不是你自己的代码,你可以避免订阅崩溃但在"僵尸模式"下保持存活的情况。这个问题是众所周知的,解决办法也是众所周知的。您需要向订阅添加一个直方图度量来度量处理速率。另一个有用的指标是订阅差距,如果订阅缓慢或停止,订阅差距将会扩大。在许多情况下,你可以检测到订阅丢失,因为它会给你的应用程序一个信号(不能连接到数据库,等等),这可以用作健康检查,迫使Kubernetes重新启动pod。我在Eventuous文档中写过这些事情。
快速的回答是It Depends,具体取决于您的read模型的需求是什么。
你的读模型绝对有可能需要同步处理每个事件(即对于任何2个事件,有一个有效的顺序,这些事件可以应用):如果是这种情况,那么是的,你只能有一个进程(这个进程基本上必须是单线程的,太)。您的吞吐量将受到限制。"这是休息时间,"除非您可以重构您的事件模型或协商需求变更以放松此需求。
如果不是这种情况,你通常可以用某种方式对事件进行分区/分片:这种机制会因事件存储而异(例如,在Akka Persistence中,你可能会按持久性(通常类似于聚合)ID进行分片,或者你可能会标记事件),但关键是要让事件的应用顺序在相同的分区/分片中。然后,每个实例可以负责不同的分区/分片:这通常需要在实例之间进行某种形式的协调,例如使用强一致性数据存储(例如Kubernetes中的etcd)进行租赁。您还需要深入研究事件存储提供的排序保证:例如,给定聚合根的事件将具有已定义的顺序,但聚合根排序的事件可能不会以任何可预测的顺序交错。
在事件本身中包含时间戳、序列号或矢量时钟也可以促进事后重建全局顺序,尽管这通常需要对时间方面的不确定性进行一些显式建模(例如建模撤回和置信度)。