本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力。
从生产者、MQBroker、消费者三个方面入手,详细介绍了RabbitMQ如何保证消息可靠传递不丢失的。
消息丢失不外乎生产者在传输过程中丢失消息,RabbitMQ没有及时持久化而丢失消息,Consumer没有消费成功被提交丢失消息这三种情况,实际上,所有的消息队列,比如Kafka、RocketMQ的消息可靠性都可以从这三方面入手。
1 生产者可靠性
正常情况下,如果消息经过交换器进入队列就可以完成消息的持久化,但如果消息在没有到达broker之前出现意外,那就造成消息丢失,这就是生产者数据丢失。
RabbitMQ提供transaction事务消息和confirm确认模式来保证消息不会丢失。
1.1 事务机制
事务的实现主要是对信道(Channel)的设置,分别在消息发送前后调用该不同的接口:
// 开启事务
channel.txSelect
try {
//发送消息
} catch (Exception e) {
channel.txRollback
//未能到达RabbitMQ服务器,会抛出异常,此时可以重发消息
}
// 发送成功,事务完成,提交事务
channel.txCommit
事务机制虽然能够保证生产者消息成功到达RabbitMQ服务器,但是采用同步阻塞机制,性能很低,严重影响吞吐量下。
1.2 confirm 机制
采用事务机制实现会降低RabbitMQ的消息吞吐量,那么有没有更加高效的解决方式呢?当然有,那就是采用Confirm确认模式。注意消息确认和事务不能同时使用。
通过在发送消息之前使用channel.confirmSelect();开启confirm确认模式。
在发送消息之后,Confirm可以等待消息确认,Confirm有三种确认的方式:
channel.waitForConfirms(); //普通发送方确认模式;
channel.waitForConfirmsOrDie(); /批量确认模式;
channel.addConfirmListener(); //异步监听发送方确认模式,可以在消息发送之前添加。
异步的confirm模式最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以通过在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息。
开启异步的confirm模式之后,每次写入的消息都会被分配一个唯一的序列号(从1开始),可以在消息发送之前通过channel#getNextPublishSeqNo方法获取序号,一旦消息被投递到所有匹配的队列之后,broker就会发送一个ack给生产者(也包含该消息的序列号),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出。
如果RabbitMQ未能成功处理这个而消息,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息,我们可以重试发送。
//开启confirm确认模式
channel.confirmSelect();
//异步确认,注册监听器
channel.addConfirmListener((sequenceNumber, multiple) -> {
// 确认消息时的代码
}, (sequenceNumber, multiple) -> {
// 消息未确认时的代码
});
一般来说,异步confirm使用得最多。
2 RabbitMQ可靠性
为了防止RabbitMQ丢数据的情况,一般是开启RabbitMQ持久化的配置。
那么如何持久化呢,需要两步:
- 生产者创建队列的时候,将queue的持久化属性durable设置为true,这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。
- 生产者发送消息的时候,将消息的deliveryMode设置为2,这表示将消息设置为持久化,此时 RabbitMQ 就会将消息持久化到磁盘上去。
这样两步设置以后,RabbitMQ就算挂了,重启后也能恢复数据。
但是在消息还没有持久化到硬盘时,可能服务已经挂掉,此时消息还在内存中,还是可能丢失。这种情况可以让持久化配置可以和confirm机制配合使用,如果同时开启,那么RabbitMQ将会在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者可以重发。
另外,最好还设置exchange持久化,即将exchange的durable属性设置为true。如果不设置exchange的持久化对消息的可靠性来说没有什么影响,但是同样如果exchange不设置持久化,那么当broker服务重启之后,exchange将不复存在,那么既而发送方rabbitmq producer就无法正常发送消息。
3 消费者可靠性
启用手动确认模式可以解决这个问题:
- 自动确认模式:在消息发送给消费者后立即确认,但存在丢失消息的可能,比如消息刚到消费者,还没处理,结果服务器挂了,此时数据就丢了。
- 手动确认模式,如果消费者来不及处理就挂掉,没有响应ack时会重复发送一条信息给其他消费者;如果监听程序处理异常了,且未对异常进行捕获,会一直重复接收消息,然后一直抛异常;如果对异常进行了捕获,但是没有在finally里ack,也会一直重复发送消息(重试机制)。
- 不确认模式,acknowledge="none" 不使用确认机制,只要消息发送完成会立即在队列移除,无论客户端异常还是断开,只要发送完就移除,不会重发。
在<rabbit:listener-container>中配置属性acknowledge=”manual”表示手动确认, NONE—不确认, AUTO—自动确认。