属于一种轮询模型,发送一次get请求,获得一个消息。如果此时RabbitMQ中没有消息,会获得一个表示空的回复。总的来说这种方式性能比较差,很明显每获得一条消息,都要和RabbitMQ进行网络通信发出请求。而且对RabbitMQ来说,RabbitMQ无法进行任何优化,因为它永远不知道应用程序何时会发出请求。对我们实现者来说,要在一个循环里,不断去服务器 get 消息。
具体使用,参见代码native模块包com.chj.consumer_balance.GetMessage 中。
发送消息代码:
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//日志消息级别,作为路由键使用
for(int i=0;i<3;i++) {
String message = "Hello World_"+(i+1);
// 参数1:exchange name 参数2:routing key
channel.basicPublish(EXCHANGE_NAME,"error",null,message.getBytes());
System.out.println(" [x] Sent 'error':'" + message + "'");
}
消费消息:消费者——拉取模式
// 声明一个队列
String queueName = "focuserror";
channel.queueDeclare(queueName,false,false,false,null);
//只关注error级别的日志,然后记录到文件中去。
String routekey="error";
channel.queueBind(queueName,GetMessageProducer.EXCHANGE_NAME,routekey);
System.out.println(" [*] Waiting for messages......");
while(true){
GetResponse getResponse = channel.basicGet(queueName,false);
if(null!=getResponse){
System.out.println("received[" +getResponse.getEnvelope().getRoutingKey()+"]"+new String(getResponse.getBody()));
}
channel.basicAck(0,true);
Thread.sleep(1000);
}
属于一种推送模型,注册一个消费者后,RabbitMQ会在消息可用时,自动将消息进行推送给消费者。这种模式我们已经使用过很多次了,具体使用参见代码native模块包com.chj.exchange.direct中。
前面说过,消费者收到的每一条消息都必须进行确认。消息确认后,RabbitMQ才会从队列删除这条消息,RabbitMQ不会为未确认的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开,这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久很久。
消费者在声明队列时,可以指定autoAck参数,当autoAck=true时,一旦消费者接收到了消息,就视为自动确认了消息。如果消费者在处理消息的过程中,出了错就没有什么办法重新处理这条消息,所以我们很多时候,需要在消息处理成功后,再确认消息,这就需要手动确认。
当autoAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存和磁盘(如果是持久化消息的话)中移去消息,否则RabbitMQ会在队列中消息被消费后立即删除它。 采用消息确认机制后,只要令autoAck=false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直持有消息直到消费者显式调用basicAck为止。
当autoAck=false时,对于RabbitMQ服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息。如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。
如何使用,参见代码native模块包com.chj.consumer_balance.ackfalse中。
消息者不对消息进行确认:
final Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
String message = new String(body, "UTF-8");
System.out.println("Received["+envelope.getRoutingKey()+"]"+message);
// 确认
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
// 拒绝
}
}
};
/*消费者正式开始在指定队列上消费消息*/
//TODO 这里第二个参数是自动确认参数,如果是false则是手动确认
channel.basicConsume(queueName,false,consumer);
消息者对消息进行确认:
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");
System.out.println("Received["+envelope.getRoutingKey() +"]"+message);
// TODO 这里进行确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 消费者正式开始在指定队列上消费消息
//TODO 这里第二个参数是自动确认参数,如果是false则是手动确认
channel.basicConsume(queueName,false,consumer);
通过运行程序,启动两个消费者A、B,都可以收到消息,但是其中有一个消费者A不会对消息进行确认,当把这个消费者A关闭后,消费者B又会收到本来发送给消费者A的消息。所以我们一般使用手动确认的方法是,将消息的处理放在try/catch语句块中,成功处理了,就给RabbitMQ一个确认应答,如果处理异常了就在catch中,进行消息的拒绝,如何拒绝,参考消息的拒绝章节。
消费者A收到消息1和3,然后关系消费者A:
当把这个消费者A关闭后,消费者B又会收到本来发送给消费者A的消息1和3:
在确认消息被接收之前,消费者可以预先要求接收一定数量的消息,在处理完一定数量的消息后,批量进行确认。如果消费者应用程序在确认消息之前崩溃,则所有未确认的消息将被重新发送给其他消费者。所以这里存在着一定程度上的可靠性风险。这种机制一方面可以实现限速(将消息暂存到RabbitMQ内存中)的作用,一方面可以保证消息确认质量(比如确认了但是处理有异常的情况)。
注意:消费确认模式必须是非自动ACK机制(这个是使用baseQos的前提条件,否则会Qos不生效),然后设置basicQos的值;另外,还可以基于consume和channel的粒度进行设置(global)。
具体使用,参见代码native模块包com.chj.consumer_balance.qos中。我们可以进行批量确认,也可以进行单条确认。
发送消息(发送21条消息,其中第21条消息表示本批次消息的结束):
/*声明了一个消费者*/
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");
System.out.println("Received["+envelope.getRoutingKey() +"]"+message);
// TODO 单条确认
// channel.basicAck(envelope.getDeliveryTag(),true);
}
};
//TODO 如果是两个消费者(QOS ,批量)则轮询获取数据
//TODO 15条预取(15都取出来 150, 210-150 60 )
// global参数:true\false是否将上面设置应用于channel,简单点说,就是上面限制是channel 级别的还是consumer级别。
channel.basicQos(15,true);
// 消费者正式开始在指定队列上消费消息
channel.basicConsume(queueName,false,consumer);
// TODO 自定义消费者批量确认
//BatchAckConsumer batchAckConsumer = new BatchAckConsumer(channel);
//channel.basicConsume(queueName,false,batchAckConsumer);
}
在未确认消息的情况下只能获取到15条消息,进入等待确认。
自定义批量确认——消费者:
public class BatchAckConsumer extends DefaultConsumer {
//计数,第多少条
private int meesageCount =0;
public BatchAckConsumer(Channel channel) {