工作模式就是work模式,1:n 指1个生产者 多个消费者,消费者存在竞争关系,只有一个消费者会获得消息,进行消费,多个消费者竞争消费
比较适用于生产环境->负载均衡,能者多劳模式,如果机器网络较好,处理速度较快,那么采用这种方式,该机器消费消息就较多,可以通过basicQos来调整策略
工作模式上代码
为了区分效率,我们新建2个消费者,设置不同的消费延迟时间,另外把消息确认机制设置为手动确认
依旧是Maven项目,项目 pom还是参考上一篇 RabbitMQ系列(三)RabbitMQ进阶-Queue队列特性 (一)简单队列
在 java包下面新建 work2 包,然后在包下新建生产者
package work2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import conn.MqConnectUtil;
import java.time.LocalDate;
import java.time.LocalTime;
/**
* 当前描述:
*
* @author: jiazijie
* @since: 2020/6/17 下午10:31
*/
public class WorkQueueProducer {
/**
* 工作队列
*/
private final static String WORK_QUEUE_NAME = "work_queue_name_B";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = MqConnectUtil.getConnectionDefault();
// 从连接中创建通道
Channel channel = connection.createChannel();
/* 声明(创建)队列 queueDeclare( String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments)
* queue - 队列名
* durable - 是否是持久化队列, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失
* exclusie - 是否排外的,仅限于当前队列使用
* autoDelete - 是否自动删除队列,当最后一个消费者断开连接之后队列是否自动被删除,可以通过界面 查看某个队列的消费者数量,当consumers = 0时队列就会自动删除
* arguments - 队列携带的参数 比如 ttl-生命周期,x-dead-letter 死信队列等等
*/
channel.queueDeclare(WORK_QUEUE_NAME, false, false, false, null);
//定义消息内容(发布多条消息)
for (int i = 0; i < 10; i++) {
String message = "Hello World! Time:" + LocalDate.now() + " " + LocalTime.now() + " id:" + i;
/* 发送消息 String exchange, String routingKey, BasicProperties props, byte[] body
* exchange - 交换机 ,"" 空时候指定的是 获取的virtualHost 虚拟服务器的 默认的exchang,每个virtualHost都有一个AMQP default type:direct 直接转发
* queuename - 队列信息
* props - 参数信息
* message 消息体 byte[]类型
*/
channel.basicPublish("", WORK_QUEUE_NAME, null, message.getBytes());
System.out.println(" **** Producer Sent Message: '" + message + "'");
// //模拟发送消息延时,便于演示多个消费者竞争接受消息
// Thread.sleep(i * 10);
}
//关闭通道和连接
channel.close();
connection.close();
}
}
新建两个消费者,一个消费WorkQueueConsumer 速度 100ms 1条消息、一个消费WorkQueueConsumerOther 速度 按照 id*1000ms的大小去消费,模拟两个不同的消费者
消费者1 WorkQueueConsumer
package work2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import conn.MqConnectUtil;
public class WorkQueueConsumer {
/**
* 工作队列
*/
private final static String WORK_QUEUE_NAME = "work_queue_name_B";
public static void main(String[] argv) throws Exception {
Connection connection = MqConnectUtil.getConnectionDefault();
Channel channel = connection.createChannel();
/*确保这里的队列是存在的*/
channel.queueDeclare(WORK_QUEUE_NAME, false, false, false, null);
// //!!!!! 同一时刻服务器只会发送一条消息给消费者
// channel.basicQos(1);
System.out.println(" **** Consumer->1 Waiting for messages. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
/* 消息确认机制
* autoAck true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
* autoAck false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态
* 并且服务器会认为该消费者已经挂掉,不会再给其发送消息,直到该消费者反馈
* !!!!!! 注意这里是 false,手动确认
*/
channel.basicConsume(WORK_QUEUE_NAME, false, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" **** Consumer->1 Received '" + message + "'");
doSomeThing(message);
//返回确认状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
/**
* 模拟处理复杂逻辑:休眠100ms
*
* @param message
* @throws Exception
*/
public static void doSomeThing(String message) throws Exception {
//遍历Count ,sleep , 接收一条消息后休眠 100 毫秒,模仿复杂逻辑
Thread.sleep(100);
}
}
新建
消费者2 WorkQueueConsumerOther
package work2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import conn.MqConnectUtil;
public class WorkQueueConsumerOther {
/**
* 工作队列
*/
private final static String WORK_QUEUE_NAME = "work_queue_name_B";
public static void main(String[] argv) throws Exception {
Connection connection = MqConnectUtil.getConnectionDefault();
Channel channel = connection.createChannel();
/*确保这里的队列是存在的*/
channel.queueDeclare(WORK_QUEUE_NAME, false, false, false, null);
// //!!!!! 同一时刻服务器只会发送一条消息给消费者
// channel.basicQos(1);
System.out.println(" **** Consumer->1 Waiting for messages. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
/* 消息确认机制
* autoAck true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
* autoAck false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态
* 并且服务器会认为该消费者已经挂掉,不会再给其发送消息,直到该消费者反馈
* !!!!!! 注意这里是 false,手动确认
*/
channel.basicConsume(WORK_QUEUE_NAME, false, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" **** Consumer->1 Received '" + message + "'");
doSomeThing(message);
//返回确认状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
/**
* 模拟处理复杂逻辑:按照id来休眠
*
* @param message
* @throws Exception
*/
public static void doSomeThing(String message) throws Exception {
String[] spMsg = message.split("id:");
Integer count = Integer.valueOf(spMsg[1]);
for (int i = 0; i < count; i++) {
//遍历Count ,sleep , 接收一条消息后休眠 100 毫秒,模仿复杂逻辑
Thread.sleep(1000);
}
}
}
先开启消费者1 WorkQueueConsumerr,在开启消费者2 WorkQueueConsumerOther,然后再开启生产者,生产10条消息
可以看到,生产者在 3ms内,生产10条消息
消费者1和消费者2两个消费者平均消费,即使消费者1很快的处理完毕后,其余的消息也是不会发到消费者1上的,还是平均由两个消费者均衡处理
执行结果,可以看到,1条消息只能被1个消费者消费,不管消费耗时多久,都是平均消费?这就有点怪了,我牛逼,我还不能多干点?很奇怪啊,我这能力有点浪费啊
!!!划重点,mq消息的发送与消费与 basicQos有关
!!!划重点,mq消息的发送与消费与 basicQos有关
!!!划重点,mq消息的发送与消费与 basicQos有关
RabbitMQ提供了一种qos(服务质量保证)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过基于consume或者channel设置Qos的值)未被确认前,不进行消费新的消息
basicQos 假如队列中现在有100条消息,现在我们是手动确认消息机制,如果消费者消费能力差,那么mq服务器会一次性把所有消息都推送过来,我们一台消费者客户端,1分钟同时要接收到300条消息,已经超过我们最大的负载,这时就可能导致,服务器资源被耗尽,消费者客户端卡死等情况,basicQos就有作用了,用于流控
我们可以使用basic.qos方法,并设置prefetch_count=1。 basicQos(1) 这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个工作者(worker),直到它已经处理了上一条消息并且作出了响应在手动确认的前提下,如果有1条消息没有ack 应答,那么我们就暂时不推送消息,如果有1条应答了,我们就推送1条,用来保证系统稳定性,防止大量数据过来导致服务器挂掉
现在 我们来控制basicQos,设置消费者 channel的 qos配置
// !!!!! 同一时刻服务器只会发送一条消息给消费者
// channel.basicQos(1);
!!!现在我们把x消费者1 WorkQueueConsumer 和消费者2 WorkQueueConsumerOther 代码中
basicQos 注释放开,channel.basicQos(1); 再次测试一下功能
可以看到 能者多劳
消费者->1 休眠100ms,处理任务能力强,然后接收到了更多的MQ消息去消费,处理了8个消息
消费者->2 休眠id*1000ms,处理能力弱,只处理了2个消息
basicQos 等待消费者->1 处理完1条消息,当前消息已经被消费了,你可以下发下一条消息了,然后下发另一条,消费者1 再次接收到消息,再次消费,能者多劳
工作队列结束,下篇 我们介绍 RabbitMQ系列(五)RabbitMQ进阶-Queue队列特性 (三)发布/订阅 模式