几天以来,我一直试图弄清楚如何告知其余的微服务,在微服务A中创建了一个新实体,将该实体存储在MongoDB中。
我想:
微服务之间的耦合度低
避免像两阶段提交(2PC)
起初像RabbitMQ这样的消息代理似乎是一个很好的工具,但后来我发现MongoDB中的提交新文档和发布消息的问题在经纪人不是原子的。
eventuate.io的解决此问题的一种方法意味着通过添加一个标记来说明文档的架构有点脏,该标记表明文档是否已在代理中发布,并且具有在MongoDB中搜索未发布文档的预定后台进程并将其发布到使用confirmations的代理,当确认到达时,文档将被标记为已发布(使用at-least-once和idempotency语义)。此解决方案在this和this答案中提出。
阅读Chris Richardson撰写的Introduction to Microservices我最后在Developing functional domain models with event sourcing的精彩演讲中提到了其中一张幻灯片:
如何原子更新数据库和发布事件并在没有2PC的情况下发布事件? (双写问题)。
答案很简单(在下一张幻灯片中)
更新数据库和发布事件
这是this one基于CQRS a la Greg Young的不同方法。
域存储库负责发布事件 通常在与存储一起的单个交易中 事件商店中的事件。
我认为委托将事件存储和发布到事件存储的责任是一件好事,因为避免了2PC或后台进程的需要。
但是,以某种方式它是真的that:
如果您依靠活动商店发布您拥有的活动 紧密耦合到存储机制。
但是如果我们采用消息代理来进行微服务的交互,我们可以说同样的话。
让我更担心的是,事件存储似乎成了单点故障。
如果我们从example查看此eventuate.io
我们可以看到,如果事件存储已关闭,我们无法创建帐户或资金转移,从而失去了微服务的一个优势。 (虽然系统会继续响应查询)。
因此,确认eventuate示例中使用的事件存储是单点故障是正确的吗?
答案 0 :(得分:5)
您所面对的是Two General's Problem的一个实例。基本上,您希望网络上有两个实体同意the network is not fail safe之类的内容。 Leslie Lamport证明这是不可能的。
因此,无论您向网络添加多少新实体,消息队列为一,您都不会100%确定将达成协议。实际上,情况恰恰相反:您添加到分布式系统的实体越多,您就越不能确定最终会达成协议。
对于您的案例,一个实际的答案是,如果您考虑添加更多复杂性和单点故障,2PC并不是那么糟糕。如果您绝对不想要单点故障并且想要假设网络是可靠的(换句话说,网络本身不能是单点故障),您可以尝试使用P2P算法,例如{{3}但是对于两个同伴我敢打赌它会简化为简单的2PC。
答案 1 :(得分:1)
我们使用NServiceBus中的Outbox方法来处理这个问题:
http://docs.particular.net/nservicebus/outbox/
这种方法要求整个操作的初始触发器作为队列中的消息进入,但效果很好。
答案 2 :(得分:1)
您还可以为事件存储内的每个条目创建一个标志,告知该事件是否已发布。另一个进程可以在事件存储中轮询这些未发布的事件,并将它们放入消息队列或主题中。这种方法的缺点是该队列或主题的消费者必须设计为消除传入消息的重复,因为该模式仅保证至少一次传递。由于轮询频率,另一个缺点可能是延迟。但由于我们已经进入了最终一致的区域,这可能不是一个大问题。
答案 3 :(得分:1)
如果我们有两个事件存储,那么每当创建域事件时,它就排队到两个事件存储。查询端的事件处理程序处理从两个事件存储中弹出的事件。
当然,每个事件都应该 幂等 。 但这不会解决我们的事件存储是单一入口点的问题吗?
答案 4 :(得分:0)
并不是mongodb解决方案特别重要,但您是否考虑过利用Redis 5中引入的Streams功能来实现可靠的事件存储。看看这个介绍here
我发现它具有丰富的功能,例如消息尾部,消息确认以及轻松提取未确认消息的功能。这肯定有助于至少实现一次消息传递保证。它还支持使用“消费者组”概念的消息负载平衡,这可以帮助扩展处理部分。
关于您对单点故障的担忧,根据文档,流和使用者信息可以在节点之间复制并持久化到磁盘(我相信使用常规的Redis机制)。这有助于解决单点故障问题。我目前正在考虑将其用于我的一个微服务项目。