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

时间:2018-12-09 11:59:23

标签: spring-boot rabbitmq publish-subscribe distributed-system

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

2 个答案:

答案 0 :(得分:2)

您的问题与基本订阅处理的关系不大。根本问题是您是否可以保证一次操作将被准确执行一次。简短的答案是,您可能希望使用直接交换,以使消息进入一个队列并由(可能有许多)使用者使用。

长答案是不能保证“恰好一次”,因此您需要将其作为设计的一部分。

背景

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

此外,您应该意识到无法保证“恰好一次”交付的事实。从数学上讲,无法保证。相反,您可以使用以下两种方法之一(相互排斥):

  • 最多一次交货(0
  • 至少一次交货(1 <= n)

来自RabbitMQ文档:

  

使用确认可确保至少一次交付。如果没有确认,则在发布和使用操作期间可能会丢失消息,并且只能保证一次发送。

发布和使用消息时发生了几件事。由于消息处理系统(尤其是AMQP协议)的异步特性,因此无法保证仅进行一次处理,而仍然获得消息传递系统所需的性能(实质上是试图确保一次强制执行一切都通过重复数据删除过程中的串行过程完成。

设计含义

鉴于上述,重要的是您的设计必须“至少一次”交付。对于删除操作,这涉及将该操作的定义重写为断言的而不是程序性的(例如,“删除 this ”变为“确保 this 不存在”。 )。区别在于,您描述的是最终状态,而不是过程。

答案 1 :(得分:1)

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