模型:
一个生产者,对应多个消费者,可以弥补简单队列一对一的限制,在实际开发中,生产者生产消息速度往往很快,但是消费者消费消息时可能会有比较耗时的操作,如果是简单工作队列,那么将可能导致消息队列中积累很多的消息,多个消费者可以增加消费速度。
轮询分配消息:
如果采用轮询的方式将队列中的消息分配给每个消费者,那么其实现方式与简单队列没有区别,只不过消费者有多个,代码没有区别。
公平分发(fair dispatch)
轮询的方式导致每个消费者都消费差不多的消息,当消费者处理速度有差异的时候,造成执行速度快的消费者先执行完,执行慢的消费者后执行完,为了处理这种情况,可以使用公平分发,不采用轮询的方式,生产者在发送消息到消息队列的时候,告诉消息队列,每次消息队列收到消费者确认消息后只发送一条消息到消费者。而消费者取消自动应答模式,使用手动应答,即当消费者消费完消息,自己发送应答消息给消息队列。
生产者代码
public class Provider {
private static final String QUNUE_NAME="test_qunue";
public static void main(String args[]) throws Exception {
//获取rabbitMQ连接
Connection conn = RabbitMqUtil.getConnection();
//创建channel
Channel channel = conn.createChannel();
//创建队列声明
channel.queueDeclare(QUNUE_NAME, false, false, false, null);
//每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
//限制发送给同一个消费者不得超过一条消息
int count = 1;
channel.basicQos(count);
//发送消息
for(int i=0; i<50; i++) {
String msg = "hello "+i;
channel.basicPublish("", QUNUE_NAME, null, msg.getBytes());
}
channel.close();
conn.close();
}
}
代码与简单队列大致相同,channel.basicQos(count)告诉消息队列每次发送一条消息给消费者。
消费者代码
public class Reciver {
private static final String QUNUE_NAME = "test_qunue";
public static void main(String[] args) throws Exception {
newApi();
}
public static void newApi() throws Exception {
// 获取连接
Connection conn = RabbitMqUtil.getConnection();
// 创建channel
final Channel channel = conn.createChannel();
// 声明队列,如果生产者中声明了队列,消费者中可以不声明
channel.queueDeclare(QUNUE_NAME, false, false, false, null);
channel.basicQos(1);
// 创建消费者
Consumer consumer = new DefaultConsumer(channel) {
// 当生产者发送消息到rabbitmq之后,会自动触发消费者中的handleDelivery方法
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
try {
String msg = new String(body, "UTF-8");
System.out.println("msg1:" + msg);
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
} finally {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 监听队列,将自动应答关闭,使用手动应答
boolean autoAck = false;
channel.basicConsume(QUNUE_NAME, autoAck, consumer);
}
}
消费者端需要告诉消息队列每次发送一个消息过来,处理完消息后手动返回确认消息
channel.basicAck(envelope.getDeliveryTag(), false);
消息应答
boolean autoAck = false;
channel.basicConsume(QUNUE_NAME, autoAck, consumer);
autoAck=true时,自动确认模式,一旦rabbitmq将消息分发给消费者后,就会从消息队列中将该条消息删除,那么在这种情况下,如果消费者发生故障,虽然接收到消息,但是没有来得及处理,结果该消息就丢失了。如下消费者,在手动发送确认之前执行1/0,发生异常,那么消息队列没有收到确认,会将消息发送给其他的消费者处理。
public static void newApi() throws Exception {
// 获取连接
Connection conn = RabbitMqUtil.getConnection();
// 创建channel
final Channel channel = conn.createChannel();
// 声明队列,如果生产者中声明了队列,消费者中可以不声明
channel.queueDeclare(QUNUE_NAME, false, false, false, null);
channel.basicQos(1);
// 创建消费者
Consumer consumer = new DefaultConsumer(channel) {
// 当生产者发送消息到rabbitmq之后,会自动触发消费者中的handleDelivery方法
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
try {
String msg = new String(body, "UTF-8");
System.out.println("msg1:" + msg);
Thread.sleep(2000);
int i = 1/0;
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (Exception e) {
System.out.println("异常了");
} finally {
//channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 监听队列,将自动应答关闭,使用手动应答
boolean autoAck = false;
channel.basicConsume(QUNUE_NAME, autoAck, consumer);
}
消息持久化
当rabbitmq发生故障的时候,如果没有将声明的消息队列进行持久化声明,那么消息会丢失。声明消息队列时,可在参数中指明是否持久化,queueDeclare方法的第二个参数设置成true则表明持久化消息队列中的消息
channel.queueDeclare(QUNUE_NAME, true, false, false, null);
注意:如果是已经声明过的队列,使用queueDeclare的不同参数再次声明该队列的时候会报错,不允许使用不同的参数声明已经存在的队列,可以在后台将原有队列删除,或者重新定义新的队列。