上节学习消息发布时的策略,本节学习获取消息时的策略
我们知道消费者会消费队列上的消息,那么消费者如何知道队列上什么时候有消息的呢
有拉取和推送两种方式
最简单的方式就是一直轮询该队列(称之为拉取),也就是while(){...}的方式
显然这种方式很消耗性能,不推荐使用,但是可以先看一下
一、拉取
1.1 消费者demo
public class GetMessageConsumer {
public static void main(String[] argv)
throws IOException, TimeoutException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(GetMessageProducer.EXCHANGE_NAME,"direct");
String queueName = "focuserror";
channel.queueDeclare(queueName,false,false,false,null);
String routekey="error";
channel.queueBind(queueName,GetMessageProducer.EXCHANGE_NAME, routekey);
System.out.println(" [*] Waiting for messages......");
//TODO 无限循环拉取
while(true){
//参数2为 false表示不自动确认,需要手动确认,一般设置为true
GetResponse getResponse = channel.basicGet(queueName, false);
if(null!=getResponse){
System.out.println("处理业务");
}
//手动确认(上面参数为true时,该语句可省略)
channel.basicAck(0,true);
Thread.sleep(1000);
}
}
}
通过basicGet方法获取队列上的消息
这里注意,在使用拉取的时候,我们设置了一个是否自动进行消息确认的参数
这里引出一个知识点
消费者获取消息之后,无论是拉取还是推送,都应该给MQ一个消息确认
消息确认后,RabbitMQ 才会从队列删除这条消息
因为RabbitMQ 不会为未确认的消息设置超时时间
它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开
这么设计的原因是RabbitMQ 允许消费者消费一条消息的时间可以很久
1.2 消息确认
消息确认可分为自动确认和手动确认
自动确认:消费者在声明队列时,指定 autoAck 为true,一旦消费者接收到了消息,就视为自动确认了消息。如果消费者在处理消息的过程中,出了错,就没有什么办法重新处理这条消息,所以我们很多时候,需要在消息处理成功后,再确认消息,这就需要手动确认。
手动确认:当 autoAck=false 时,RabbitMQ 会等待消费者显式发回 ack 信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ 会在队列中消息被消费后立即删除它。
当 autoAck=false 时,对于 RabbitMQ 服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者, 但是还没有收到消费者 ack 信号的消息。如果服务器端一直没有收到消费者的 ack 信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来那个消费者)
public class AckFalseConsumerB {
public static void main(String[] argv)
throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(DirectProducer.EXCHANGE_NAME,"direct");
String queueName = "focuserror";
channel.queueDeclare(queueName,false,false,false,null);
/*绑定,将队列和交换器通过路由键进行绑定*/
String routekey = "error";
channel.queueBind(queueName,DirectProducer.EXCHANGE_NAME,routekey);
System.out.println("waiting for message........");
/*声明了一个消费者*/
final Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
//TODO 这里进行确认
System.out.println("业务处理成功,手动确认:"+envelope.getDeliveryTag());
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
/*消费者正式开始在指定队列上消费消息*/
//这里第二个参数是自动确认参数,如果是false则是手动确认
channel.basicConsume(queueName,false,consumer);
}
}
二、推送
上面介绍消息确认时采用的即是推送模式
三、QOS预取模式
上面介绍都是每条消息被消费后即进行消费确认
//预取500条消息
channel.basicQos(500,true);
/*消费者正式开始在指定队列上消费消息*/
channel.basicConsume(queueName,false,consumer);
总结
消息者获取消息的两种方式:拉取和推送
消息确认的两种方式:自动和手动
一共四种情况
1、拉取自动确认
GetResponse getResponse = channel.basicGet(queueName, true);
2、拉取手动确认
//采用手动确认方式
GetResponse getResponse = channel.basicGet(queueName, false);
//消费者消费完后,进行手动确认
channel.basicAck(0,true);
3、推送自动确认
//这里第二个参数是自动确认参数,如果是false则是手动确认
channel.basicConsume(queueName,true,consumer);
4、推送手动确认
//这里第二个参数是自动确认参数,如果是false则是手动确认
channel.basicConsume(queueName,false,consumer);
//消费者消费完成后,进行手动确认
channel.basicAck(envelope.getDeliveryTag(),false);