1、简单队列(Simple Queue)
Simple队列是一个生产者对应一个消费者。
1.1 图例:
P:消息的生产者
C:消息的消费者
红色:队列
生产者将消息发送到队列,消费者从队列中获取消息。
1.2 pom.xml 依赖:
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.4.1</version>
</dependency>
</dependencies>
1.3 获取MQ连接
public static Connection getConnection() throws IOException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("guest");
factory.setPassword("guest");
return factory.newConnection();
}
1.4 生产者与消费者
1.4.1 生产者
public class Send {
public static final String QUEUE_NAME = "test_simple_queue";
public static void main(String[] args) throws IOException {
Connection conn = ConnectionUtils.getConnection();
//创建一个通道
Channel channel = conn.createChannel();
//声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String msg = "Hello Simple Queue !";
/*
* 向server发布一条消息
* 参数1:exchange名字,若为空则使用默认的exchange
* 参数2:routing key
* 参数3:其他的属性
* 参数4:消息体
* RabbitMQ默认有一个exchange,叫default exchange,它用一个空字符串表示,它是direct exchange类型,
* 任何发往这个exchange的消息都会被路由到routing key的名字对应的队列上,如果没有对应的队列,则消息会被丢弃
*/
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
System.out.println("------send msg :"+ msg);
channel.close();
conn.close();
}
1.4.2 消费者1
public class receive {
public static final String QUEUE_NAME = "test_simple_queue";
public static void main(String[] args) throws IOException, InterruptedException {
// 获取连接
Connection connection = ConnectionUtils.getConnection();
// 创建通道
Channel channel = connection.createChannel();
//定义消息队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
//监听队列
channel.basicConsume(QUEUE_NAME,true,consumer);
while (true){
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msgStr = new String(delivery.getBody());
System.out.println("------[Receive] msg :"+msgStr);
}
}
}
1.4.3消费者2(推荐使用)
public class Receive2 {
public static final String QUEUE_NAME = "test_simple_queue";
public static void main(String[] args) throws IOException, InterruptedException {
// 获取连接
Connection connection = ConnectionUtils.getConnection();
// 创建通道
Channel channel = connection.createChannel();
// 队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 定义一个消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body,"utf-8");
System.out.println("--------------------new API ----------------------");
System.out.println("------[Receive] msg :"+msg);
}
};
// 监听队列
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
2、工作队列(Work Queue)
**轮询分发 :**使用任务队列的优点之一就是可以轻易的并行工作。如果我们积压了好多工作,我们可以通过增加工作者(消费者)来解决这一问题,使得系统的伸缩性更加容易。在默认情况下,RabbitMQ将逐个发送消息到在序列中的下一个消费者(而不考虑每个任务的时长等等,且是提前一次性分配,并非一个一个分配)。平均每个消费者获得相同数量的消息。这种方式分发消息机制称为Round-Robin(轮询)。
**公平分发 :**虽然上面的分配法方式也还行,但是有个问题就是:比如:现在有2个消费者,所有的奇数的消息都是繁忙的,而偶数则是轻松的。按照轮询的方式,奇数的任务交给了第一个消费者,所以一直在忙个不停。偶数的任务交给另一个消费者,则立即完成任务,然后闲得不行。而RabbitMQ则是不了解这些的。这是因为当消息进入队列,RabbitMQ就会分派消息。它不看消费者为应答的数目,只是盲目的将消息发给轮询指定的消费者。
2.1 轮询分发
平均分配,每个消费者获取相同的消息数量。
2.1.1 生产者:
public class Send {
public static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException {
Connection conn = ConnectionUtils.getConnection();
Channel channel = conn.createChannel();
// 声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for (int i = 0; i < 30; i++) {
String msg="hello work queue"+i;
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
// 释放连接
channel.close();
conn.close();
}
}
2.1.2 消费者
消费者1:
public class Receive01 {
public static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException {
Connection conn = ConnectionUtils.getConnection();
Channel channel = conn.createChannel();
// 声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[1] Receive Msg : " + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done ");
}
}
};
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
消费者 2:
public class Receive02 {
public static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException {
Connection conn = ConnectionUtils.getConnection();
Channel channel = conn.createChannel();
// 声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[2] Receive Msg : " + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[2] done ");
}
}
};
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
2.2 公平分发
由于RabbitMQ消息队列并不知情消费者1、消费者2的接收能力,默认只好采用轮询分发的方式,所以出现上面的情况。
怎样才能做到按照每个消费者的能力分配消息呢?
联合使用 Qos 和 Acknowledge 就可以做到。
basicQos 方法设置了当前信道最大预获取(prefetch)消息数量为1。消息从队列异步推送给消费者,消费者的 ack 也是异步发送给队列,从队列的视角去看,总是会有一批消息已推送但尚未获得 ack 确认,Qos 的prefetchCount参数就是用来限制这批未确认消息数量的。设为1时,队列只有在收到消费者发回的上一条消息 ack 确认后,才会向该消费者发送下一条消息。prefetchCount 的默认值为0,即没有限制,队列会将所有消息尽快发给消费者
2.2.1 图例
2.2.2 生产者
public class Send {
public static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException {
Connection conn = ConnectionUtils.getConnection();
Channel channel = conn.createChannel();
// 声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for (int i = 0; i < 30; i++) {
String msg="hello work queue"+i;
/**
* 第一个参数为 exchange:交换机名称
* 第二个参数为 routingKey:路由键,
* 第三个参数为 props:消息属性集,包含14个属性成员,如持久化,优先级,过期时间等。
* 第四个参数为 body:消息体,需要发送的消息。
**/
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
// 释放连接
channel.close();
conn.close();
}
}
2.2.3 消费者
消费者1:消费者睡眠2秒
public class Receive01 {
public static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException {
Connection conn = ConnectionUtils.getConnection();
final Channel channel = conn.createChannel();
// 声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//同一时刻服务器只会发一条消息给消费者
//该消费者在接收到队列里的消息但没有返回确认结果之前,它不会将新的消息分发给它。
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[1] Receive Msg : " + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
/**
* 手动确认
* 第1个参数:通过发送Tag标识来确认消费的是消息队列中的哪个消息
* 第2个参数:是否开启多个消息同时确认,这里我们设置了每次只能消费一个消息,因此不需要开启
*/
channel.basicAck(envelope.getDeliveryTag(),false);
System.out.println("[1] done ");
}
}
};
//手动确认消息
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
**消费者2:**该消费者睡眠1秒
public class Receive02 {
public static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws Exception {
Connection conn = ConnectionUtils.getConnection();
final Channel channel = conn.createChannel();
// 声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//同一时间只会发一条消息给消费者
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[2] Receive Msg : " + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 手动确认
* 第1个参数:通过发送Tag标识来确认消费的是消息队列中的哪个消息
* 第2个参数:是否开启多个消息同时确认,这里我们设置了每次只能消费一个消息,因此不需要开启
*/
channel.basicAck(envelope.getDeliveryTag(),false);
System.out.println("[2] done ");
}
};
//改为手动确认
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
3、消息的应答与持久化
消费者从队列中获取消息,服务端如何知道消息已经被消费呢?
模式1:自动确认
只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费。
模式2:手动确认
消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。
手动模式:
boolean autoAck = true;自动确认模式,一旦RabbitMQ将消息分发给消费者就会自动在内存中删除该条消息。这种情况,如果杀死正在执行的消费者,则该消息就会丢失。
boolean autoAck = false;手动确认模式,如果有一个消费者挂掉,RabbitMQ就会将该消息交付给其他消费者。只有当消费者发出一个应答ACK,即告知RabbitMQ我已经处理完了这条消息了!然后 RabbitMQ才会将该消息从内存中删除。
如果在分发消息的过程,RabbitMQ服务器挂掉了,消息还是会丢失!!
// 声明一个队列
// QUEUE_NAME durable
boolean durable = true;
channel.queueDeclare(QUEUE_NAME,durable,false,false,null);
解决方式:声明队列的时候,有一个持久化该队列的参数,设置成 true 即可。
注:通过参数修改此队列时,保证这个队列是个新的队列,不重复定义。
4、订阅模式 Publish/Subscribe
解读:
1、1个生产者,多个消费者
2、每一个消费者都有自己的一个队列
3、生产者没有将消息直接发送到队列,而是发送到了交换机 exchange
4、每个队列都要绑定到交换机
5、生产者发送的消息,经过交换机,到达队列,实现一个消息被多个消费者获取的目的
注意:一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费。
消费者绑定了交换机并使用fanout分发模式时,绑定的消费者皆可接收来自交换机的消息。
4.1 生产者代码
public class Send {
public static final String EXCHANGE_NAME="test_exchange_fanout";
public static void main(String[] args) throws IOException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建一个通道
Channel channel = connection.createChannel();
//声明一个交换机(分发模式,只要和交换机绑定的队列,都能收到消息)
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
// 发送消息
String msg = "Hello exchange ,Publish/Subscribe Mode !";
/**
* 第一个参数为 exchange:交换机名称
* 第二个参数为 routingKey:路由键,
* 第三个参数为 props:消息属性集,包含14个属性成员,如持久化,优先级,过期时间等。
* 第四个参数为 body:消息体,需要发送的消息。
**/
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
System.out.println("------[send] msg :"+ msg);
//释放资源
channel.close();
connection.close();
}
}
4.2 消费者
消费者1
public class Receive01 {
public static final String QUEUE_NAME = "test_exchange_queue01";
public static final String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建一个通道
final Channel channel = connection.createChannel();
//声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
* 队列绑定到交换机
* 第一个参数为队列名,
* 第二个参数为交换机名
**/
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//保证队列一次只分发一个消息
channel.basicQos(1);
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("[1] Receive Msg : "+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1] done ");
/**
* 手动确认
* 第1个参数:通过发送Tag标识来确认消费的是消息队列中的哪个消息
* 第2个参数:是否开启多个消息同时确认,这里我们设置了每次只能消费一个消息,因此不需要开启
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
});
}
}
消费者2
public class Receive02 {
public static final String QUEUE_NAME = "test_exchange_queue02";
public static final String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建一个通道
final Channel channel = connection.createChannel();
//声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
* 队列绑定到交换机
* 第一个参数为队列名,
* 第二个参数为交换机名
**/
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//保证队列一次只分发一个消息
channel.basicQos(1);
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("[2] Receive Msg : "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[2] done ");
/**
* 手动确认
* 第1个参数:通过发送Tag标识来确认消费的是消息队列中的哪个消息
* 第2个参数:是否开启多个消息同时确认,这里我们设置了每次只能消费一个消息,因此不需要开启
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
});
}
}
5、路由模式Routing
当消费者声明的队列只有绑定了下相应的 routingKey 时,才会接收到消息。
5.1 生产者
public class Send {
public static final String EXCHANGE_NAME="test_exchange_direct";
public static void main(String[] args) throws IOException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建一个通道
Channel channel = connection.createChannel();
//声明一个交换机(分发模式,只要和交换机绑定的队列,都能收到消息)
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
// 发送消息
String msg = "Hello exchange ,Routing Mode !";
/**
* 第一个参数为 exchange:交换机名称
* 第二个参数为 routingKey:路由键,
* 第三个参数为 props:消息属性集,包含14个属性成员,如持久化,优先级,过期时间等。
* 第四个参数为 body:消息体,需要发送的消息。
**/
channel.basicPublish(EXCHANGE_NAME,"info",null,msg.getBytes());
System.out.println("------[send] msg :"+ msg);
//释放资源
channel.close();
connection.close();
}
}
5.2 消费者
消费者1:
public class Receive01 {
public static final String QUEUE_NAME = "test_queue_direct01";
public static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建一个通道
final Channel channel = connection.createChannel();
//声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
* 队列绑定到交换机
* 第一个参数为队列名,
* 第二个参数为交换机名
**/
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"info");
//保证队列一次只分发一个消息
channel.basicQos(1);
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("[1] Receive Msg : "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1] done ");
/**
* 手动确认
* 第1个参数:通过发送Tag标识来确认消费的是消息队列中的哪个消息
* 第2个参数:是否开启多个消息同时确认,这里我们设置了每次只能消费一个消息,因此不需要开启
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
});
}
}
消费者2
public class Receive02 {
public static final String QUEUE_NAME = "test_queue_direct02";
public static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建一个通道
final Channel channel = connection.createChannel();
//声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
* 队列绑定到交换机
* 第一个参数为队列名,
* 第二个参数为交换机名
**/
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"debug");
//channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"info");
//保证队列一次只分发一个消息
channel.basicQos(1);
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("[2] Receive Msg : "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[2] done ");
/**
* 手动确认
* 第1个参数:通过发送Tag标识来确认消费的是消息队列中的哪个消息
* 第2个参数:是否开启多个消息同时确认,这里我们设置了每次只能消费一个消息,因此不需要开启
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
});
}
}
6、主题模式Topics
与路由模式异曲同工,主要是依据通配符对 routingKey进行匹配识别,让消费者接收某些特定的消息,而不接受其他消息。
#代表匹配一个或者多个; * 匹配一个
6.1 生产者
public class Send {
public static final String EXCHANGE_NAME="test_exchange_topic";
public static void main(String[] args) throws IOException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明一个交换机(主题模式)
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
//发送消息并指明路由
String msg="hello Topic.....";
String routingKey="goods.add";
//发送
channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
System.out.println("send: "+msg);
channel.close();
connection.close();
}
}
6.2 消费者
public class Receive01 {
public static final String QUEUE_NAME = "test_topic_queue";
public static final String EXCHANGE_NAME ="test_exchange_topic";
public static void main(String[] args) throws IOException {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机上
//# 代表匹配一个或者多个; * 匹配一个
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");
channel.basicQos(1);
channel.basicConsume(QUEUE_NAME,false,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println(msg);
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
7、消息确认机制
7.1 开启事务
默认的事务型消息确认机制
channel.txSelect();开启事务的支持
channel.txCommit();提交事务
channel.txRollback();回滚事务
public class Send {
public static final String QUEUE_NAME="tx_queue_01";
public static void main(String[] args) throws IOException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
try {
//开启事务的支持
channel.txSelect();
for(int i=1;i<50;i++){
String msg = "Hello confirm Queue !"+i;
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
//提交事务
channel.txCommit();
System.out.println("【tx】 send Msg success");
} catch (IOException e) {
System.out.println("【tx】 rollback Msg success");
//回滚事务
channel.txRollback();
e.printStackTrace();
}
channel.close();
connection.close();
}
}
7.2 串行confirm模型机制:
每发送一条消息,调用waitForConfirms()方法等待服务端confirm,这实际上是一种串行的confirm,每publish一条消息之后就等待服务端confirm,如果服务端返回false或者超时时间内未返回,客户端进行消息重传;
public class Send01 {
public static final String QUEUE_NAME="tx_queue_02";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.confirmSelect();
/**
*通过循环,发送了50条消息,在channel.waitForConfirms()等待broker发送ack或nack,
*这种模式每发送一条消息就会等待broker代理服务器返回消息
**/
for(int i=1;i<50;i++){
String msg = "Hello confirm Queue !"+i;
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
if(channel.waitForConfirms()){
System.out.println("发送成功");
}else{
System.out.println("发送失败");
}
}
}
}
7.3批量confirm模式
每发送一批消息之后,调用waitForConfirms()方法,等待服务端confirm,这种批量确认的模式极大的提高了confirm效率,但是如果一旦出现confirm返回false或者超时的情况,客户端需要将这一批次的消息全部重发,这会带来明显的重复消息,如果这种情况频繁发生的话,效率也会不升反降;
public class Send02 {
public static final String QUEUE_NAME="tx_queue_03";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.confirmSelect();
for(int i=1;i<50;i++){
String msg = "Hello confirm Queue !"+i;
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
//waitForConfirms或者waitForConfirmsOrDie该方法会造成程序阻塞
if(channel.waitForConfirms()){
System.out.println("发送成功");
}else{
System.out.println("发送失败");
}
System.out.println("------测试成功------");
}
}
7.4异步confirm模式机制
public class Send03 {
public static final String QUEUE_NAME="tx_queue_04";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//开启confirm模式
channel.confirmSelect();
String msg = "你好 世界! !";
for(int i=0;i<100;i++){ //循环发消息
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
long start = System.currentTimeMillis();
//设置监听器
channel.addConfirmListener(new ConfirmListener() {
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("ack:deliveryTag:"+deliveryTag+",multiple:"+multiple);
}
//deliveryTag;唯一消息标签 当前Chanel发出的消息序号
//multiple: 是否批量
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("nack:deliveryTag:"+deliveryTag+",multiple:"+multiple);
}
});
//addConfirmListener不会造成程序阻塞
System.out.println("--------测试-----------");
System.out.println("执行waitForConfirmsz b OrDie耗费时间: "+(System.currentTimeMillis()-start)+"ms");
}
}
由于是异步的,producer不需要等待broker返回ack任可以继续发送消息,比channel.waitForConfirms()速度快很多。
8、Consumer端的消息确认
与producer端类似,为了保证消息从队列可靠地到达消费者,RabbitMQ提供消息确认机制。consumer在声明队列时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ会在队列中消息被消费后立即删除它。
采用消息确认机制后,只要令noAck=false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直持有消息直到消费者显式调用basicAck为止。
当noAck=false时,对于RabbitMQ服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息。如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。
RabbitMQ中的事务与confirmSelect模式讲解
https://blog.csdn.net/nuoWei_SenLin/article/details/81457891
本文整合网络资源和个人见解,如有侵权,请联系本人将会删除。