RabbitMQ消息确认机制(事务+confirm)

本文详细介绍了RabbitMQ中确保消息传递可靠性的事务和Confirm机制。事务可能导致性能下降,而Confirm模式允许异步确认,提高吞吐量。在Confirm模式下,生产者可以通过设置channel为confirm模式并监听ConfirmListener来确认消息是否已成功投递至队列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可参考https://blog.youkuaiyun.com/hzw19920329/article/details/54340711

在rabbitmq中,可以通过持久化数据,解决服务器异常之后数据丢失问题。
消费者也可以通过手动应答来告知消息队列有没有收到消息。

但是生产者将消息发送出去之后,消息到底有没有到达rabbitmq服务器,默认情况下是不知道的。

两种方式:
AMQP实现了事务机制
confirm

事务机制,针对生产者,消费者无需修改

public class Provider {
	private static final String QUEUE_NAME="queue_tx";
	public static void main(String[] args) throws Exception{
		Connection conn = RabbitMqUtil.getConnection();
		Channel channel = conn.createChannel();
		try {
			//开启事务
			channel.txSelect();
			channel.basicPublish("", QUEUE_NAME, null, "message".getBytes());
			//提交
			channel.txCommit();
		} catch (Exception e) {
			//回滚
			System.out.println("异常,数据回滚");
			channel.txRollback();
		}
		finally {
			channel.close();
			conn.close();
		}
	}
}

这种方式会导致发送消息的同时还夹杂着其他的请求,降低了吞吐量。

confirm模式
生产者将channel设置成confirm模式,一旦channel进入confirm模式,所有在该channel上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitmq就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,rabbitmq回传给生产者的确认消息中的deliver-tag包含了确认消息的序列号,此外rabbitmq也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm模式可以是异步的。

开启confirm模式:channel.confirmSelect()
当生产者发送消息给消息队列之后,可以通过channel.waitForConfirm方法来确认消息是否已经送达成功,返回true说明送达成功,false则失败。
生产者代码

public class Provider {
	private static final String QUEUE_NAME = "confirm_queue";
	public static void main(String[] args) throws Exception {
		Connection conn = RabbitMqUtil.getConnection();
		Channel channel = conn.createChannel();
		channel.queueDeclare(QUEUE_NAME,false,false,false,null);
		//开启confirm模式
		channel.confirmSelect();
		//发送消息
		for(int i=0; i<10; i++) {
			channel.basicPublish("", QUEUE_NAME, null, ("first confirm message"+i).getBytes());
		}
		
		//判断消息是否发送成功
		if(channel.waitForConfirms()) {
			System.out.println("发送消息成功");
		}
		channel.close();
		conn.close();
	}
}

注意,如果一个队列已经被设置为事务模式,那么就不能再将其设置成confirm模式了。

异步模式
Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Channel发出的消息序列号),我们需要为每一个Channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次handleAck方法,unconfirm集合删掉相应的一条(multiple=false)或多条(multiple=true)记录,从程序运行效率上来说,unconfirm集合最好采用有序集合SortedSet存储。

public class Provider_asyn {
	private static final String QUEUE_NAME = "confirm_queue_asyn";
	public static void main(String[] args) throws Exception {
		Connection conn = RabbitMqUtil.getConnection();
		Channel channel = conn.createChannel();
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		
		channel.confirmSelect();
		//存放发送消息的消息编号
		final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
		
		//添加channel的监听
		channel.addConfirmListener(new ConfirmListener() {
			//发送不成功执行,deliveryTag为消息的编号,multiple表示是否是多条,true表示批量发送确认消息,表示在此消息编号之前的所有消息都已经接收到了
			//通过该方法可以用于实现失败消息重发,保持消息接口幂等性。根据具体的业务逻辑实现。
			public void handleNack(long deliveryTag, boolean multiple) throws IOException {
				
				if(multiple) {
					System.out.println("发送失败,multiple:"+multiple);
					confirmSet.headSet(deliveryTag+1).clear();
				}
				else {
					confirmSet.remove(deliveryTag);
				}
			}
			//发送成功执行,deliveryTag为消息的编号,multiple表示是否是多条
			public void handleAck(long deliveryTag, boolean multiple) throws IOException {
				// TODO Auto-generated method stub
				if(multiple) {
					System.out.println("发送成功,multiple:"+multiple);
					confirmSet.headSet(deliveryTag+1).clear();
				}
				else {
					confirmSet.remove(deliveryTag);
				}
			}
		});
		while(true) {
			long seqNo = channel.getNextPublishSeqNo();
			channel.basicPublish("", QUEUE_NAME, null, "异步消息发送".getBytes());
			confirmSet.add(seqNo);
		}
		
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值