1、Work模式 - 公平分发(Fair Dispatch)
当有多个消费者时,我们的消息会被哪个消费者消费呢,我们又该如何均衡消费者消费信息的多少呢?
主要有两种模式:
1、轮询模式的分发:一个消费者一条,按均分配;
2、公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配;
2、Work模式 - 公平分发(Fair Dispatch)
生产者
package com.sky.rabbitmq.work.fair;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author 尹稳健~
* @version 1.0
*/
public class Producer {
public static void main(String[] args) {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
connection = connectionFactory.newConnection();
channel = connection.createChannel();
String exchange_name = "";
String routeKey = "queue1";
String exchange_type = "direct";
// 声明队列
/**
* @param 队列名称
* @param 是否持久化
* @param 排他性
* @param 自动删除
* @param 是否有参数 header模式会用到
*/
// channel.queueDeclare("queue7",true,false,false,null);
// channel.queueDeclare("queue8",true,false,false,null);
// channel.queueDeclare("queue9",true,false,false,null);
// 声明交换机
/**
* @param 交换机名称
* @param 交换机类型
* @param 是否持久化
* @param
*/
// channel.exchangeDeclare(exchange_name,exchange_type,true);
// 绑定队列
/**
* @param 队列名称
* @param 交换机名称
* @param 路由key
*/
// channel.queueBind("queue7",exchange_name,"order");
// channel.queueBind("queue8",exchange_name,"order");
// channel.queueBind("queue9",exchange_name,"goods");
// channel.exchangeBind();
for (int i = 0;i<20;i++) {
String message = "我是梁志超他奶奶" + i;
channel.basicPublish(exchange_name,routeKey,null,message.getBytes());
}
System.out.println("消息发送成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送失败!");
}finally {
// 7:关闭连接
if (channel!=null && channel.isOpen()){
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
// 8:关闭通道
if (connection!= null){
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
消费者一:
package com.sky.rabbitmq.work.fair;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @author 尹稳健~
* @version 1.0
*/
public class Work2 {
private static Runnable runnable = new Runnable() {
public void run() {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
//获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
connection = connectionFactory.newConnection();
channel = connection.createChannel();
/**
* 启动一个消费者,并返回服务端生成的消费者标识
* queue:队列名
* autoAck:true 接收到传递过来的消息后acknowledged(应答服务器),false 接收到消息后不应答服务器
* deliverCallback: 当一个消息发送过来后的回调接口
* cancelCallback:当一个消费者取消订阅时的回调接口;
* 取消消费者订阅队列时除了使用{@link Channel#basicCancel}之外的所有方式都会调用该回调方法
* @return 服务端生成的消费者标识
*/
// 6: 定义接受消息的回调
final Channel finalChannel = channel;
//预期取数 同一时刻 服务器只会推送一条消息给消费者
finalChannel.basicQos(1);
channel.basicConsume( queueName , false , new DeliverCallback() {
public void handle(String s, Delivery delivery) throws IOException {
try {
System.out.println(queueName + ":收到消息是:" +
new String(delivery.getBody(), "UTF-8"));
Thread.sleep(200);
//手动应答
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, new CancelCallback() {
public void handle(String s) throws IOException {
System.out.println("异常");
}
});
System.out.println("接收消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
};
public static void main(String[] args) {
new Thread(runnable,"queue1").start();
// new Thread(runnable,"queue8").start();
// new Thread(runnable,"queue9").start();
// new Thread(runnable,"queue2").start();
// new Thread(runnable,"queue3").start();
}
}
消费者二:
package com.sky.rabbitmq.work.fair;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @author 尹稳健~
* @version 1.0
*/
public class Work1 {
private static Runnable runnable = new Runnable() {
public void run() {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
//获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
connection = connectionFactory.newConnection();
channel = connection.createChannel();
/**
* 启动一个消费者,并返回服务端生成的消费者标识
* queue:队列名
* autoAck:true 接收到传递过来的消息后acknowledged(应答服务器),false 接收到消息后不应答服务器
* deliverCallback: 当一个消息发送过来后的回调接口
* cancelCallback:当一个消费者取消订阅时的回调接口;
* 取消消费者订阅队列时除了使用{@link Channel#basicCancel}之外的所有方式都会调用该回调方法
* @return 服务端生成的消费者标识
*/
// 6: 定义接受消息的回调
final Channel finalChannel = channel;
//预期取数 同一时刻 服务器只会推送一条消息给消费者
finalChannel.basicQos(1);
channel.basicConsume( queueName , false , new DeliverCallback() {
public void handle(String s, Delivery delivery) throws IOException {
try {
System.out.println(queueName + ":收到消息是:" +
new String(delivery.getBody(), "UTF-8"));
Thread.sleep(2000);
//手动应答
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, new CancelCallback() {
public void handle(String s) throws IOException {
System.out.println("异常");
}
});
System.out.println("接收消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
};
public static void main(String[] args) {
new Thread(runnable,"queue1").start();
// new Thread(runnable,"queue8").start();
// new Thread(runnable,"queue9").start();
// new Thread(runnable,"queue2").start();
// new Thread(runnable,"queue3").start();
}
}
测试结果
小结
从结果可以看到,消费者1在相同时间内,处理了更多的消息;以上代码我们实现了公平分发模式;
- 消费者一次接收一条消息,代码channel.BasicQos(0, 1, false);
- 公平分发需要消费者开启手动应答,关闭自动应答
- 关闭自动应答代码channel.BasicConsume(“queue_test”, false, consumer);
- 消费者开启手动应答代码:channel.BasicAck(ea.DeliveryTag, false);
总结
(1)当队列里消息较多时,我们通常会开启多个消费者处理消息;公平分发和轮询分发都是我们经常使用的模式。
(2)轮询分发的主要思想是“按均分配”,不考虑消费者的处理能力,所有消费者均分;这种情况下,处理能力弱的服务器,一直都在处理消息,而处理能力强的服务器,在处理完消息后,处于空闲状态;
(3) 公平分发的主要思想是”能者多劳”,按需分配,能力强的干的多。