【rabbitMQ】02 如何保证消息不丢失(附消息为什么会丢失)

  • 生产者:消息从生产者生产发送给Broker
  • 消息队列:消息在Broker存放,如果是镜像复制集群,消息将被复制到其他副本上
  • 消费者:消费者从Broker上拉取信息,经过网络传输发送到Consumer

从上述通信过程可以分析,可能丢失消息一共有3种情况
在这里插入图片描述

如何确保消息不丢失

如何保证消息不丢失呢?如下图的解决方案即可
在这里插入图片描述

1. 消息在传入Broker过程中丢失

外界环境问题导致:发生网络丢包、网络故障等造成RabbitMQ Server端收不到消息,因为生产环境的网络是很复杂的,网络抖动,丢包现象很常见

解决方案 1:事务功能

RabbitMQ中与事务机制有关的方法有三个:txSelect(), txCommit()以及txRollback(), txSelect用于将当前channel设置成transaction模式,txCommit用于提交事务,txRollback用于回滚事务

// 开启事务
channel.txSelect
try {
    // 这里发送消息
} catch (Exception e) {
    channel.txRollback

    // 这里再次重发这条消息
}

// 提交事务
channel.txCommit

缺点:
因为事务,吞吐量会下来,因为太耗性能。
解决方案 2:confirm功能

将信道设置成 confirm 模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的 ID 。一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。如果 RabbitMQ 发生内部错误从而导致消息丢失,会发送一条 nack(not acknowledged,未确认)消息。

Confirm的三种实现方式:

方式一:channel.waitForConfirms()普通发送方确认模式;

方式二:channel.waitForConfirmsOrDie()批量确认模式;

方式三:channel.addConfirmListener()异步监听发送方确认模式;

方式一:普通Confirm模式

 private static final String QUEUE_NAME="test_simple_queue";


Connection connection= ConnectionUtils.getConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 开启发送方确认模式
channel.confirmSelect();
String message = String.format("时间 => %s", new Date().getTime());
channel.basicPublish("", config.QUEUE_NAME, null, message.getBytes("UTF-8"));
if (channel.waitForConfirms()) {
	System.out.println("消息发送成功" );
}

方式二:channel.waitForConfirmsOrDie()批量确认模式;

 private static final String QUEUE_NAME="test_simple_queue";


Connection connection= ConnectionUtils.getConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 开启发送方确认模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
	String message = String.format("时间 => %s", new Date().getTime());
	channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
}
channel.waitForConfirmsOrDie(); //直到所有信息都发布,只要有一个未确认就会IOException
System.out.println("全部执行完成");

方式三:channel.addConfirmListener()异步监听发送方确认模式;

private static final String QUEUE_NAME="test_simple_queue";


Connection connection= ConnectionUtils.getConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(config.QueueName, false, false, false, null);
// 开启发送方确认模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
	String message = String.format("时间 => %s", new Date().getTime());
	channel.basicPublish("", config.QueueName, null, message.getBytes("UTF-8"));
}
//异步监听确认和未确认的消息
channel.addConfirmListener(new ConfirmListener() {
	@Override
	public void handleNack(long deliveryTag, boolean multiple) throws IOException {
		System.out.println("未确认消息,标识:" + deliveryTag);
	}
	@Override
	public void handleAck(long deliveryTag, boolean multiple) throws IOException {
		System.out.println(String.format("已确认消息,标识:%d,多个消息:%b", deliveryTag, multiple));
	}
});

2. 消息在RabbitMQ中暂存内存,未消费就挂了丢失

解决方案 1:持久化
如何持久化?要想消息持久化,前提就是队列、Exchange都持久化
生产者,持久化交换机
在这里插入图片描述
消费者,持久化队列
在这里插入图片描述
这样rabbitMQ挂掉之后,交换机和队列以及消息就不会丢失了。

3.消费者消费到消息,还未处理就挂了

消息一旦被消费者接收,队列中的消息就会被删除,RabbitMQ是如何知道消息被接受的呢?

解决方案 :ACK机制

RabbitMQ有一个ACK机制,当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接受。

不过这种回执ACK分为两种情况

  • 手动ACK:消息一旦被接受,消费者自动发送ACK
		channel.basicAck(envelope.getDeliveryTag(),false);// 手动发送ack
		boolean autoAck=false;// 关闭自动应答
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);// 开启监听队列
  • 自动ACK:消息接收后,不会发送ACK,需要手动调用
		boolean autoAck=true;// 开启自动应答
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);// 开启监听队列

我觉得手动ACK要好,因为,如果消费者领取消息,就自动回复了ACK,还没有执行操作就挂掉了,或者抛出异常,或者消息消费失败,RabbitMQ都无从得知,这样消息就丢失了,而手动ACK则可以让用户控制什么时候返回ACK,可以在确定消息消费成功之后,再调用发送ACK的方法。

使用场景

  • 如果消息不太重要,丢失也没有什么影响,就可以使用自动ACK
  • 如果消息非常重要,不容有失,那么最好在消费完成后手动ACK,否则接收到消息就自动ACK,RabbitMQ就会把消息从队列中删除造成信息丢失。

注意:
无论你选择手动ACK还是自动ACK,必须进行ACK回复,不然就会进行数据重发。

猜你喜欢

转载自blog.csdn.net/yujing1314/article/details/107468934