我们有一个微服务架构并应用cqrs模式。发送到微服务的命令触发应用程序状态更改,并在kafka总线上发出相应的事件。我们将这些事件投影到用elasticsearch构建的读取模型中。
到目前为止,还不错。
我们的微服务最终是一致的。但在任何时候,它们都不是(必然的)。因此,它们发送的事件也不总是相互一致的。
此外,为了保证应用程序状态更改和相应事件的发射之间的一致性,我们在同一事务中保留新状态和相应事件(我知道我们可以使用事件源,避免完全保留状态)。然后,异步工作者负责在kafka总线上发送这些事件。这种模式保证每个状态更改至少发送一个事件(这不是问题,因为我们的事件是幂等的)。但是,由于每个微服务都有自己的事件表和异步工作进程,因此我们不能保证事件将按照它们各自的微服务中发生相应状态更改的顺序发送。
编辑:澄清一下,每个微服务都有自己的数据库、事件表和工作程序。特定的工作进程按照事件在其相应的事件表中持久化的顺序处理事件,但是不同事件表上的不同工作进程(即对于不同的微服务)不提供这样的保证。
当在同一elasticsearch文档中从不同的微服务投射这些不连贯或无序的事件时,问题就出现了。
一个具体的例子:让我们设想由不同的微服务管理的三个不同的聚合a、b和c(领域驱动设计意义上的聚合):
a和b之间有一种多对多的关系。聚合a引用他绑定到的聚合根b,但b不知道它与a的关系。当b被删除时,管理a的微服务侦听相应的事件并撤消a与b的绑定。
同样地,b和c之间也存在多对多关系。b知道所有相关的c聚集体,但事实并非如此。当c被删除时,管理b的微服务侦听相应的事件并撤消b与c的绑定。
c有一个属性“name”。
其中一个用例是通过elasticsearch查找绑定到聚合b的所有聚合a,该聚合b又绑定到具有特定名称的聚合c。
如上所述,独立的事件表和worker可以在不同微服务的事件发射之间引入可变延迟。例如,创建a、b和c并将它们绑定在一起可能导致以下事件序列:
b已创建
b绑定到c
用名称xyz创建的c
创建的
a绑定到b
批事件的另一个示例:假设我们最初有聚合b和c,并且同时发出两个命令:
删除c
把b和c结合起来
这可能导致以下事件:
c删除
b绑定到c
b与c解除绑定(响应事件1)
具体来说,我们很难在elasticsearch文档中投影这些事件,因为这些事件有时引用不再存在或还不存在的聚合。任何帮助都将不胜感激。
1条答案
按热度按时间rggaifut1#
我不认为您提出的问题是您系统的投影部分独有的—它也可能发生在微服务a、b和c之间。
通常,投影仪
C created
与b同时进行。只有这样,b才能将自己绑定到c,这就使得投影仪不可能出现您提到的特定顺序。但是,如果b和c之间的网络通信比c和投影仪之间的通信速度快得多,那么消息可能会以错误的顺序到达,这是正确的。
我从未遇到过这样的问题,但我想到了几个选择:
不要在读取模型级别强制执行“外键”。存储b和它的c引用,即使你现在对c知之甚少。换句话说,使
B bound to C
以及C created
交换的。为事件添加因果关系id。这允许客户端识别和处理无序消息。您可以选择自己的策略-拒绝、等待因果事件到达、无论如何尝试处理等等,但这并不是一个简单的实现。
消息平台可以保证在特定条件下订购。你提到Kafka,在同一个主题和分区下。我认为,rabbitmq有着更为强大的先决条件。
我不是一个消息传递Maven,但是看起来微服务间的通信场景是有限的。这似乎也违背了当前最终一致性的趋势,即我们倾向于交换操作(见CRDT)而不是确保总顺序。