一、模型
- P(producer):生产者——发送消息
- Q(Queue): 消息队列(图中红色方形)——存储消息
- C(consumer): 消费者——接收消息
轮询分发(Round-Robin)即消费者1(C1)从队列(Queue)中获取一条消息之后,消费者2(C2)再从队列(Queue)中获取一条消息,等消费者2(C2)获取完之后又轮到消费者1(C1)获取,如此轮流获取,与业务时长无关,保证两个消费者获得的消息是均衡的。
二、为何会有工作队列(Work Queue)的出现
简单队列(Simple Queue)是一一对应的,而且我们实际开发中,生产者发送消息是毫不费力的,但消费者一般是跟业务相结合的,消费者接收到消息之后就需要处理,可能需要花费一定的时间,这时候队列就会积压了很多消息。
三、Java编程实现
1、导入AMQP协议jar包,以及创建RabbitMQ连接工具类,请查看三、RabbitMQ之简单队列(Simple Queue);
2、创建生产者发送消息;
package com.rabbitMQ.work;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitMQ.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* 工作队列-生产者
* @author zhoujin
* @data 2019-1-16
*/
public class WorkProducer {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
// 1.获取连接
conn = ConnectionUtils.getConnection();
// 2.从连接中获取通道
channel = conn.createChannel();
// 3.申明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 4.发送消息入队列
for (int i = 1; i <= 20; i++) {
String message = "This is work queue, 发送第" + i + "条消息!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
Thread.sleep(i * 200);
}
System.out.println("======================= Work Queue send message end! =======================");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 5.关闭通道以及连接
try {
ConnectionUtils.closeConnection(channel, conn);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
3、创建消费者1接收消息;
package com.rabbitMQ.work;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitMQ.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
/**
* 工作队列-消费者1
* @author zhoujin
* @data 2019-1-17
*/
public class WorkFirstConsumer {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) {
try {
// 1.获取连接
Connection conn = ConnectionUtils.getConnection();
// 2.从连接中获取通道
Channel channel = conn.createChannel();
// 3.声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 4.创建消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("======================= The first consumer received a message! 【Content:" + message + "】 =======================");
// 休眠,模拟业务处理
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 5.监听队列
channel.basicConsume(QUEUE_NAME, true, consumer);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
4、创建消费者2接收消息。
package com.rabbitMQ.work;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitMQ.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
/**
* 工作队列-消费者2
* @author zhoujin
* @data 2019-1-17
*/
public class WorkSecondConsumer {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) {
try {
// 1.获取连接
Connection conn = ConnectionUtils.getConnection();
// 2.从连接中获取通道
Channel channel = conn.createChannel();
// 3.声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 4.创建消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("======================= The second consumer received a message! 【Content:" + message + "】 =======================");
// 休眠,模拟业务处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 5.监听队列
channel.basicConsume(QUEUE_NAME, true, consumer);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
四、运行代码以及控制台输出
1、运行两个消费者的代码;
为何要先运行消费者的代码?
若先启动生产者(Producer),生产者(Producer)就会马上将消息发送至队列(Queue)中。但你启动第一消费者(Consumer)时,启动会它会立即获取队列(Queue)中的消息,从而导致启动另一个消费者(Consumer)时,队列(Queue)中已无消息,看不到接收现象。
2、运行生产者代码。
2.1、生产者控制台输出
2.2、第一个消费者控制台输出
2.3、第二个消费者控制台输出
五、工作队列(Work Queue)之轮询分发(Round-Robin)的不足
由于轮询分发(Round-Robin)是不管各个消费者(Consumer)的业务时长而依次轮流分发消息的,就会存在处理业务时长短的消费者(Consumer)等待其他处理业务时长长的消费者(Consumer)的情况,这样一来就会降低工作效率。