上篇博文已经实现了RabbitMQ在java中的配置和简单队列的实现,这篇博文将结合现实情况介绍RabbitMQ中的工作队列。
工作队列-单个生产者对应多个消费者
新建连接RabbitMQ的工具类utils
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class utils {
public static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setVirtualHost("/vhost_zdy");
factory.setUsername("username");
factory.setPassword("password");
return factory.newConnection();
}
}
新建生产者类work_Queue_Send类,假设每20ms产生一条消息
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class workQueue_Send {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = utils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for(int i=0;i<50;i++)//发50条消息
{
String msg="msg"+i;
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
Thread.sleep(20);//假设每20ms发送一次消息
System.out.println("send:"+ msg);
}
channel.close();
connection.close();
}
}
新建两个消费者类work_Queue_Receive和work_Queue_Receive_2,并且其处理消息的速度分别为2s和1s
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class workQueue_Receive {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = utils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [1] Waiting for messages.");
DefaultConsumer defaultConsumer= new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"UTF-8");
System.out.println("[1]receive:"+ msg);
try {
Thread.sleep(2000);//假设消费者1处理时间为2s
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1]done:"+ msg);
}
}
};
channel.basicConsume(QUEUE_NAME,defaultConsumer);
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class workQueue_Receive_2 {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = utils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [2] Waiting for messages.");
DefaultConsumer defaultConsumer= new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"UTF-8");
System.out.println("[2]receive:"+ msg);
try {
Thread.sleep(1000);//假设消费者2处理时间为1s
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[2]done:"+ msg);
}
}
};
channel.basicConsume(QUEUE_NAME,defaultConsumer);
}
}
按照常理,work_Queue_Receive_2应该能够处理更多的消息,但是结果缺与我们的想象不同,两个消费者处理了同样数量的消息,且消费者1处理了奇数序号消息,消费者2处理了偶数序号消息。
出现这种情况是因为work_Queue默认使用了轮询分发的模式(Round-Robin)
下面讲介绍如何使用公平分发(Fair dispatch)
首先在Send类中设置 prefetchCount = 1
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class workQueue_Send {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = utils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//每个消费者发送确认消息之前,消息队列不发送下一个到消费者,即一次只处理一个信息
int prefetchCount = 1;
channel.basicQos(prefetchCount);
for(int i=0;i<50;i++)//发50条消息
{
String msg="msg"+i;
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
Thread.sleep(20);//假设每20ms发送一次消息
System.out.println("send:"+ msg);
}
channel.close();
connection.close();
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class workQueue_Receive {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = utils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicQos(1);//保证一次只分发一个
System.out.println(" [1] Waiting for messages.");
DefaultConsumer defaultConsumer= new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"UTF-8");
System.out.println("[1]receive:"+ msg);
try {
Thread.sleep(2000);//假设消费者1处理时间为2s
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1]done:"+ msg);
//手动回执
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoack=false;
channel.basicConsume(QUEUE_NAME,autoack,defaultConsumer);
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class workQueue_Receive_2 {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = utils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicQos(1);//保证一次只分发一个
System.out.println(" [2] Waiting for messages.");
DefaultConsumer defaultConsumer= new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"UTF-8");
System.out.println("[2]receive:"+ msg);
try {
Thread.sleep(1000);//假设消费者2处理时间为1s
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[2]done:"+ msg);
//手动回执
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoack=false;
channel.basicConsume(QUEUE_NAME,autoack,defaultConsumer);
}
}
我们来看下结果,在公平分发模式下,work_Queue_Receive_2处理了更多的信息,符合预期
消息应答和消息持久化
那么消费者中autoack设置为false与设置为true有什么区别呢?
boolean autoack=false;
channel.basicConsume(QUEUE_NAME,autoack,defaultConsumer);
autoack=true(自动确认模式)
一旦rabbitMQ将消息分发给消费者就会从内存中删除此消息,如果消费者未能正常处理消息,有可能造成消息丢失。
autoack=false(手动确认模式)--默认
由消费者通知消息队列,这条消息已经处理完毕,消息队列才删除此消息。
那么能不能对消息队列中的数据进行持久化呢?
我们需要在声明一个队列时将第二个参数durable设置为true(值得注意的是对于已存在的未持久化队列是不可以修改为持久化的队列的)
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
更多详情推荐参考官方文档