如何确保只有一个消费者实际消费已发布的消息?

How do I ensure that only one consumer actually consumes a published message?

我使用微服务架构的 Rabbitmq。我对我的许多用例使用主题和直接交换,并且效果很好。但是我有一个用例,我必须从数据库中删除一条记录。当我删除记录时,需要调用其他几个服务和 maintain/delete 引用的记录。我可以通过简单地调用那些直接交换的服务来实现这一点,但我读到它是首选编排而不是编排。这意味着我应该实现 publish/subscribe 模式(rabbitmq 中的扇出)。 我的问题是,如果我在分布式系统中使用 publish/subscribe 模式,如何确保服务只有一个实例使用已发布的消息?

我觉得你应该为每个服务都有一个单独的队列,应该通知实例有关数据库记录删除的信息。交换器将消息的副本放入所有队列中。服务实例竞争对专用队列的访问(只有一个获取消息)。

您的问题与发布-订阅无关,而与基本消息处理有关。根本问题是您是否可以保证操作将恰好执行一次。简短的回答是,您可能希望使用直接交换,以便消息进入一个队列并由一个(可能是多个)消费者处理。

长答案是 "exactly once" 不能保证,所以你需要在你的设计中做这部分。

背景

最好的做法是让消息处理成为一个幂等操作。事实上,幂等性是几乎所有外部接口的关键设计假设(我认为它在内部接口中同样重要)。

此外,您应该知道无法保证 "exactly once" 送达。从数学上讲,无法做出这样的保证。相反,您可以拥有以下两种情况之一(互斥):

  • 最多发送一次(0 < n <= 1)
  • 至少交付一次(1 <= n)

来自 RabbitMQ 文档:

Use of acknowledgements guarantees at-least-once delivery. Without acknowledgements, message loss is possible during publish and consume operations and only at-most-once delivery is guaranteed.

发布和使用消息时会发生几件事情。由于消息处理系统的异步性质,尤其是 AMQP 协议,无法保证恰好一次处理,同时仍然产生消息系统所需的性能(本质上,试图确保恰好一次会强制一切都通过重复数据删除点的串行过程。

设计意义

鉴于上述情况,重要的是您的设计依赖于 "at least once" 交付。对于删除操作,这涉及将该操作的定义重写为断言式而非程序式(例如 "Delete this" 变为 "Ensure this does not exist.")。区别在于你描述的是最终状态而不是过程。