发布确认模式原理
生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,rabbitmq 就会发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,rabbitmq 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号;
此外 rabbitmq 也可以设置 basicack 的 multiple 域,表示到这个序列号之前的所有消息都已经得到了处理。 confirm 模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等待信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack 消 息,生产者应用程序同样可以在回调方法中处理该 nack 消息
开启发布确认模式
发布确认默认是没有开启的,如果要开启需要调用方法 confirmSelect,每当你要想使用发布 确认,都需要在 channel 上调用该方法
Connection connection = factory.newConnection();
channel.confirmSelect();
对比单个、批量和异步确认发布性能
1、单个确认发布
一种简单的确认方式,它是一种同步确认发布的方式,发布一个消息之后只有等待它被确认发布,后续的消息才能继续发布,waitForConfirmsOrDie(long)这个方法只有在消息被确认的时候才返回,如果在指定时间范围内这个消息没有被确认那么它将抛出异常。
这种确认方式有一个最大的缺点就是:发布速度特别慢,因为如果没有确认发布的消息就会阻塞所有后续消息的发布,这种方式最多提供每秒不超过数百条发布消息的吞吐量。
import com.rabbitmq.client.Channel;
import com.yc.rabbitmq.util.RabbitMqUtils;
import java.io.IOException;
import java.util.UUID;
/**
* 单个发布确认模式
* @author YC
*/
public class ReleaseConfirmation01 {
public static void main(String[] args) throws Exception {
//使用随机串作为队列名称
String queueName = UUID.randomUUID().toString();
Channel channel = RabbitMqUtils.getChannel();
//创建队列
channel.queueDeclare(queueName, false, false, false, null);
//开启发布确认
channel.confirmSelect();
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
String msg = "消息" + i;
channel.basicPublish("", queueName,null, msg.getBytes());
boolean flag = channel.waitForConfirms();
if(flag){
System.out.println(msg+"发布成功");
}
}
long end = System.currentTimeMillis();
System.out.println("发送100条消息使用时间:"+(end-start));
}
}
运行结果:
发送100条消息使用时间:7923
2、批量确认发布
单个确认发布的方式非常慢,与单个等待确认消息相比,先发布一批消息然后一起确认可以极大地提高吞吐量,当然这种方式的缺点就是:当发生故障导致发布出现问题时,不知道是哪个消息出现 问题了,我们必须将整个批处理保存在内存中,以记录重要的信息而后重新发布消息。当然这种方案仍然是同步的,也一样阻塞消息的发布
import com.rabbitmq.client.Channel;
import com.yc.rabbitmq.util.RabbitMqUtils;
import java.util.UUID;
/**
* 批量发布确认模式
* @author YC
*/
public class ReleaseConfirmation02 {
public static void main(String[] args) throws Exception {
//使用随机串作为队列名称
String queueName = UUID.randomUUID().toString();
Channel channel = RabbitMqUtils.getChannel();
//创建队列
channel.queueDeclare(queueName, false, false, false, null);
//开启发布确认
channel.confirmSelect();
long start = System.currentTimeMillis();
for (int i = 1; i <= 100; i++) {
String msg = "消息" + i;
channel.basicPublish("", queueName,null, msg.getBytes());
//每发送10条消息进行一次确认
if(i % 10 == 0){
int prevIndex = i - 9 ;
String tempMsg = "消息" + prevIndex;
boolean flag = channel.waitForConfirms();
if(flag){
System.out.println("("+tempMsg+"-"+msg+")"+"发布成功");
}
}
}
long end = System.currentTimeMillis();
System.out.println("发送100条消息使用时间:"+(end-start));
}
}
运行结果:
发送100条消息使用时间:923
3、异步确认发布
异步确认虽然编程逻辑比上两个要复杂,但是性价比最高,无论是可靠性还是效率都没得说, 他是利用回调函数来达到消息可靠性传递的,不需要在发送消息的逻辑中等待确认,发送消息的逻辑只需要发送消息,不需要知道是否发布成功,是否发布成功通过开启监听器来判断,发布失败可以在监听其中重新发布
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmCallback;
import com.yc.rabbitmq.util.RabbitMqUtils;
import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
/**
* 异步发布确认模式
* @author YC
*/
public class ReleaseConfirmation03 {
public static void main(String[] args) throws Exception {
//使用随机串作为队列名称
String queueName = UUID.randomUUID().toString();
Channel channel = RabbitMqUtils.getChannel();
//创建队列
channel.queueDeclare(queueName, false, false, false, null);
//开启发布确认
channel.confirmSelect();
/*
存储未确认的消息
线程安全有序的一个哈希表,适用于高并发的情况
1.轻松的将序号与消息进行关联
2.轻松批量删除条目 只要给到序列号
3.支持并发访问
*/
ConcurrentSkipListMap<Long, String> map = new ConcurrentSkipListMap<>();
/*
监听器的回调,确认收到消息的一个回调
1.消息序列号
2.true 可以确认小于等于当前序列号的消息,false 确认当前序列号消息,
如果是false,每次只能确认或否认一条消息;
如果是true,所有序列号不大于sequencenumber的消息都会被确认
*/
ConfirmCallback ackCallback = (sequenceNumber, multiple) -> {
System.out.println("发布成功的消息编号:"+sequenceNumber);
if(multiple){
//返回的是小于等于当前序列号的未确认消息 是一个 map
ConcurrentNavigableMap<Long, String> confirmed = map.headMap(sequenceNumber, true);
//清除该部分未确认消息
confirmed.clear();
}else{
map.remove(sequenceNumber);
}
};
//发布失败的回调
ConfirmCallback nackCallback = (sequenceNumber, multiple) -> {
System.out.println(map.get(sequenceNumber)+"发布失败");
};
channel.addConfirmListener(ackCallback, nackCallback );
long start = System.currentTimeMillis();
for (int i = 1; i <= 100; i++) {
String msg = "消息" + i;
/*
channel.getNextPublishSeqNo()获取下一个消息的序列号
通过序列号与消息体进行一个关联
全部都是未确认的消息体
*/
map.put(channel.getNextPublishSeqNo(), msg);
channel.basicPublish("", queueName,null, msg.getBytes());
}
long end = System.currentTimeMillis();
System.out.println("发送100条消息使用时间:"+(end-start));
}
}
运行结果:
发送100条消息使用时间:8
三种方式对比
单独发布消息:同步等待确认,简单,但吞吐量非常有限。
批量发布消息:批量同步等待确认,简单,合理的吞吐量,一旦出现问题但很难推断出是哪条消息出现问题。
异步处理:最佳性能和资源使用,在出现错误的情况下可以很好地控制,实现稍微难些
转载于:https://www.ycblog.top/article?articleId=167&commentPageNum=1