【RabbitMQ教程】Work queues 工作队列模式

目录

前言

Work queues工作模式介绍

消息模型

适用场景

消费策略(重要)

消费策略选择

消费策略代码示例

轮询分发

(1)定义生产者和消费者

(2)运行程序

(3)结果分析

公平分发

(1)定义生产者和消费者

(2)运行程序

(3)结果分析

总结


前言

将‘work queues工作队列模式’单独抽出来细讲,目的是借助这个模式好好讲一下rabbitmq的‘轮询分发’和‘公平分配’。

Work queues工作模式介绍

rabbitmq六大工作模式架构图:

消息模型

1、竞争式消费消息。与‘广播模式’区分开,同一个队列中的消息只能被一个消费者进行消费。该消息模型有一个生产者和 多个消费者,多个消费者可以 同时消费 同一个队列消息

  • 比如生产者可将5000条数据放到「队列 」中,然后可以启动5个消费者,在默认策略下(默认是轮询分发消息),平均每个消费者消费1000条来分担压力;

2、如何让程序有 多个消费者同时消费同一个队列消息呢?

  1. 在程序中,自己手动创建多个消费者;(我个人认为除了写测试case,在实际生产中应该没有人这么干);
  2. 实际生产中,集群部署程序;那么一台机器就有一个消费者,多台机器就有多个消费者;
  3. rabbitmq中,提供了一个参数,可以配置 一台机器 消费者实例个数;

适用场景

Work Queues 对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
例如:短信服务部署多个,只需要有一个节点成功发送即可。

消费策略(重要)

这个消息模型对应着前面说过的轮寻分发 和公平分发, 默认是轮训分发。

1、轮询分发(自动ack)-不推荐

  • 轮询分发采用 自动ack机制
  • 默认是轮询分发(即平均分发消息给消费者不考虑消费者的性能差异 和处理消息的能力);
  • 不推荐使用轮询分发,因为轮询分发不考虑消费者性能差异,追求 平均分配;

2、公平分发(手动ack)-推荐

  • 公平分发采用手动ack机制

消费策略选择

轮询模式下,Work Queue是将生产者生产的消息一次性平均分配给消费者,当分配完消息后,它的自动确认机制会一次性全部确认,在官方文档中有这么一段解释:

    Message acknowledgment
    Doing a task can take a few seconds. You may wonder what happens if one of the consumers starts a long task and dies with it only partly done. With our current code once RabbitMQ delivers message to the consumer it immediately marks it for deletion. In this case, if you kill a worker we will lose the message it was just processing. We’ll also lose all the messages that were dispatched to this particular worker but were not yet handled.
    But we don’t want to lose any tasks. If a worker dies, we’d like the task to be delivered to another worker.

当生产者生产了10个消息,2个消费者平均分到了5个消息,当消费者一消费完3个消息时不明原因宕机了,剩余的2个消息则会丢失,而我们希望由其他的消费者来对这些剩余的消息消费,要是在业务中出现消息丢失可能会造成很严重的后果,所以官方不推荐使用自动消息确认。下面我们通过代码的形式,分别来测试轮询分发(自动签收消息)和 公平分配(手动消息确认)。
 

消费策略代码示例

 基于maven采用java原生写法。不需要写properties或者yml配置文件

pom依赖:

     <dependencies>
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>
    </dependencies>

rabbitutils工具类:

package com.baiqi.rabbitmq.utils;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;



public class RabbitUtils {
    private static ConnectionFactory connectionFactory = new ConnectionFactory();
    static {
        connectionFactory.setHost("127.0.0.1");
        //5672是RabbitMQ的默认端口号
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("cms");
        connectionFactory.setPassword("cms");
        //相当于表
        connectionFactory.setVirtualHost("/cms_vm");
    }
    public static Connection getConnection(){
        Connection conn = null;
        try {
            // TCP的长连接
            conn = connectionFactory.newConnection();
            return conn;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

轮询分发

(1)定义生产者和消费者

生产者:

public class Provider {
    @Test
    public void test() throws IOException, InterruptedException {
        Connection connection = RabbitMqUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work",true,false,false,null);
        for(int i=0;i<10;i++){
            channel.basicPublish("","work", MessageProperties.PERSISTENT_TEXT_PLAIN,(i+1+": hello workqueues").getBytes());
        }
        RabbitMqUtil.close(channel,connection);
    }
}

消费者1:

public class Customer1 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMqUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work",true,false,false,null);
        channel.basicConsume("work",true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("Customer1消费消息:"+new String(body));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

消费者2:

public class Customer2 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMqUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work",true,false,false,null);
        channel.basicConsume("work",true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("Customer2消费消息:"+new String(body));
            }
        });
    }
}

(2)运行程序

先对两个消费者进行开启,进入异步监听模式,然后让生产者生产10条消息,将消费者一线程休眠2秒,模拟该业务慢的情况。

消费者1:

消费者2:

(3)结果分析

无论是否当某个消费者处理缓慢时,还是一样地平均消费。

刚才的实现有以下问题:

  • 消费者1比消费者2的效率要低,一次任务的耗时较长

  • 然而两人最终消费的消息数量是一样的

  • 消费者2大量时间处于空闲状态,消费者1一直忙碌

现在的状态属于是把任务平均分配,正确的做法应该是消费越快的人,消费的越多。

怎么实现呢?

通过 BasicQos 方法设置prefetchCount = 1。这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理1个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。相反,它会将其分派给不是仍然忙碌的下一个Consumer。

值得注意的是:prefetchCount在手动ack的情况下才生效,自动ack不生效
 

公平分发

(1)定义生产者和消费者

生产者:

public class Provider {
    @Test
    public void test() throws IOException, InterruptedException {
        Connection connection = RabbitMqUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work",true,false,false,null);
        for(int i=0;i<10;i++){
            channel.basicPublish("","work", MessageProperties.PERSISTENT_TEXT_PLAIN,(i+1+": hello workqueues").getBytes());
        }
        RabbitMqUtil.close(channel,connection);
    }
}

消费者1:

public class Customer1 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMqUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work",true,false,false,null);
        //每次只确认一条消息
        channel.basicQos(1);
        channel.basicConsume("work",false,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("Customer1消费消息:"+new String(body));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
    }
}

消费者一通过线程进行了2秒的休眠,模拟处理业务慢的情况。

消费者2:

public class Customer2 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMqUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work",true,false,false,null);
        channel.basicQos(1);
        channel.basicConsume("work",false,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("Customer2消费消息:"+new String(body));
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
    }
}

这里我们通过basicQos()设置了每次拉取一条消息;

消息被消费完后通过basicAck()手动确认,第一个参数为消息的标识,用来标识信道中投递的消息,RabbitMQ 推送消息给消费者时,会附带一个 Delivery Tag,以便 消费者可以在消息确认时告诉RabbitMQ到底是哪条消息被确认了;第二个参数为是否多消息确认;

当某个消费者宕机了,也不会丢失消息,剩余的则分担到其他的消费者身上,这样的设置可以防止消息的丢失,保证了数据的完整性。

(2)运行程序

消费者1:

消费者2:

(3)结果分析

体现了能者多劳,处理效率快的消费者可以处理较多的消息;

并且,如果当消费者1宕机了(其实宕机也可以认为是处理效率慢的一种,只不过有点极端),其余的消息也可以被消费者2消费;

总结

work queues是竞争式消费;

消费策略:轮询分发、公平分发;前者是自动ack,后者是手动ack。

prefetchCount在手动ack的情况下才生效,自动ack不生效

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
RabbitMQ提供了6种工作模式,包括简单模式work queues、Publish/Subscribe发布与订阅模式、Routing路由模式、Topics主题模式、RPC远程调用模式(不太符合MQ)。这些模式分别具有不同的特点和使用场景。简单模式是最基本的模式,消息发送到队列中被消费者接收。工作队列模式是多个消费者共同消费一个队列中的消息。Publish/Subscribe发布与订阅模式中,生产者将消息发送到交换机,然后交换机将消息广播给所有绑定的队列。Routing路由模式是生产者选择将消息发送到特定的路由键上,消费者通过绑定队列和路由键来接收消息。Topics主题模式类似于Routing模式,但是可以使用通配符进行匹配路由键。RPC远程调用模式用于远程调用服务,不太符合消息队列的特点。 可以看出,这些模式在消息的传输、消费者的数量、消息的路由等方面有所不同。工作队列模式不需要定义交换机,而发布/订阅模式需要定义交换机。发布/订阅模式是面向交换机发送消息,而工作队列模式是面向队列发送消息(底层使用默认交换机)。发布/订阅模式需要设置队列和交换机的绑定,而工作队列模式不需要设置,实际上工作队列模式会将队列绑定到默认的交换机。 综上所述,RabbitMQ工作模式包括简单模式work queues、Publish/Subscribe发布与订阅模式、Routing路由模式、Topics主题模式、RPC远程调用模式。每种模式在实际应用中有不同的用途和特点,可以根据具体需求选择合适的工作模式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [RabbitMQ工作模式](https://blog.csdn.net/weixin_42440154/article/details/124689685)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [RabbitMQ六种工作模式详解](https://blog.csdn.net/qq_44760609/article/details/125084962)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@来杯咖啡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值