confirm确认模式
0.概念
虽然AMQP提供的事务机制可以保证消息的准确达到,但是采用AMQP事务机制会降低RabbitMQ的吞吐量,因此我们为了性能上的要求,可以采用第二种解决方案:通过使用Confirm模式来保证消息的准确性。
注意:两种事物控制形式不能同时开启。
1.创建Maven项目
项目目录
2.导入rabbitmq依赖
<!--rabbitmq依赖-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.3</version>
</dependency>
3.同步confirm
实现方式:
- 普通confirm模式:每发送一条消息后调用waitForConfirms()方法,等待服务器端confirm。实际上是一种串行confirm。
- 批量confirm模式:每发送一批消息后,调用waitForConfirmsOrDie()方法,等待服务端confirm。
3.1 确认-同步-生产者Send
/**
* 确认-同步-生产者
*/
public class Send {
// 定义队列名称
private final static String QUEUE_NAME = "sync";
public static void main(String[] argv) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 连接工厂配置
factory.setHost("192.168.68.152");
factory.setPort(5672);
factory.setUsername("lwx");
factory.setPassword("lwx");
factory.setVirtualHost("/lwx");
// 创建连接
Connection connection = null;
// 创建信道
Channel channel = null;
try {
// 创建连接
connection = factory.newConnection();
// 创建信道
channel = connection.createChannel();
/**
* 声明队列
* 第一个参数queue:队列名称
* 第二个参数durable:是否持久化
* 第三个参数Exclusive:排他队列,如果一个队列被声明为排他队列,
* 该队列仅对首次声明他的连接可见,并在连接断开时自动删除。
* 注意:
* 1.排他队列是基于连接可见的,同一连接的不同通道是可以同时
* 访问同一个连接创建的排他队列的。
* 2.”首次“,如果一个连接已经声明了一个排他队列,其它连接是
* 不允许建立同名的排他队列的,这与普通队列不同。
* 3.即使该队列是持久化的,一旦连接关闭或者客户端退出,该排
* 他队列都会被自动删除的。这种队列适用于只限于一个客户端发
* 送读取消息的应用场景。
* 第四个参数Auto-delete:自动删除,如果该队列没有任何订阅的消费者
* 的话,该队列会被自动删除。这种队列适用于临时队列。
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 启动确认模式
channel.confirmSelect();
String message = "Hello World!";
// 发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
/*// 普通确认,只能单条确认
if (channel.waitForConfirms()) {
System.out.println("确认成功!");
}*/
// 批量确认,只要有一条确认不成功直接抛异常
channel.waitForConfirmsOrDie();
System.out.println("[x]Sent'" + message + "'");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (channel != null) {
channel.close();
}
if (connection != null) {
connection.close();
}
}
}
}
3.2 确认-同步-消费者Recv
/**
* 确认-同步-消费者
*/
public class Recv {
// 定义队列名称
private final static String QUEUE_NAME = "sync";
public static void main(String[] argv) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 连接工厂配置
factory.setHost("192.168.68.152");
factory.setPort(5672);
factory.setUsername("lwx");
factory.setPassword("lwx");
factory.setVirtualHost("/lwx");
// 创建连接
Connection connection = factory.newConnection(); //Connection间接继承了AutoCloseable接口,可以不用手动关闭连接
// 创建信道
Channel channel = connection.createChannel();
// 绑定队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
System.out.println("[*] Waiting for messages. To exit press CTRL+C");
// 打印消息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("[x] Received '" + message + "'");
};
/**
* 消费消息
* 1.队列名称
* 2.自动确认
*/
channel.basicConsume(QUEUE_NAME,true,deliverCallback,consumerTag -> {
});
}
}
3.3 缺点
以上代码可以看出,使用同步的方式需要等待所有的消息发送成功以后才会执行后面的代码,只要有一个消息未被确认就会抛出IO异常。
4.异步confirm
实现方式:
- 异步confirm模式:提供一个回调方法,服务端confirm了一条或者多条消息后Client端会回调这个方法。
4.1 确认-异步-生产者Send
/**
* 确认-异步-生产者
*/
public class Send {
// 定义队列名称
private final static String QUEUE_NAME = "async";
public static void main(String[] argv) {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 连接工厂配置
factory.setHost("192.168.68.152");
factory.setPort(5672);
factory.setUsername("lwx");
factory.setPassword("lwx");
factory.setVirtualHost("/lwx");
// 创建连接
Connection connection = null;
// 创建信道
Channel channel = null;
try {
// 维护信息发送回执deliveryTag
final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
// 创建连接
connection = factory.newConnection();
// 创建信道
channel = connection.createChannel();
// 开启confirm确认模式
channel.confirmSelect();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 添加channel监听
channel.addConfirmListener(new ConfirmListener() {
// 已确认
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// multiple = true 已确认多条 false 已确认单条
if (multiple){
System.out.println("handleAck--success-->multiple" + deliveryTag);
// 清除前 deliveryTag 项标识id
confirmSet.headSet(deliveryTag + 1L).clear();
} else {
System.out.println("handleAck--success-->single" + deliveryTag);
confirmSet.remove(deliveryTag);
}
}
// 未处理
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
//需要自己写
// multiple = true 已确认多条 false 已确认单条
if (multiple){
System.out.println("handleAck--success-->multiple" + deliveryTag);
// 清除前 deliveryTag 项标识id
confirmSet.headSet(deliveryTag + 1L).clear();
} else {
System.out.println("handleAck--success-->single" + deliveryTag);
confirmSet.remove(deliveryTag);
}
}
});
// 循环发送消息演示消息确认
while (true) {
// 创建消息
String message = "Hello World!";
// 获取unconfirm的消息序号deliveryTag
long seqNo = channel.getNextPublishSeqNo();
channel.basicPublish("",QUEUE_NAME,null,message.getBytes("UTF-8"));
// 将消息序号deliveryTag添加至SortedSet
confirmSet.add(seqNo);
}
} catch (IOException | TimeoutException e) {
e.printStackTrace();
} finally {
try {
// 关闭通道
if (channel != null && channel.isOpen()) {
channel.close();
}
// 关闭连接
if (connection != null && connection.isOpen()) {
connection.close();
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
4.2 确认-异步-消费者Recv
/**
* 确认-异步-消费者
*/
public class Recv {
// 定义队列名称
private final static String QUEUE_NAME = "async";
public static void main(String[] argv) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 连接工厂配置
factory.setHost("192.168.68.152");
factory.setPort(5672);
factory.setUsername("lwx");
factory.setPassword("lwx");
factory.setVirtualHost("/lwx");
// 创建连接
Connection connection = factory.newConnection(); //Connection间接继承了AutoCloseable接口,可以不用手动关闭连接
// 创建信道
Channel channel = connection.createChannel();
// 绑定队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
System.out.println("[*] Waiting for messages. To exit press CTRL+C");
// 打印消息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("[x] Received '" + message + "'");
};
/**
* 消费消息
* 1.队列名称
* 2.自动确认
*/
channel.basicConsume(QUEUE_NAME,true,deliverCallback,consumerTag -> {
});
}
}