RabbitMQ系列(四)RabbitMQ进阶-Queue队列特性 (二)工作队列 Work模式

RabbitMQ进阶-Queue队列特性 (二)工作队列 Work模式

文章目录

    • RabbitMQ进阶-Queue队列特性 (二)工作队列 Work模式
        • 1.work 工作队列(工作模式)
        • 2.代码实战
          • 2.1 生产者
          • 2.2 新建2个消费者
          • 2.3 执行结果
          • 2.4 结果分析 basicQos
        • 3.消费者 basicQos

1.work 工作队列(工作模式)

工作模式就是work模式,1:n 指1个生产者 多个消费者,消费者存在竞争关系,只有一个消费者会获得消息,进行消费,多个消费者竞争消费
比较适用于生产环境->负载均衡,能者多劳模式,如果机器网络较好,处理速度较快,那么采用这种方式,该机器消费消息就较多,可以通过basicQos来调整策略
RabbitMQ系列(四)RabbitMQ进阶-Queue队列特性 (二)工作队列 Work模式_第1张图片
工作模式上代码
为了区分效率,我们新建2个消费者,设置不同的消费延迟时间,另外把消息确认机制设置为手动确认

2.代码实战

依旧是Maven项目,项目 pom还是参考上一篇 RabbitMQ系列(三)RabbitMQ进阶-Queue队列特性 (一)简单队列

2.1 生产者

在 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();
    }
}
2.2 新建2个消费者

新建两个消费者,一个消费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);
        }
    }
}
2.3 执行结果

先开启消费者1 WorkQueueConsumerr,在开启消费者2 WorkQueueConsumerOther,然后再开启生产者,生产10条消息
可以看到,生产者在 3ms内,生产10条消息
RabbitMQ系列(四)RabbitMQ进阶-Queue队列特性 (二)工作队列 Work模式_第2张图片
消费者1和消费者2两个消费者平均消费,即使消费者1很快的处理完毕后,其余的消息也是不会发到消费者1上的,还是平均由两个消费者均衡处理
RabbitMQ系列(四)RabbitMQ进阶-Queue队列特性 (二)工作队列 Work模式_第3张图片

2.4 结果分析 basicQos

执行结果,可以看到,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条,用来保证系统稳定性,防止大量数据过来导致服务器挂掉

3.消费者 basicQos

现在 我们来控制basicQos,设置消费者 channel的 qos配置

//        !!!!! 同一时刻服务器只会发送一条消息给消费者
//        channel.basicQos(1);

!!!现在我们把x消费者1 WorkQueueConsumer 和消费者2 WorkQueueConsumerOther 代码中
basicQos 注释放开,channel.basicQos(1); 再次测试一下功能

开启生产者,生产10条消息
RabbitMQ系列(四)RabbitMQ进阶-Queue队列特性 (二)工作队列 Work模式_第4张图片

看下消费者1和消费者2
RabbitMQ系列(四)RabbitMQ进阶-Queue队列特性 (二)工作队列 Work模式_第5张图片

可以看到 能者多劳

消费者->1 休眠100ms,处理任务能力强,然后接收到了更多的MQ消息去消费,处理了8个消息
消费者->2 休眠id*1000ms,处理能力弱,只处理了2个消息
basicQos 等待消费者->1 处理完1条消息,当前消息已经被消费了,你可以下发下一条消息了,然后下发另一条,消费者1 再次接收到消息,再次消费,能者多劳

工作队列结束,下篇 我们介绍 RabbitMQ系列(五)RabbitMQ进阶-Queue队列特性 (三)发布/订阅 模式

你可能感兴趣的:(MQ中间件,rabbitmq,basicQos,工作队列,能者多劳,公平分发)