如何保证使用rabbitmq时,消息不丢失?
其实这个问题适用于任何mq,虽然不同的mq具体操作上有区别,但大体上需要从三个方面考虑:生产者发送、broker存储和消费者接收均保证消息可靠。
我们来看看rabbitmq是怎么做到这三点的。
一、生产者消息可靠
生产者保证消息可靠有两种方式,事务
和confirm机制
。
1.事务控制
rabbitmq允许通过事务的方式发送消息。
关键代码如下:
// == 将信道设置成事务模式
channel.txSelect
try {
// 这里发送消息
} catch (Exception e) {
// == 事务回滚
channel.txRollback
}
// == 提交事务
channel.txCommit
如果出现问题可以回滚事务,之后做重发之类的操作。
问题在于事务是同步的,后续的操作都会等待事务执行而阻塞在这里,影响性能。
2.confirm机制
单条confirm
// == 信道开启消息确认 channel.confirmSelect(); // 单条发送 channel.basicPublish("",QUEUE_NAME,null,message.getBytes()); if (channel.waitForConfirms()){ // 消息发送成功 }else { // 消息发送失败 }
每条数据通过
channel.waitForConfirms()
获取发送结果批量confirm
// == 信道开启消息确认 channel.confirmSelect(); // 批量发送 for (int i = 0; i < 10; i++) { channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); } //直到所有信息都发布,只要有一个未确认就会IOException channel.waitForConfirmsOrDie();
一个批次通过
channel.waitForConfirmsOrDie()
获取发送结果。
批次发送失败,需要整个批次重发;消费端需要做好重复消息的处理,一般在业务层面做好幂等。
异步confirm
前两种都是同步的确认,异步确认能将效率能最大化。channel.addConfirmListener(new ConfirmListener() { // == 消息失败处理 @Override public void handleNack(long deliveryTag, boolean multiple) throws IOException { //deliveryTag;唯一消息标签 //multiple:是否批量 System.err.println("-------no ack!-----------"); } // == 消息成功处理 @Override public void handleAck(long deliveryTag, boolean multiple) throws IOException { System.err.println("-------ack!-----------"); } });
无论成功还是失败,都会回调listener中的一个方法。
二、broker消息可靠
持久化
首先要开启持久化,分为队列持久化和消息持久化。
- 队列持久化:
在创建队列时将channel.queueDeclare()第二个参数改为true。 - 消息持久化:
在使用信道发送消息时channel.basicPublish()将第三个参数改为:MessageProperties.PERSISTENT_TEXT_PLAIN表示持久化消息。
同时开启以上两种持久化机制,这样消息到达broker时,rabbitmq会做落盘操作。
但rabbitmq的消息落盘也有很小的时间间隔,有可能刚写入系统缓存服务器就挂了。
因此还要开启rabbitmq的镜像集群,在写入主的同时也将数据同步到从服务。
三、消费者消息可靠
消息确认模式有:
- AcknowledgeMode.NONE:自动确认(默认)。
- AcknowledgeMode.AUTO:根据情况确认。
- AcknowledgeMode.MANUAL:手动确认。
手动ack
消费者成功接收消息时,默认会自动向broker发送ack,表示消息接收。
我们需要改为手动ack模式。
一张图总结
经过上述讨论,消息可靠性需要牺牲一定的性能,从而降低mq的吞吐量,因此需要在可靠性和吞吐量之间寻求一个平衡。
考虑一下:当前业务场景是否对消息的可靠性有这么高的要求,同时实时性也要保证呢?如果实时性要求不高,能否通过生产、消费两端核对的方式,达到可靠性要求呢?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。