10

几天以来,我一直在试图弄清楚如何通知其他微服务在微服务 A 中创建了一个新实体,该微服务 A 将该实体存储在 MongoDB 中。

我想要:

  • 微服务之间的耦合度低

  • 避免微服务之间的分布式事务,如两阶段提交 (2PC)

起初,像 RabbitMQ 这样的消息代理似乎是完成这项工作的好工具,但后来我发现在 MongoDB 中提交新文档并在代理中发布消息不是原子的问题。

为什么是事件溯源?通过 eventuate.io: 在此处输入图像描述

解决此问题的一种方法是通过添加一个标记来说明文档是否已在代理中发布并具有计划的后台进程来搜索 MongoDB 中未发布的文档并将其发布到代理,从而使文档的架构变得更脏确认,当确认到达时,文档将被标记为已发布(使用至少一次和幂等语义)。在这个这个答案中提出了这个解决方案。

阅读 Chris Richardson 的《微服务简介》后,我在这篇关于使用事件溯源开发功能域模型的精彩演讲中结束了其中一张幻灯片问道:

如何在没有 2PC 的情况下原子更新数据库发布事件和发布事件?(双写问题)。

答案很简单(在下一张幻灯片上)

更新数据库发布事件

这是基于CQRS a la Greg Young的另一种方法。

域存储库负责发布事件,这通常在单个事务中,同时将事件存储在事件存储中。

我认为将存储和发布事件的责任委托给事件存储是一件好事,因为它避免了 2PC 或后台进程的需要。

然而,从某种意义上说,这是真的

如果您依赖事件存储来发布事件,那么您将与存储机制紧密耦合。

但是,如果我们采用消息代理来实现微服务之间的通信,我们也可以这么说。

更让我担心的是,事件存储似乎变成了单点故障。

如果我们从eventuate.io看这个例子 在此处输入图像描述

我们可以看到,如果事件存储关闭,我们就无法创建账户或转账,失去了微服务的优势之一。(尽管系统将继续响应查询)。

那么,确认最终示例中使用的事件存储是单点故障是否正确?

4

5 回答 5

6

您面临的是两个将军问题的一个实例。基本上,您希望网络上有两个实体就某事达成一致,但网络不是故障安全的。Leslie Lamport 证明这是不可能的。

因此,无论您向网络添加多少新实体,消息队列都是一个,您永远无法 100% 确定会达成一致。事实上,相反的情况发生了:你添加到分布式系统中的实体越多,你就越不能确定最终会达成一致。

您的案例的一个实际答案是,如果您考虑增加更多的复杂性和单点故障,2PC 并不是那么糟糕。如果你绝对不希望出现单点故障,并且想假设网络是可靠的(换句话说,网络本身不可能是单点故障),你可以尝试DHT等 P2P 算法,但是对于两个同行我敢打赌它减少到简单的 2PC。

于 2016-03-21T20:10:03.173 回答
2

我们使用 NServiceBus 中的 Outbox 方法来处理这个问题:

http://docs.particular.net/nservicebus/outbox/

这种方法要求整个操作的初始触发器作为队列中的消息进入,但效果很好。

于 2016-03-22T09:05:13.497 回答
1

如果我们有两个事件存储,并且每当创建域事件时,它都会排队到它们两个上。查询端的事件处理程序处理从两个事件存储中弹出的事件。

当然,每个事件都应该是幂等的。但这难道不能解决我们的事件存储是单一入口点的问题吗?

于 2018-02-25T13:19:51.430 回答
1

您还可以为事件存储中的每个条目创建一个标志,以告知此事件是否已发布。另一个进程可以轮询事件存储以查找那些未发布的事件,并将它们放入消息队列或主题中。这种方法的缺点是该队列或主题的消费者必须设计为对传入消息进行重复数据删除,因为这种模式只保证至少一次传递。由于轮询频率,另一个缺点可能是延迟。但是由于我们已经在这里进入了最终一致的区域,所以这可能不是一个大问题。

于 2016-04-21T21:31:47.477 回答
0

不是特别的 mongodb 解决方案,但您是否考虑过利用 Redis 5 中引入的 Streams 功能来实现可靠的事件存储。看看这个介绍在这里

我发现它具有丰富的功能,例如消息拖尾、消息确认以及轻松提取未确认消息的能力。这肯定有助于实现至少一次消息传递保证。它还支持使用“消费者组”概念的消息负载平衡,这有助于扩展处理部分。

关于您对成为单点故障的担忧,根据文档,流和消费者信息可以跨节点复制并持久化到磁盘(我相信使用常规的 Redis 机制)。这有助于解决单点故障问题。我目前正在考虑将其用于我的一个微服务项目。

于 2019-04-08T15:13:54.933 回答