在mq消息传递时,为了避免消息丢失,mq引入了消息确认机制
mq是一种生产者和消费者模式,中间以mq为中介进行消息分发,将生产者与消费者隔离,生产者不必关心消息何时被消费,消费者也不关心消息如何生产,使得两者不必同步处理。
因此基于这种模式,可能会产生消息丢失问题,如何确保消费成功发送。以及消息成功接收,需要保证:
1、生产者的确认模式
2、消息和队列的持久化
3、消费者的确认模式
一、发布者确认模式
1、单个确认模式
生产者每发送一次消息,就会等待一次消息确认,是一种同步等待的方式,效率最低
public class ConfirmServer {
static Channel init() throws IOException, TimeoutException {
ConnectionFactory factory = ConnectionUtil.getFactory(); //获取一个连接工厂
Connection connection = factory.newConnection(); //创建连接
Channel channel = connection.createChannel(); //信道
channel.confirmSelect(); //开启确认模式
return channel;
}
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Channel channel = init();
long l = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
channel.basicPublish("","queue",null,"hello".getBytes());
boolean flag = channel.waitForConfirms();//等待确认
}
System.out.println("耗时: " + (System.currentTimeMillis() - l));
}
}
2、批量确认模式(用一个计数器,到一定数量确认一次)
public class ConfirmServer {
static Channel init() throws IOException, TimeoutException {
ConnectionFactory factory = ConnectionUtil.getFactory(); //获取一个连接工厂
Connection connection = factory.newConnection(); //创建连接
Channel channel = connection.createChannel(); //信道
channel.confirmSelect(); //开启确认模式
return channel;
}
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Channel channel = init();
long l = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 100; i++) {
channel.basicPublish("","queue",null,"hello".getBytes());
if(++count == 25){
boolean flag = channel.waitForConfirms();//等待确认
count = 0;
}
}
System.out.println("耗时: " + (System.currentTimeMillis() - l));
}
}
但是这个方式有一个缺陷,即这一批有一个消息确认失败,mq会丢弃整批的消息,也没办法知道是哪些消息接收失败
3、采用异步回调
public class ConfirmServer {
static Channel init() throws IOException, TimeoutException {
ConnectionFactory factory = ConnectionUtil.getFactory(); //获取一个连接工厂
Connection connection = factory.newConnection(); //创建连接
Channel channel = connection.createChannel(); //信道
channel.confirmSelect(); //开启确认模式
channel.addConfirmListener((deliveryTag, multiple) -> {
/**
* deliveryTag 消息标识
* multiple 为false是单个确认,为true是小于该tag的批量确认
*/
System.out.println("接收成功回调");
System.out.println("deliveryTag" + deliveryTag + "multiple:" + multiple);
},(deliveryTag, multiple) -> {
System.out.println("接收失败回调");
});
return channel;
}
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Channel channel = init();
long l = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 100; i++) {
channel.basicPublish("","queue",null,"hello".getBytes());
}
System.out.println("耗时: " + (System.currentTimeMillis() - l));
}
}
可以看到,这是一种异步回调的方式,并且效率也是最高的,既可以单个确认,也可以批量的确认,当消息确认失败,也会调用失败的回调,返回失败的tag进行重发操作。
二、消费者确认模式
消费者这边分为自动和手动应答方式,自动应答在消费消息时设置true即可
public class Customer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = ConnectionUtil.getFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
while (true){
//false,关闭自动应答
channel.basicConsume("queue",false,(consumerTag, message) -> {
System.out.println("成功接收到消息[" + consumerTag + "]:" + new String(message.getBody()));
System.out.println(message.getEnvelope().getDeliveryTag());
/**
* 消息确认
* deliveryTag 应答的标识
* multiple 是否批量
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(),true);
/**
* 消息拒绝
* deliveryTag
* multiple 批量
* requeue 是否重新入队,慎用(拒绝后,入队反复操作,可能导致消息积压)
*/
// channel.basicNack(deliveryTag,multiple,requeue);
/**
* 消息拒绝
* deliveryTag
* requeue
*
*/
// channel.basicReject(deliveryTag,requeue);
},(consumerTag -> {
System.out.println(consumerTag + "接收失败");
}));
}
}
}