RabbitMQ消息可靠性保障
消息丢失的情况
publisher 在往rabbitmq发送消息时,可能由于网络问题导致消息丢失
rabbitmq在投递消息时,找不到匹配的队列消息时,可能导致消息丢失,rabbitmq队列存储消息在未push给消费者之前,如果服务器故障可能导致消息丢失
rabbitmq在将消息push给消费者时,可能由于网络问题导致消息丢失
也就是说消息可能在发送过程中、rabbitmq服务器上、消息接收过程中丢失。
下面分类阐述上述三种情况,如何解决。
发送过程
publisher-confirm
在发送消息时,采用发送方确认(publisher-confirm)机制,通过channel.confirmSelect方法将信道设置为confirm模式。在这种模式下,RabbitMQ收到生产者发送的消息后,会发送一个ack或者nack给生产者。
-
- ack: 消息已经正确投递给队列或者其他交换机(如果是持久化消息,那么ack会在消息持久化到磁盘后才会给出)
- nack: RabbitMQ从接收到消息,到消息成功投递到队列,这期间处理该消息的任何异常 都会导致rabbitmq发送nack给生产者。
需要注意的是,如果rabbitmq收到消息后,将消息投递给队列时,如果没有匹配的队列,这是rabbitmq发送给生产者的确认是 ack,这会导致消息丢失并且没有任何错误信息。因此,生产者不仅要开启生产者确认模式,还要开启publisher-return。
rabbitmq发送ack或者nack的时机:
正常情况下,rabbitmq收到的消息投递到所有匹配的队列后才会发ack生产者。如果有多个队列匹配消息,只要投递到其中任何一个队列时发生错误,rabbitmq都会进行nack。如果消息是持久化消息,则消息持久化到磁盘后才会发送ack给生产者。
publisher-return
在发送消息时,将mandatory参数设置为true,此时,如果rabbitmq无法根据交换机和路由键找到符合条件的队列进行投递,RabbitMQ会将消息退回给生产者。如果mandatory为false,则消息会被直接丢弃。如果使用了该参数,需要给channel设置ReturnListener来监听RabbitMQ退回的消息。需要注意的是,rabbitmq只有再无法根据路由键匹配到队列时才会将消息退回。如果有匹配队列,投递到队列时出错,rabbitmq是不会将消息退回的,只会发送nack。因此,不仅退回的消息要重新处理,nack的消息也要重新处理。
持久化消息
在发送消息时,将消息的durable属性设置为true,告诉RabbitMQ需要将消息持久化。
RabbitMQ服务器
这里需要保证RabbitMQ的高可用,并且需要防止消息在RabbitMQ集群中丢失。
RabbitMQ集群中,集群所有节点都保存有vhost、exchange、bindings、queue的元数据信息。但是队列中的message只有在声明这个queue的节点上(队列的所有者节点)有。一旦该节点崩溃,该节点上的队列和队列的绑定关系都丢失了(不过其他节点上也会有这些信息),连接到该节点上的消费者也都下线。,所有投递到该节点的队列的新消息(未来得及持久化的message都会丢失)。此时,想要该节点中的队列返回集群正常工作的唯一方法时恢复该故障节点。
RabbitMQ集群中,集群所有节点分为内存节点和磁盘节点两类,磁盘节点会将上述元数据信息保存到磁盘,而内存节点只会保存到内存中。RabbitMQ集群应当至少有一个磁盘节点。但如果集群中只有一个磁盘节点,一旦该节点崩溃,虽然RabbitMQ能够正常收发消息,但是无法进行元数据的更改,如创建交换机、队列等操作将无法进行。因此,一般建议所有节点都设置为磁盘节点。
因此在RabbitMQ集群中,需要使用镜像队列或者仲裁队列来保证消息不丢失。
镜像队列
在声明队列时,添加额外参数,将队列设置为镜像队列。如:
queue_args = {"x-ha-policy", "all"} channel.queue_declare(queue="hello",arguments=queue_args)
这样,发送到RabbitMQ服务器的消息,会被存储到集群中的所有节点上。如果你不想存储到所有节点,而只想消息有一个或者几个备份怎么办?很麻烦,需要将集群节点硬编码到你的代码中,不建议这样做。
仲裁队列
在新的RabbitMQ版本中,可以使用冲裁队列。冲裁采用Raft算法,将消息发送到集群中的大多数节点。
注意事项:
1. 需要保障消息可靠性时,不要使用备份交换机(AE交换机),AE交换机会导致publisher-return机制失效。因为所有无法投递的消息都会到AE交换机。即使AE交换机不存在,RabbitMQ将消息丢弃也不会退回给生产者
接收过程
接收过程较为简单,使用ack机制即可。