RabbitMQ(二)Work Queues

RabbitMQ

3. 简单模式的使用

3.1 Hello Word

在这里,我们将使用java编写两个程序,分别是生产者与消费者,流程为生产者连接MQ发送消息,然后利用消费者去消费这个消息。

  1. Maven依赖(java环境)

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <!--rabbitmq 依赖客户端-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.8.0</version>
        </dependency>
        <!--操作文件流的一个依赖-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>
    
  2. Producer.java(生产者代码)

    package com.lvhaoit.rabbitmq.one;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    /**
     * Created by IntelliJ IDEA.
     * User: LvHaoIT (asus)
     * Date: 2022/5/30
     * Time: 9:03
     */
    public class Producer {
        public static final String QUEUE_NAME = "hello";
    
        //发消息
        public static void main(String[] args) throws Exception {
            //创建连接工厂
            ConnectionFactory factory = new ConnectionFactory();
    
            //工厂ip
            factory.setHost("1.1.1.1");
    
            //用户名
            factory.setUsername("lvhao");
            //密码
            factory.setPassword("*****");
    
            //创建连接
            Connection connection = factory.newConnection();
    
            //获取信道
            Channel channel = connection.createChannel();
    
            //声明一个序列
            /**
             * 1. 队列名称
             * 2. 是否持久化到磁盘 (队列持久化)
             * 3. 是否共享
             * 4. 是否自动删除
             * 5. 队列参数
             */
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    
            String message = "hello word";
    
            for (int i = 0; i < 10; i++) {
                //发送消息
                /**
                 * 1. 交换机
                 * 2. 队列名
                 * 3. 参数 消息持久化
                 * 4. 消息
                 */
                channel.basicPublish("", QUEUE_NAME, null, (message + i).getBytes());
                System.out.println("消息已发送");
            }
        }
    }
    
    
  3. Consumer.java(消费者代码)

    package com.lvhaoit.rabbitmq.one;
    
    import com.rabbitmq.client.*;
    
    /**
     * Created by IntelliJ IDEA.
     * User: LvHaoIT (asus)
     * Date: 2022/5/30
     * Time: 9:37
     */
    public class Consumer {
        public static final String QUEUE_NAME = "hello";
    
        //发消息
        public static void main(String[] args) throws Exception {
            //创建连接工厂
            ConnectionFactory factory = new ConnectionFactory();
    
            //工厂ip
            factory.setHost("1.1.5.1");
    
            //用户名
            factory.setUsername("lvhao");
            //密码
            factory.setPassword("******");
    
            //创建连接
            Connection connection = factory.newConnection();
    
            Channel channel = connection.createChannel();
    
            //        声明接收消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
                System.out.println(new String(message.getBody()));
            };
    
            //取消消息回调
            CancelCallback cancelCallback = consumerTag -> {
                System.out.println("消息被中断");
            };
    
              /**
             * 1. 队列名称
             * 2. 是否自动应答 true自动
             * 3. 接收消息处理方法
             * 4. 中断消息处理方法
             */
            channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
    
        }
    }
    
    
  4. 操作步骤

    1. 先运行生产者代码(可以看到生产者成功发送消息)

    2. 再运行消费者代码(可以看到消费者成功消费生产者发送的消息)

    3. 运行截图:
      在这里插入图片描述

4. Work Queues

工作队列(又称任务队列)的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。 相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进 程将弹出任务并最终执行作业。当有多个工作线程时,这些工作线程将一起处理这些任务。

4.1 轮询分发消息

这个例子中,我们将启动三个工作线程,一个消息发送线程,看看MQ是如何分发消息的

  1. 抽取共同代码块,形成可共用工具类。(用来获取信道)

    public class RabbitMQUtils {
        public static Channel getChannel() throws Exception {
            //创建连接工厂
            ConnectionFactory factory = new ConnectionFactory();
    
            //工厂ip
            factory.setHost("121.196.195.124");
    
            //用户名
            factory.setUsername("lvhaoit");
            //密码
            factory.setPassword("lh051920");
    
            //创建连接
            Connection connection = factory.newConnection();
    
            //获取信道
            Channel channel = connection.createChannel();
            return channel;
        }
    }
    
    
  2. 编写消费者工作线程

    public class Work01 {
    
        public static final String QUEUE_NAME = "hello";
    
        // 处理消息
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
    
            //        声明接收消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
                System.out.println(new String(message.getBody()));
            };
    
            //取消消息回调
            CancelCallback cancelCallback = consumerTag -> {
                System.out.println(consumerTag + "消息被中断");
            };
    
            System.out.println("C3正在的等待接收消息");
            channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
        }
    }
    
  3. 编写生产者生产线程

    /**
     * 消息处理轮询机制,一个消费线程消费一个 交错消费
     */
    public class Task01 {
    
        public static final String QUEUE_NAME = "hello";
    
        //发消息
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    
            Scanner scanner = new Scanner(System.in);
    
            while (scanner.hasNext()) {
                String message = scanner.next();
    
                //发消息
                channel.basicPublish("", QUEUE_NAME, null, (message).getBytes());
                System.out.println("消息已发送: " + message);
    
            }
        }
    }
    
    
  4. 操作步骤

    1. 打开消费者的并行选项

      在这里插入图片描述

    2. 同时启动三个消费者线程

      在这里插入图片描述

    3. 打开生产者线程,并发送三条消息

      在这里插入图片描述

    4. 观察启动的三个消费者进程,可以发现如下情况

      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

  5. 得出结论工作队列默认是以轮询的方式进行消息分配,即人人平等,本着公平原则,一人一条,交错消费。

4.2 消息应答

4.2.1 概念

消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成 了部分突然它挂掉了,会发生什么情况。RabbitMQ 一旦向消费者传递了一条消息,便立即将该消 息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续 发送给该消费这的消息,因为它无法接收到。

为了保证消息在发送过程中不丢失,rabbitmq 引入消息应答机制,消息应答就是:消费者在接 收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了。

4.2.2 自动应答

消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者 channel 关闭,那么消息就丢 失了,当然另一方面这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制, 当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终 使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并 以某种速率能够处理这些消息的情况下使用

通俗来说:当使用自动应答的时候能够更加的高效,但是对工作环境有着更高的要求,万一其中发生MQ服务器宕机或者消息过度积压,就有可能造成消息丢失的情况。

4.2.3 消息应答的方法(手动应答)

  1. Channel.basicAck(用于肯定确认)

    使用场景:RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了

  2. Channel.basicNack(用于否定确认)

  3. Channel.basicReject(用于否定确认)

    与 Channel.basicNack 相比少一个参数

    使用场景:不处理该消息了直接拒绝,可以将其丢弃了

优点:手动应答的好处就是可以批量应答并且减少网络拥堵。

4.2.4 Multiple的解释

​ Channel.basicAck(deliveryTag,true)

​ 这里的true就是multiple,意思是代表批量应答

​ 批量应答:假设channel(信道)上传送tag的消息有1,2,3,4,当前tag是4,那么此时1-4这些还未应答的消息都会被批量收到消息应答。

​ 当这里为false时,每次只会应答当前此条消息的应答。

为了保证队列中的消息不出现消息丢失情况,最好将此选项设置为false。

4.2.5 消息的自动重新入队

​ 如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或 TCP 连接丢失),导致消息 未发送 ACK 确认,RabbitMQ 将了解到消息未完全处理,并将对其重新排队。如果此时其他消费者 可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确 保不会丢失任何消息。

4.2.6 手动应答效果

  1. 消费线程 C3 (该线程会在处理消息时,线程沉睡三十秒,模拟处理消息慢的工作线程)

    public class Work03 {
        public static final String QUEUE_NAME = "ack_queue";
    
        // 处理消息
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
    
            //        声明接收消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
                System.out.println(new String(message.getBody(), "UTF-8"));
                try {
                    //睡眠三十秒
                    Thread.sleep(1000 * 30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
    
                //手动应答
                /**
                 * 1.消息的标记
                 * 2.是否需要批量应答 最好不要批量应答,批量应答有可能造成消息丢失
                 */
                channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
                System.out.println("处理结束!");
            };
    
            //取消消息回调
            CancelCallback cancelCallback = consumerTag -> {
                System.out.println(consumerTag + "消息被中断");
            };
    
            System.out.println("C3(执行时间较长)正在的等待接收消息");
            //采用手动应答
            boolean autoACK = false;
    
            /**
             * 1. 队列名称
             * 2. 是否自动应答 true自动
             * 3. 接收消息处理方法
             * 4. 中断消息处理方法
             */
            channel.basicConsume(QUEUE_NAME, autoACK, deliverCallback, cancelCallback);
        }
    }
    
  2. 消费线程 C4 (该线程会在处理消息时,线程沉睡两秒,模拟处理消息快的工作线程)

    public class Work04 {
        public static final String QUEUE_NAME = "ack_queue";
    
        // 处理消息
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
    
            //        声明接收消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
                System.out.println(new String(message.getBody(), "UTF-8"));
    
                try {
                    //睡眠2秒
                    Thread.sleep(1000 * 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                //手动应答
                /**
                 * 1.消息的标记
                 * 2.是否需要批量应答 最好不要批量应答,批量应答有可能造成消息丢失
                 */
                channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
                System.out.println("处理结束!");
            };
    
            //取消消息回调
            CancelCallback cancelCallback = consumerTag -> {
                System.out.println(consumerTag + "消息被中断");
            };
    
    
            System.out.println("C4(执行时间较短)正在的等待接收消息");
    
    
          
            //采用手动应答
            boolean autoACK = false;
            /**
             * 1. 队列名称
             * 2. 是否自动应答 true自动
             * 3. 接收消息处理方法
             * 4. 中断消息处理方法
             */
    
            channel.basicConsume(QUEUE_NAME, autoACK, deliverCallback, cancelCallback);
        }
    }
    
  3. 生产线程

    /**
     * 生产者
     * 让其中一个消费线程中断宕机,消费的信息会重新丢回队列给新的消费者重新消费
     */
    public class Task02 {
    
    
        public static final String QUEUE_NAME = "ack_queue";
    
        //发消息
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
            /**
             * 1。 队列名称
             * 2. 是否持久化到磁盘 (队列持久化)
             * 3. 是否共享
             * 4. 是否自动删除
             * 5. 队列参数
             */
            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
    
            //循环发消息
    
            Scanner scanner = new Scanner(System.in);
    
            while (scanner.hasNext()) {
                String message = scanner.next();
                /**
                 * 1. 交换机
                 * 2. 队列名
                 * 3. 参数 消息持久化
                 * 4. 消息
                 */
                channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
                System.out.println("生产者发出消息: " + message);
    
            }
        }
    }
    
    
  4. 操作步骤

    1. 打开两个消费进程与一个生产进程

      在这里插入图片描述

    2. 用生产进程发送四个消息

      在这里插入图片描述

    3. 可以发现c3进程第一个消息还没处理完成,此时c4进程两条消息已经都处理完成,c3进程处理中一条消息,堆积了一条消息,此时关闭c3进程,可知c3进程处理中的消息也未能正确进行消息应答。

      在这里插入图片描述

    4. 此时发现c4进行将c3未处理完的消息和堆积的消息一起拿过来处理掉了,可以证明c3中的消息重新入列了。

      在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LvhaoIT

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

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

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

打赏作者

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

抵扣说明:

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

余额充值