发送方确认机制:
发送方无法确认消息是否准确到达RabbitMQ,随后我们了解到事务机制可以解决这个问题,但是采用事务机制会严重降低RabbitMQ的消息吞吐量,这里引入一种更轻量级的方式---发行方确认(publisher confirm)机制
信道设置成confirm(确认)模式,一旦信道进入confirm模式,所有在改信道上发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所匹配的队列之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一ID)
消息和队列是持久化的,那么确认会在消息写入磁盘之后发出。RabbitMQ回传给生产者的确认消息中的deliveryTag包含了确认消息的 序号,此外RabbitMQ也可以设置channel.basicAck方法的multiple参数,表示到这个序号之前的所有消息都已经得到了处理。
发送方确认机制最大的好处在于它是异步的,一旦发布一条消息生产者程序可以在等待信道返回确认的同时继续发布下一条消息,当消息得到最终确认之后,生产者程序可以通过回调 方法来处理该确认消息,
如果RabbitMQ因自身原因导致消息丢失,就会发送一条nack命令。 生产者通过channel.confirmSelect()设置confirm模式,所有被发送的后续消息都会被ack或者nack一次,不会出现消息即被ack又被nack的情况。
package com.song.songvue.config.message;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 生产者客户端代码
*/
public class RabbitProducer {
private static final String IP_ADDRESS = "172.16.200.239";
private static final int PORT = 5672;
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(IP_ADDRESS);
factory.setPort(PORT);
factory.setUsername("root");
factory.setPassword("123456");
// 创建连接
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明交换器同时指定了备份交换器
Map<String, Object> params = new HashMap<>();
params.put("alternate-exchange", "myAe");
channel.exchangeDeclare("song_exchange", "direct", true, false, false, params);
// 备份交换器
channel.exchangeDeclare("myAe", "fanout", true, false, false, null);
// 备份队列
channel.queueDeclare("back_queue", true, false, false, null);
channel.queueBind("back_queue", "myAe", "");
// 队列
channel.queueDeclare("song", true, false, false, null);
// 绑定队列
channel.queueBind("song", "song_exchange", "songKey");
try {
// 将信道设置成 publisher confirm模式
channel.confirmSelect();
// 发布消息
channel.basicPublish("song_exchange", "songKey", MessageProperties.PERSISTENT_TEXT_PLAIN,
"publisher confirm test message".getBytes("UTF-8"));
if (!channel.waitForConfirms()) {
System.out.println("send message failed");
}
/*
如果要发送多条消息只需要将channel.basicPublish和channel.waitForConfirms()包裹在循环内,
不需要将confirmSelect放在循环内。
如果没有开启publisher confirm模式, 调用任何waitForConfirms方法都会报java.lang.IllegalStateException
*/
// 发布一个无法路由匹配的消息, 会被存储到备份队列中
channel.basicPublish("song_exchange", "backKey", MessageProperties.PERSISTENT_TEXT_PLAIN,
" test back-queue message".getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
// 关闭资源
channel.close();
connection.close();
}
}
注意点:
事务机制和publisher confirm机制两者是互斥的,不能共存。如果企图将已开启的事务模式再设置为publisher confirm RabbitMQ会报错。事务机制和publisher confirm机制确保的消息能够正确的到达RabbitMQ, 这里的发送至RabbitMQ是指消息被正确地发送至RabbitMQ的交换器,如果交换器没有匹配的队列,消息也会被丢失. 所以在使用这两种机制时要配合mandatory或者备份交换器
publisher confirm的优势不在于需要同步确认。
批量confirm方法:
每发送一批消息后,调用channel.waitForConfirms(),等待服务器的确认返回。
异步confirm方法:
提供一个回调方法,服务端确认了一条或者多条消息之后客户端会回调这个处理方法。
批量confirm方法,客户端程序需要定期或者定量, 来调用channel.waitForConfirms(),如果在返回时出现了Basic.Nack或者超时情况客户端需要将这一批消息全部重发,这会带来明显的重复消息数量。
package com.song.songvue.config.message;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
/**
* 生产者客户端代码
*/
public class RabbitProducer {
private static final String IP_ADDRESS = "172.16.200.239";
private static final int PORT = 5672;
private static final int BATCH_COUNT = 20;
private static Connection connection;
private static Channel channel;
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(IP_ADDRESS);
factory.setPort(PORT);
factory.setUsername("root");
factory.setPassword("123456");
// 创建连接
connection = factory.newConnection();
// 创建信道
channel = connection.createChannel();
// 声明交换器同时指定了备份交换器
Map<String, Object> params = new HashMap<>();
params.put("alternate-exchange", "myAe");
channel.exchangeDeclare("song_exchange", "direct", true, false, false, params);
// 备份交换器
channel.exchangeDeclare("myAe", "fanout", true, false, false, null);
// 备份队列
channel.queueDeclare("back_queue", true, false, false, null);
channel.queueBind("back_queue", "myAe", "");
// 队列
channel.queueDeclare("song", true, false, false, null);
// 绑定队列
channel.queueBind("song", "song_exchange", "songKey");
channel.queuePurge("song");
try {
channel.confirmSelect();
batchSendMessage();
} catch (Exception e) {
e.printStackTrace();
}
// 关闭资源
channel.close();
connection.close();
}
private static void batchSendMessage() throws IOException {
int msgCount = 0;
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(BATCH_COUNT);
while (true) {
String message = "batch message test";
channel.basicPublish("song_exchange", "songKey", MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes("UTF-8"));
// 将发送的消息存储
blockingQueue.offer(message);
if (++msgCount >= BATCH_COUNT) {
msgCount = 0;
try {
if (channel.waitForConfirms()) {
// 将缓存消息清空
blockingQueue.clear();
}
// 重新发送缓存消息
} catch (Exception e) {
e.printStackTrace();
// 重新发送缓存消息
}
break;
}
}
}
}