一、模型
- P(producer):生产者——发送消息
- Q(Queue): 消息队列(图中红色方形)——存储消息
- C(consumer): 消费者——接收消息
公平分发(Fair Dispatch)即消费者1(C1)从队列(Queue)中获取一条消息之后,处理完消息之后手动发送反馈回执,告诉RabbitMQ我处理完了,接着就会再从队列中获取一条消息 ;在消费者1(C1)获取消息后处理业务之时,消费者2(C2)也会从队列中获取一条消息之后,处理完消息之后同样会手动发送反馈回执,告诉RabbitMQ我处理完了,接着也会再从队列中获取一条消息。两个消费者(C1&C2)不需要彼此等待对方处理完业务才能获取消息,这样一来业务处理时间短的消费者就能多处理消息,做到“能者多劳”,提高工作效率。
二、公平分发(Fair Dispatch)需要注意点
1、保证消费者一次只接收一条消息(Channel.basicQos(1));
2、使用公平分发需要消费者手动反馈,所以必须关闭自动应答(ack 改为手动,具体在消费者代码中注释会体现)。
三、Java编程实现
1、导入AMQP协议jar包,以及创建RabbitMQ连接工具类,请查看 三、RabbitMQ之简单队列(Simple Queue);
2、创建生产者发送消息;
package com.rabbitMQ.workfair;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitMQ.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* 工作对列之公平分发(Fair dispatch)-生产者
* @author zhoujin
* @data 2019-1-17
*/
public class WorkFairProducer {
private static final String QUEUE_NAME = "work_fair_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.限制发送给同一个消费者不得超过一条消息
* 每个消费者发送确认消息之前,消息队列不发送下一条消息至消费者,一次只处理一条消息
*/
channel.basicQos(1);
// 5.发送消息入队列
for (int i = 1; i <= 20; i++) {
String message = "This is fair work queue, 发送第" + i + "条消息!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
Thread.sleep(i * 200);
}
System.out.println("======================= Fair work Queue send message end! =======================");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
ConnectionUtils.closeConnection(channel, conn);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
3、创建消费者1接收消息;
package com.rabbitMQ.workfair;
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;
/**
* 工作对列之公平分发(Fair dispatch)-消费者1
* @author zhoujin
* @data 2019-1-17
*/
public class WorkFairFirstConsumer {
private static final String QUEUE_NAME = "work_fair_queue";
public static void main(String[] args) {
try {
// 1.获取连接
Connection conn = ConnectionUtils.getConnection();
// 2.从连接中获取通道
final Channel channel = conn.createChannel();
// 3.声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 4.保证一次只接受一条消息
channel.basicQos(1);
// 5.创建消费者
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 fair work consumer received a message! 【Content:" + message + "】 =======================");
// 休眠,模拟业务处理
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 6.手动发送反馈回执
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 7.监听队列(关闭自动应答,即第二个参数设置为false)
channel.basicConsume(QUEUE_NAME, false, consumer);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
4、创建消费者2接收消息;
package com.rabbitMQ.workfair;
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;
/**
* 工作对列之公平分发(Fair dispatch)-消费者2
* @author zhoujin
* @data 2019-1-18
*/
public class WorkFairSecondConsumer {
private static final String QUEUE_NAME = "work_fair_queue";
public static void main(String[] args) {
try {
// 1.获取连接
Connection conn = ConnectionUtils.getConnection();
// 2.从连接中获取通道
final Channel channel = conn.createChannel();
// 3.声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 4.保证一次只接受一条消息
channel.basicQos(1);
// 5.创建消费者
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 fair work consumer received a message! 【Content:" + message + "】 =======================");
// 休眠,模拟业务处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 6.手动反馈回执
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 7.监听队列(关闭自动应答,即第二个参数设置为false)
channel.basicConsume(QUEUE_NAME, false, 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、第二个消费者控制台输出
五、总结
由两个消费者的控制台输出可以看出,消费者1(C1)业务时长为2秒,其处理9了条消息;消费者2(C2)业务时长为1秒,其处理了11条消息。由于两个消费者处理业务时长相差不大,所以处理消息条数差不多,可以尝试把两者处理业务时长差距变大,查看结果。虽然处理消息数据差不多,但看到消费者2(C2)接连处理了第2条、第3条和第5条、第6条数据,不再是顺序轮流获取消息了,也体现了“能者多劳”的公平分发(Fair Dispatch)。