只想学习补充自己的呆头鹅
自述
该篇文章属于额外篇,主要描述RabbitMQ提供的消息确认,这也是倒数第二篇文章了,最后一篇文章将对RabbitMQ进行总结,敬请期待吧!
发布确认
发布者确认是实现可靠发布的RabbiMQ扩展,当在通道上启用发布者确认时,客户端发布的消息由代理异步确认,这也就意味着它们已经在服务器端得到处理。
//开启消息确认
channel.confirmSelect();
//判断是否开启消息确认
channel.waitForConfirms();
单独发布
单独发布和往常我们的普通队列类型,只不过通过通道设置消息确认,实现简单,但会影响吞吐量,每次发布消息都会进行消息确认,会降低发布速度。
ConfirmSend.java
/**
* confirm 生产者
* 单条
*/
public class ConfirmSend {
//exchangename
private static final String EX_CHANGE_NAME = "ex_yunk";
public static void main(String[] args) {
try {
//1.connection
Connection connection = ConnectionUtil.getConnection();
//2.channel
Channel channel = connection.createChannel();
//3.create exchange
channel.exchangeDeclare(EX_CHANGE_NAME, "fanout");
//4.msg confirm --开启消息确认
channel.confirmSelect();
//5.msg
String msg = "msg success";
//增量
int size = 0;
//begin
long begin = System.currentTimeMillis();
//6.publish
while (size < 5000) {
size++;
//发送
channel.basicPublish(EX_CHANGE_NAME, "", null, msg.getBytes());
//5秒内确认
channel.waitForConfirmsOrDie(5000);
}
System.out.println("消息发送成功" + (System.currentTimeMillis() - begin));
//close
ConnectionUtil.closeConnection(connection, channel);
} catch (Exception e) {
e.printStackTrace();
}
}
}
批量发布
批量发布见名知意,批量的确认消息,提高吞吐量,但唯一不足点:当批量确认时如遇到问题也无法定位到问题所在。
ConfirmSend2.java
/**
* confirm 生产者
* 批量
*/
public class ConfirmSend2 {
//exchangename
private static final String EX_CHANGE_NAME = "ex_yunk";
public static void main(String[] args) {
try {
//1.connection
Connection connection = ConnectionUtil.getConnection();
//2.channel
Channel channel = connection.createChannel();
//3.create exchange
channel.exchangeDeclare(EX_CHANGE_NAME, "fanout");
//4.msg confirm --开启消息确认
channel.confirmSelect();
//5.msg
String msg = "msg success";
//消息数量
int outmsgcount = 0;
//100/1 每100条发一次
int batchSize = 100;
//增量
int size = 0;
//begin
long begin = System.currentTimeMillis();
//6.publish
while (size < 5000) {
size++;
outmsgcount++;
channel.basicPublish(EX_CHANGE_NAME, "", null, msg.getBytes());
//批量消息确认
if (outmsgcount == batchSize) {
channel.waitForConfirmsOrDie(5000);
outmsgcount = 0;
}
}
//谨防疏漏的消息确认
if (outmsgcount > 0) {
channel.waitForConfirmsOrDie(5_000);
}
System.out.println("消息发送成功" + (System.currentTimeMillis() - begin));
//close
ConnectionUtil.closeConnection(connection, channel);
} catch (Exception e) {
e.printStackTrace();
}
}
}
异步处理发布者
异步处理发布者确认是有点麻烦的,具体步骤如下:①提供一种将发布序列号与消息相关联的方法。②在通道上注册监听器,方便对消息确认或nack-ed进行相对应的处理,注: 无论是消息确认后或nack-ed,都需要将相关条目消息进行删除。
ConfirmSend3.java
/**
* confirm 生产者
* 异步 aysn
*/
public class ConfirmSend3 {
//exchangename
private static final String EX_CHANGE_NAME = "ex_yunk";
public static void main(String[] args) {
try {
//1.connection
Connection connection = ConnectionUtil.getConnection();
//2.channel
Channel channel = connection.createChannel();
//3.create exchange
channel.exchangeDeclare(EX_CHANGE_NAME, "fanout");
//4.msg confirm --开启消息确认
channel.confirmSelect();
//5.msg
String msg = "msg success";
//将序列号与消息绑定到一起
ConcurrentNavigableMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();
//asyntype
//methodone:消息确认回调
//methodtwo:消息未确认回调
//②不管是消息确认或nack,最终都要将映射的消息删除
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long sequence, boolean multiple) throws IOException {
if (multiple) {
//返回小于等于该序列号的消息--前提是inclusive参数需设为true
ConcurrentNavigableMap<Long, String> confirmed = outstandingConfirms.headMap(sequence, true);
//删除所有映射
confirmed.clear();
} else {
//删除指定的映射
outstandingConfirms.remove(sequence);
}
}
@Override
public void handleNack(long sequence, boolean multiple) throws IOException {
//获取指定key的消息
String msg = outstandingConfirms.get(sequence);
//执行重复操作
handleAck(sequence, multiple);
}
});
//begin
long begin = System.currentTimeMillis();
int size = 0;
while (size < 5000) {
size++;
//获取序列号
long nextPublishSeqNo = channel.getNextPublishSeqNo();
//①将序列号与消息进行绑定
outstandingConfirms.put(nextPublishSeqNo, msg);
//发送消息
channel.basicPublish(EX_CHANGE_NAME, "", null, msg.getBytes());
}
System.out.println("消息发送成功" + (System.currentTimeMillis() - begin));
//close
ConnectionUtil.closeConnection(connection, channel);
} catch (Exception e) {
e.printStackTrace();
}
}
}
消费者
普通消费者。
/**
* confirms 消费者
*/
public class ConfirmRecv {
//exchangename
private static final String EX_CHANGE_NAME = "ex_yunk";
public static void main(String[] args) {
try {
//1.connection
Connection connection = ConnectionUtil.getConnection();
//2.channel
Channel channel = connection.createChannel();
//3.create exchange
channel.exchangeDeclare(EX_CHANGE_NAME, "fanout");
//4.queue
String queue = channel.queueDeclare().getQueue();
//5.biding
channel.queueBind(queue, EX_CHANGE_NAME, "");
//6.callback
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("获取的消息=====" + new String(delivery.getBody()));
}
};
//7.consumer ---autoack
channel.basicConsume(queue, true, deliverCallback, cancel -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
性能测试
条数 | 时间(ms) | 类型 |
---|---|---|
5000 | 2193 | 单条 |
5000 | 653 | 批量 |
5000 | 760 | 异步 |
总结
单独发布:同步等待消息确认,实现简单,但吞吐量下降(tps)。
批量发布:同步等待消息确认,批量消息确认,吞吐量提升,但批量消息确认时如出现问题无法定位问题关键所在。
异步发布:异步等待消息确认,最佳性能和资源使用,错误情况下的良好控制,但实现复杂,难理解。
Ending
good night!