RabbitMQ之工作队列、消息应答原理、消息手动应答代码案例

本文详细介绍了RabbitMQ的工作队列实现,包括连接工厂、工作线程和生产者代码。接着讲解了消息应答的重要性,区分了自动应答和手动应答,并提供了手动应答的代码案例。在手动应答中,通过消费者01和消费者02的不同处理时间展示了消息在消费者挂掉后的重新分配处理。测试结果显示,未确认的消息会被重新入队,确保消息不丢失。
摘要由CSDN通过智能技术生成

一、工作队列

1、简介

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PVCqq0wE-1630999921184)(D:\学习资料\图片\image-20210830150418755.png)]

2、实现工作队列

2.1、抽取连接工厂工具类 

package com.kgf.rabbitmq.two;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/*
 * 此类为连接工厂创建信道的工具类
 * */
public class RabbitMqUtils {
    // 得到一个连接的channel
    public static Channel getChannel() throws IOException, TimeoutException {
        //创建一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();

        //配置mq的链接信息
        factory.setHost("192.168.56.20");//ip地址
        factory.setUsername("admin");//用户名
        factory.setPassword("123456");//密码
        factory.setPort(5672);//连接端口,注意浏览器访问的是15672端口,这里是5672端口
        Connection connection = factory.newConnection();
        com.rabbitmq.client.Channel channel = connection.createChannel();
        return channel;
    }
}

2.2、工作线程代码

package com.kgf.rabbitmq.two;

import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Worker01 {

    // 队列名称
    public static final String QUEUE_NAME = "hello";

    // 接受消息
    public static void main(String[] args) throws IOException, TimeoutException {
        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("C1等待接收消息");
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

2.3、生产者代码

package com.kgf.rabbitmq.two;

import com.rabbitmq.client.Channel;

import java.nio.charset.StandardCharsets;
import java.util.Scanner;

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(StandardCharsets.UTF_8));
            System.out.println("发送消息完成:"+ message);

        }
    }
}

2.4、同时开启2个工作线程:

 先启动工作线程1:

然后修改代码:

 

 再启动工作线程2:

2.5、启动生产者进行测试

 

 

 

二、消息应答

1、简介

  • 消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉了,会发生什么情况。RabbitMQ一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续发送给该消费这的消息,国为它无法接收到。
  • 为了保证消息在发送过程中不丢失,RabbitMQ引入消息应答机制,消息应答就是:消费者在接收到消息并且处理该消息之后,告诉RabbitMQ它已经处理了,RabbitMQ可以把该消息删除了。

2、自动应答(不推荐)

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

3、手动消息应答的方法

  • Channel.basicAck (用于肯定确认):RabbitMQ 已知道该消息成功被处理,可以将其丢弃了。
  • Channel.basicNack(用于否定确认)

  • Channel.basicReject(用于否定确认),与Channel.basicNack相比少了一个参数,不处理该消息了,直接拒绝,可以将其丢弃了。

4、Multiple的解释

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9D2ESH4-1630999921186)(D:\学习资料\图片\image-20210830161627088.png)]

multiple的true和false是不同的意思:

  1. true表示批量应答channel上未应答的消息,比如channel上有传送tag的消息5,6,7,8,,当前tag是8,那么此时5-8的这些还未应答的消息就会被确认收到消息应答
  2. false同上面相比只会应答tag=8的消息,5,6,7这三个消息依然不会被确认收到消息应答

 5、消息自动重新入队

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1B1lYzZs-1630999921187)(D:\学习资料\图片\image-20210830162433897.png)]

三、消息手动应答代码案例

1、生产者代码

package com.kgf.rabbitmq.three;

import com.kgf.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;

/*
 * 消息在手动应答时是不丢失、放回队列中重新消费
 * */
 public class Task2 {
 
     // 队列名称
     public static final String TASK_QUEUE_NAME = "ack_queue";
 
     public static void main(String[] args) throws IOException, TimeoutException {
         Channel channel = RabbitMqUtils.getChannel();
 
         // 声明队列
         channel.queueDeclare(TASK_QUEUE_NAME,false,false,false,null);
 
         Scanner scanner = new Scanner(System.in);
         while (scanner.hasNext()){
             String message = scanner.next();
             channel.basicPublish("",TASK_QUEUE_NAME,null,message.getBytes(StandardCharsets.UTF_8));
             System.out.println("生产者发出消息:"+message);
         }
 
     }
 }

2、消费者代码

消费者 01:睡眠时间1秒。

package com.kgf.rabbitmq.three;

import com.kgf.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

/*
 * 消费者
 * */
 public class Worker02 {
     // 队列名称
     public static final String TASK_QUEUE_NAME = "ack_queue";
 
     public static void main(String[] args) throws Exception {
         Channel channel = RabbitMqUtils.getChannel();
         System.out.println("Worker02等待接受消息处理时间较短");

         DeliverCallback deliverCallback = (consumerTag, message) -> {
             try {
                 // 沉睡一秒
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("接受到的消息是:"+new String(message.getBody()));

             //进行手动应答
             /*
             * 参数1:消息的标记  tag
             * 参数2:是否批量应答,false:不批量应答 true:批量
             * */
             channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
         };


         // 采用手动应答
         boolean autoAck = false;
         channel.basicConsume(TASK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag) -> {
             System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
         });
 }
}

消费者 02:把睡眠时间改成 30 秒。

package com.kgf.rabbitmq.three;

import com.kgf.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/*
 * 消费者
 * */
 public class Worker03 {
     // 队列名称
     public static final String TASK_QUEUE_NAME = "ack_queue";
 
     public static void main(String[] args) throws Exception {
         Channel channel = RabbitMqUtils.getChannel();
         System.out.println("Worker03等待接受消息处理时间较长");

         DeliverCallback deliverCallback = (consumerTag, message) -> {
             try {
                 // 沉睡30秒
                 Thread.sleep(30000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("接受到的消息是:"+new String(message.getBody()));

             //进行手动应答
             /*
             * 参数1:消息的标记  tag
             * 参数2:是否批量应答,false:不批量应答 true:批量
             * */
             channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
         };


         // 采用手动应答
         boolean autoAck = false;
         channel.basicConsume(TASK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag) -> {
             System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
         });
 }
}

3、测试

首先启动生产者(因为第一次启动要先创建队列)

再启动两个消费者:

 

 

使用生产者发送消息,注意:Worker03由于处理时间太长,我们挂掉他,这个时候信息会重回队列分给Worker02进行处理。 

 

可以发现当work03接受一个消息后我们直接挂掉它,最后的DD消息就被work02接受了  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值