RabbitMQ(1)

一、MQ相关概念

MQ本质上是一个队列(Queue),队列中存放的是消息(Message),是一种消息中间件。这种中间件可以跨进程通信,解决上下游系统的耦合问题;

二、MQ作用:

MQ在实际应用中有三大作用:

1、流量削峰

比如一个订单系统,在特定时间内有大量请求并发访问,这时可以使用MQ来进行削峰处理,即把同一时间下单的请求分散成一段时间去处理,相当于使用MQ做了一个缓冲处理,所有请求不会直接交给系统处理,这样可以极大提高系统稳定性。

2、应用解耦

以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的订单被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,下单用户感受不到物流系统的故障,提升系统的可用性

3、异步处理

有些服务间调用是异步的,例如 A 调用 B,B 需要花费很长时间执行,但是 A 需要知道 B 什么时候可以执行完,以前一般有两种方式,A 过一段时间去调用 B 的查询 api 查询。或者 A 提供一个 callback api, B 执行完之后调用 api 通知 A 服务。这两种方式都不是很优雅,使用消息总线,可以很方便解决这个问题,A 调用 B 服务后,只需要监听 B 处理完成的消息,当 B 处理完成后,会发送一条消息给 MQ,MQ 会将此消息转发给 A 服务。这样 A 服务既不用循环调用 B 的查询 api,也不用提供 callback api。同样 B 服务也不用做这些操作。A 服务还能及时的得到异步处理成功的消息;

三、Rabbit MQ四大核心概念:

1、生产者

生成数据发送到MQ的程序

2、消费者

消费MQ中的数据的程序

3、交换机

交换机是Rabbitmq中的一个重要组件,它的作用是将生产者发送过来的数据根据RoutingKey推送到指定的队列。推送到MQ中的消息。至于交换机如何处理队列中的消息是由交换机类型来决定的。

3.1、交换机类型

①、直接交换机类型(direct);
②、主题模式的交换机(topic);
③、发布订阅模式或广播模式交换机(fanout);
④、标题类型交换机(headres);
⑤、自定义类型的交换机,例如使用插件实现不阻塞式的消息延迟;
⑥、系统默认交换机,即MQ自带的一个默认交换机,当交换机名称为空串时,即为使用默认交换机;

四、RabbitMQ六大模式:

准备工作,为了演示如下6种模式,建了一个工具类,方便每次获取连接;

package com.atguigu.rabbitmq.utils;

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

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

/**
 * 描述:rabbitMQ工具类。
 *
 * @version 1.0
 * @date 2021/07/13 15:42:17
 */
public class RabbitMqUtils {

    private static ConnectionFactory connectionFactory;

    private static Connection connection;

    static {

        try {
            //创建连接工厂
            connectionFactory = new ConnectionFactory();
            //设置IP 连接RabbitMQ的队列
            connectionFactory.setHost("192.168.137.131");
            //设置端口
            //Linux中需要开发5672端口
            connectionFactory.setPort(5672);
            //用户名
            connectionFactory.setUsername("admin");
            //密码
            connectionFactory.setPassword("123");
            //创建连接
            connection = connectionFactory.newConnection();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }

    /**
     * 工厂方法,返回一个发送接收信息的渠道供生产者和消费者使用
     * 此渠道是轮询分发的
     * @return
     * @throws Exception
     */
    public static Channel getChannel() throws Exception {

        return connection.createChannel();
    }

    /**
     * 工厂方法,返回一个发送接收信息的渠道供生产者和消费者使用
     * 此渠道采用不公平机制分发,channel.basicQos(1)
     * 值为0即是轮询分发机制,值为1即是不公平分发机制
     * @return
     * @throws Exception
     */
    public static Channel getChannelNoPolling() throws IOException {
        //创建一个渠道,用来发送消息
        Channel channel = connection.createChannel();
        //rabbitMQ默认采用轮询分发消息,设置成不轮询分发,即不公平分发
        channel.basicQos(1);
        return channel;
    }

    /**
     * 预取值方式分发消息,
     * 根据每个消费者预先设置的预取值来决定所在渠道能堆积的消息数量
     * @param preValue
     * @return
     * @throws IOException
     */
    public static Channel getChannel(int preValue) throws IOException {
        //创建一个渠道,用来发送消息
        Channel channel = connection.createChannel();
        //rabbitMQ默认采用轮询分发消息,设置成不轮询分发,即不公平分发
        channel.basicQos(preValue);
        return channel;
    }

    /**
     * 开启发布确认方式
     * @return
     */
    public static Channel getChannelAndEnableConfirm() throws IOException {
        //创建一个渠道,用来发送消息
        Channel channel = connection.createChannel();
        //开启发布确认模式,默认是不开启
        channel.confirmSelect();
        return channel;
    }
}

1、简单模式(HelloWorld):

1.1、简介

简单模式,顾名思义,就是简单方式的连接。这种模式是一对一的,一个生产者对应一个消费者,生产者生产一条,消费者消费一条;

1.2、生产者代码:

public class Producer {

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

    //发消息
    public static void main(String[] args) throws Exception {
        //创建一个渠道,发送消息
        Channel channel = RabbitMqUtils.getChannel();
        //创建一个队列
        /**
         * 1、第一个参数:队列名称
         * 2、第二个参数:队列里面的消息是否持久化。默认情况下,消息存储在内存中
         * 3、第三个参数:该队列是否只供一个消费者进行消费,即是否消息共享,true:支持消息共享,false:不进行消息共享
         * 4、第四个参数:是否自动删除,最后一个消费者断开连接后,该队列是否自动删除。true:自动删除,false:不自动删除
         * 5、其他参数
         */
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //发消息
        String message = "Hello World!";

        /**
         * 1、第一个参数:发送到哪个交换机
         * 2、第二个参数:路由的key值是哪个,本次是队列的名称
         * 3、第三个参数:其他参数信息
         * 4、第四个参数:发送消息的消息体,字节数组形式
         */
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        System.out.println("消息发送完毕!");
    }
}

1.3、消费者代码

public class Consumer {

    public static void main(String[] args) throws Exception{
        //创建一个渠道,接收消息
        Channel channel = RabbitMqUtils.getChannel();

        /**
         * 消费者接收消息
         * 1、第一个参数:消息是哪个队列,即队列名称
         * 2、第二个参数:消费成功后是否要自动应答。true:自动答应,false:代表手动答应
         * 3、第三个参数:消费者成功消费后的回调函数
         * 4、第四个参数:消息消费被中断后的回调
         */
        channel.basicConsume(Producer.QUEUE_NAME,true,(consumerTag,message)->{
            System.out.println(new String(message.getBody()));
        },(consumerTag)->{
            System.out.println("消息消费被中断");
        });

    }
}

1.4、总结:

如代码所示,当使用简单模式时,我们可以不必指定交换机名称,直接给空串就行;

2、工作队列模式(WorkQueue)

2.1、简介

工作队列模式,即一个生产者对应多个消费者,多个消费者在面对同一条消息时是竞争关系,即只有一个消费者能消费到消息;具体哪个消费者能消费到消息,是根据消息分发机制,有轮询分发机制,不公平分发机制,预取值分发机制,总共三种机制;默认是采用轮询分发

2.2、轮询分发机制:

由于RabbitMQ默认是采用轮询分发机制,所以可以直接获取渠道连接即可:
①、生产者代码:

public class Task01 {

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

    public static void main(String[] args) throws Exception {
        //获取渠道
        Channel channel = RabbitMqUtils.getChannel();
        //声明连接的队列
        /**
         * 1、第一个参数:队列名称
         * 2、第二个参数:队列里面的消息是否持久化。默认情况下,消息存储在内存中
         * 3、第三个参数:该队列是否只供一个消费者进行消费,即是否消息共享,true:支持消息共享,false:不进行消息共享
         * 4、第四个参数:是否自动删除,最后一个消费者断开连接后,该队列是否自动删除。true:自动删除,false:不自动删除
         * 5、其他参数
         */
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //从控制台当中接收信息
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String message = scanner.next();
            /**
             * 1、第一个参数:发送到哪个交换机
             * 2、第二个参数:路由的key值是哪个,本次是队列的名称
             * 3、第三个参数:其他参数信息
             * 4、第四个参数:发送消息的消息体,字节数组形式
             */
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("消息发送完毕:" + message);
        }


    }
}

②、消费者代码,这里我们需要两个消费者,但代码都一样,所以把代码中消费者标识C1改成C2,然后把同一个消费者代码启动两次即可:

public class Worker01 {

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

    public static void main(String[] args) throws Exception {
        //获取一个渠道接收消息
        Channel channel = RabbitMqUtils.getChannel();

        //接收消息
        /**
         * 消费者接收消息
         * 1、第一个参数:消息哪个队列
         * 2、第二个参数:消费成功后是否要自动答应。true:自动答应,false:代表手动答应
         * 3、第三个参数:消费者成功消费的回调函数
         * 4、第四个参数:消息消费被中断后的回调
         */
        System.out.println("C1等待接收消息......");
        channel.basicConsume(QUEUE_NAME,true, (consumerTag, message) -> {
            System.out.println("接收到的消息:" + new String(message.getBody()));
        },consumerTag -> {
            System.out.println(consumerTag + "消费者取消息时被中断的回调逻辑");
        });

    }
}

③、总结:RabbitMQ是默认采用轮询分发机制,这种机制不管消息数量有多少,都是一个消费者处理一条,但是当某个消费者处理速度过快时,会处于空闲状态,造成资源的浪费;

2.3、不公平分发机制

①、不公平分发机制是一种多劳多得的机制,即哪个消费者消费的消息多,该消费者就会被分发到更多的消息。
前期准备工作,给渠道的basicQos设置1即可将该渠道发布的消息变成不公平分发模式:

/**
     * 工厂方法,返回一个发送接收信息的渠道供生产者和消费者使用
     * 此渠道采用不公平机制分发,channel.basicQos(1)
     * 值为0即是轮询分发机制,值为1即是不公平分发机制
     * @return
     * @throws Exception
     */
    public static Channel getChannelNoPolling() throws IOException {
        //创建一个渠道,用来发送消息
        Channel channel = connection.createChannel();
        //rabbitMQ默认采用轮询分发消息,设置成不轮询分发,即不公平分发
        channel.basicQos(1);
        return channel;
    }

如果是spring或者springboot整合的rabbitmq设置如下:

spring.rabbitmq.listener.direct.prefetch=1

②、生产者代码:

public class Task2 {

    //队列名称
    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannelNoPolling();

        //消息持久化
        boolean durable = true;
        //声明队列
        channel.queueDeclare(TASK_QUEUE_NAME,durable,false,false, null);

        //从控制台当中接收信息
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String message = scanner.next();
            /**
             * 1、第一个参数:发送到哪个交换机
             * 2、第二个参数:路由的key值是哪个,本次是队列的名称
             * 3、第三个参数:其他参数信息,MessageProperties.PERSISTENT_TEXT_PLAIN:设置生产者发送消息为持久化消息(保存在磁盘上)
             * 4、第四个参数:发送消息的消息体,字节数组形式
             */
            channel.basicPublish("",TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes("utf-8"));
            System.out.println("生产者消息发送完毕:" + message);
        }
    }
}

③、消费者1代码:消费者延迟1秒处理消息

public class Worker03 {

    //队列名称
    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannelNoPolling();
        System.out.println("C1等待接收消息处理时间较短...");

        channel.basicConsume(TASK_QUEUE_NAME,true,(consumerTag, message) -> {
            //沉睡10秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //接收到消息后的回调
            System.out.println("接收到的消息:" + new String(message.getBody(),"utf-8"));;
        },consumerTag -> {
            System.out.println(consumerTag + "C1消费者取消息时被中断的回调逻辑");
        });
    }
}

④、消费者2代码:消费者延迟30秒处理消息

public class Worker04 {

    //队列名称
    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel(5);
        System.out.println("C2等待接收消息处理时间较长...");

        channel.basicConsume(TASK_QUEUE_NAME,true,(consumerTag, message) -> {
            //沉睡30秒
            try {
                Thread.sleep(30000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //接收到消息的回调函数
            System.out.println("接收到的消息:" + new String(message.getBody(),"utf-8"));
        },consumerTag -> {
         //消息接收被中断的回调函数
            System.out.println(consumerTag + "C2消费者取消息时被中断的回调逻辑");
        });
    }
}

⑤、总结:不公平分发机制可以极致的利用资源,不让消费者线程有空闲的可能,但是当没有消费者空闲的情况时,而队列还在添加消息,就会造成队列消息积压,队列被撑满的情况,这个时候就要使用预取值形式的分发机制;

2.4、预取值分发机制:

①、预取值分发机制,即设置每个消费者最大可处理的消息数量,如果是2,那么当前消费者渠道的消息最多可积压2条;

②、准备工作:依旧是给消费者basicQos设置数值,但是这次设置的数值是自定义的;

/**
     * 预取值方式分发消息,
     * 根据每个消费者预先设置的预取值来决定所在渠道能堆积的消息数量
     * @param preValue
     * @return
     * @throws IOException
     */
    public static Channel getChannel(int preValue) throws IOException {
        //创建一个渠道,用来发送消息
        Channel channel = connection.createChannel();
        //rabbitMQ默认采用轮询分发消息,设置成不轮询分发,即不公平分发
        channel.basicQos(preValue);
        return channel;
    }

如果是spring或者springboot整合的rabbitMQ设置如下:

#每个消费者渠道最大可积压的消息条数为2条
spring.rabbitmq.listener.direct.prefetch=2

③、生产者代码:同上述两种模式无区别
④、消费者代码:同上述两种模式区别是获取渠道时设置了预取值数量
消息者1:渠道消息最大积压条数为2条

Channel channel = RabbitMqUtils.getChannel(2);

消费者2:渠道最大积压条数为5条

Channel channel = RabbitMqUtils.getChannel(5);

⑤、总结:预取值模式,可以根据每个消费者程序的能力设置最大的消息积压条数,相比于上述两种模式,预取值能让消费者端的负载处于一个稳定状态,是一种更优的处理方案;

3、发布订阅模式(Pub/Sub)

3.1、简介

发布订阅模式又称广播模式。具体模式与工作队列类型,即一个生产者对应多个消费者,但多个消费者之间不是竞争关系,它们可共同消费一条消息。例如:生产者发送一条消息到MQ中,交换机会将消息推送给订阅了该交换机的消费者。若有多个消费者订阅了同一个交换机,那么这几个消费者都会收到该条消息;
发布订阅模式流程图

3.2 发布订阅模式代码实现

①、生产者代码:

public class EmitLog {

    //声明交换机名称
    public static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String next = scanner.next();
            channel.basicPublish(EXCHANGE_NAME,"",null,next.getBytes("UTF-8"));
            System.out.println("生成者发出消息:" + next);
        }
    }
}

②、消费者1代码:

/**
 * @version 1.0
 * @date 2021/07/14 17:04:02
 * @Description: 消息接收 fanout类型,发布订阅模式
 */
public class ReceiveLogs01 {

    //声明交换机名称
    public static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws Exception {
        //获取一个渠道
        Channel channel = RabbitMqUtils.getChannel();
        //声明一个交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        //声明一个临时队列
        /**
         * 生成一个临时队列,队列名称随机
         * 当消费者断开与队列的连接的时,队列自动删除
         */
        String queueName = channel.queueDeclare().getQueue();

        /**
         * 绑定交换机与队列
         */
        channel.queueBind(queueName,EXCHANGE_NAME,"");
        System.out.println("消费者C1等待接收消息...");
        channel.basicConsume(queueName,true,(consumerTag, message) -> {
            //消费者成功接收消息的回调函数
            System.out.println("接收到的消息:" + new String(message.getBody(),"utf-8"));
        },consumerTag -> {
            //接收消息被中断的回调函数
        });
    }
}

③、消费者2代码:

/**
 * @author myl
 * @version 1.0
 * @date 2021/07/14 17:04:02
 * @Description: 消息接收
 */
@SuppressWarnings("all")
public class ReceiveLogs02 {

    //声明交换机名称
    public static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws Exception {
        //获取一个渠道
        Channel channel = RabbitMqUtils.getChannel();
        //声明一个交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        //声明一个临时队列
        /**
         * 生成一个临时队列,队列名称随机
         * 当消费者断开与队列的连接的时,队列自动删除
         */
        String queueName = channel.queueDeclare().getQueue();

        /**
         * 绑定交换机与队列
         */
        channel.queueBind(queueName,EXCHANGE_NAME,"");
        System.out.println("消费者C2等待接收消息...");
        channel.basicConsume(queueName,true,(consumerTag, message) -> {
            //消费者成功接收消息的回调函数
            System.out.println("接收到的消息:" + new String(message.getBody(),"utf-8"));
        },consumerTag -> {
            //接收消息被中断的回调函数
        });
    }
}

3.3、总结

发布订阅模式可以实现类型广播模式的功能,一条消息可以让多个消费者重复消费。但是前提条件是两个消费者必须声明同一类型同一名称的交换机,且该交换机必须与每个消费者所属的队列绑定(RoutingKey即为交换机与指定队列绑定的关键,注意:发布订阅模式不使用RoutingKey来作为绑定,只需要直接将交换机和队列绑定即可)。

4、路由模式(Routing)

4.1、简介

路由模式其实不是一种交换机的类型,它是一种标识符,是交换机与消费者队列绑定的关键,相当于桥梁。交换机要将消息推送到对应的队列可以使用RoutingKey来找到对应的队列,这样交换机就知道该如何处理该条消息了。

RoutingKey是由生产者指定的,生产者在发送该条消息时,就应该指定RoutingKey,告诉交换机该消息是属于哪个队列的;

4.2、代码概述

如下,是一个简单模式的MQ案例,使用的是系统默认的交换机(当交换机名称为空串时,使用的就是系统默认交换机);
指定的RoutingKey是队列名称,这样默认交换机就能找到对应的队列了

         ......
        //从控制台当中接收信息
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String message = scanner.next();
            /**
             * 1、第一个参数:发送到哪个交换机
             * 2、第二个参数:路由的key值是哪个,本次是队列的名称
             * 3、第三个参数:其他参数信息
             * 4、第四个参数:发送消息的消息体,字节数组形式
             */
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("消息发送完毕:" + message);
        }

5、主题模式(Topic)

5.1 Topic简介

5.1.1、为什么需要Topic模式:

上面介绍了广播模式,好像已经可以完成我们日常生活中大多数的业务场景了,但是,如果现在一个生产者对应两个消费者,每个消费者只想收到自己订阅的类型的消息,不是该类型的消息就不接收,即消费者有选择的接收消息,而不是生产者发送一条消息,所有消费者都要接收,这种业务场景就需要引入主题(Topic)模式了。

5.1.2、什么是Topic模式:

Topic模式又称主题模式,类似于广播模式,可以一个生产者对应多个消费者,多个消费者之间可以共享一条消息,但是与广播模式不同在于topic模式的消费者可以自己选择接收什么类型的消息;

5.1.3、topic模式消费者如何选择接收什么类型的消息?

topic模式的消费者通过RoutingKey来选择接收什么类型的消息。交换机通过RoutingKey寻找到对应的队列,然后消费者从队列收到该消息;

5.1.4、topic模式下RoutingKey类型要求:

发送到类型是 topic 交换机的消息的 routing_key 不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。这些单词可以是任意单词,比如说:“stock.usd.nyse”,“nyse.vmw”,“quick.orange.rabbit”.这种类型的。当然这个单词列表最多不能超过 255 个字节;
注意:

  • *(星号)可以代替一个单词;
  • #(井号)可以替代零个或多个单词;
5.1.5、topic模式匹配案例:

下图绑定关系如下:

Q1–>绑定的是:
中间单词带 orange的,总共3 个单词的字符串
Q2–>绑定的是
最后一个单词是以 rabbit结尾的 ,总共3 个单词;
第一个单词是以lazy开头的,后面没有单词,或有单词(lazy.#);
在这里插入图片描述

5.2、Topic代码示例:

①、生产者代码:

public class EmitLogTopic {

    //定义交换机名称
    public static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        //创建map,用于装载要发送的消息
        HashMap<String, String> bindingKeyMap = new HashMap<>();
        bindingKeyMap.put("quick.orange.rabbit","被队列 Q1Q2 接收到");
        bindingKeyMap.put("lazy.orange.elephant","被队列 Q1Q2 接收到");
        bindingKeyMap.put("quick.orange.fox","被队列 Q1 接收到");
        bindingKeyMap.put("lazy.brown.fox","被队列 Q2 接收到");
        bindingKeyMap.put("lazy.pink.rabbit","虽然满足两个绑定但只被队列 Q2 接收一次");
        bindingKeyMap.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
        bindingKeyMap.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
        bindingKeyMap.put("lazy.orange.male.rabbit","是四个单词但匹配 Q2");

        for (Map.Entry<String, String> message : bindingKeyMap.entrySet()) {
            channel.basicPublish(EXCHANGE_NAME,message.getKey(),null,message.getValue().getBytes(StandardCharsets.UTF_8));
            System.out.println("生成者发出消息:"+ message.getValue());
        }

    }
}

②、消费者1代码:

public class ReceiveLogsTopic01 {

    //定义交换机名称
    public static final String EXCHANGE_NAME = "topic_logs";

    //定义队列名称
    public static final String QUEUE_NAME = "Q1";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        //声明交换机 topic类型
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        //声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //绑定队列与交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"*.orange.*");
        System.out.println("Q1队列正在接收消息...");

        channel.basicConsume(QUEUE_NAME,true,(consumerTag, message) -> {
            //接收到消息的回调函数
            System.out.println("接收队列:"+QUEUE_NAME+"接收键:" + message.getEnvelope().getRoutingKey()+",接收到的消息:"+new String(message.getBody(),"UTF-8"));
        },consumerTag -> {
            //消息接收被中断的回调函数
        });
    }
}

③、消费者2代码:

public class ReceiveLogsTopic02 {

    //定义交换机名称
    public static final String EXCHANGE_NAME = "topic_logs";

    //定义队列名称
    public static final String QUEUE_NAME = "Q2";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        //声明交换机 topic类型
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        //声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //绑定队列与交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"*.*.rabbit");
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"lazy.#");
        System.out.println("Q2队列正在接收消息...");

        channel.basicConsume(QUEUE_NAME,true,(consumerTag, message) -> {
            //接收到消息的回调函数
            System.out.println("接收队列:"+QUEUE_NAME+"接收键:" + message.getEnvelope().getRoutingKey()+",接收到的消息:"+new String(message.getBody(),"UTF-8"));
        },consumerTag -> {
            //消息接收被中断的回调函数
        });
    }
}

④、总结:
如代码所示,消费者将队列和交换机绑定时,只要指定了对应类型的RoutingKey,即可以选择性的接收消息,而不是生产者发的消息全部接收;

6、发布确认模式(Pub/confirm)

6.1、简介及作用:

发布确认模式并不是一种具体的交换机类型。而是一种消息防丢失的机制。该机制具体流程为:生产者开启发布确认机制,通过渠道给交换机发送一条消息后,交换机收到消息后,给消费者一个确认收到消息的回应,如果该条消息交换机没有接收到,或者接收失败了,生产者会执行一个回调函数,决定如何处理这条没发送出去的消息;

6.2、如何开启发布确认机制:

6.2.1、没整合spring,开启发布确认机制:
 /**
     * 开启发布确认方式
     * @return
     */
    public static Channel getChannelAndEnableConfirm() throws IOException {
        //创建一个渠道,用来发送消息
        Channel channel = connection.createChannel();
        //调用该方法就是开启发布确认模式,默认是不开启
        channel.confirmSelect();
        return channel;
    }
6.2.2、spring整合后开启发布确认机制:
#开启交换机确认机制,三个值任选一个,关于三个值,后面介绍
spring.rabbitmq.publisher-confirm-type=correlated/SIMPLE/NONE

6.3、发布确认的三大策略:

6.3.1、单个发布确认策略
6.3.1.1、简介

该模式是一种简单的发布确认模式,且该策略是同步的,即一条消息发布后,只能等待这条消息被确认成功或者失败后,后续消息才能接着发布,所以该策略会造成阻塞,发布效率很低,不适合高吞吐量的场景;
这种策略是通过waitForConfirms()或者waitForConfirms(Long timeout)方法来阻塞的;

6.3.1.2、单个发布确认策略代码演示:
 /**
     * 单个确认模式
     */
    public static void publishMessageIndividually() throws IOException, InterruptedException {
        //获取一个渠道
        Channel channel = RabbitMqUtils.getChannelAndEnableConfirm();

        //声明队列。队列名称随机
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);
        //开始时间
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = i+"";
            channel.basicPublish("",queueName,null,message.getBytes());
            //单个消息就马上确认
            //该方法有重载方法,超时多少秒,就默认失败
            boolean flag = channel.waitForConfirms();
            if(flag){
                System.out.println("消息发送成功!");
            }
        }

        //结束时间
        long endTime = System.currentTimeMillis();
        System.out.println("发布" + MESSAGE_COUNT + "个单独确认消息,耗时:" + (endTime - startTime) + "ms");
    }
6.3.2、批量发布确认策略
6.3.2.1 、简介

批量发布确认模式和单个发布确认模式实现手段相同,在waitForConfirms()或waitForConfirms(Long timeout)方法处加了一个条数判断。同单个发布确认策略相比,该策略速度较快,但同样会造成阻塞,且不易于精准定位到失败的消息;

6.3.2.2、代码示范:
   //批量发布确认,100条消息确认一次
    public static void publishMessageBatch() throws IOException, InterruptedException {
        //获取一个渠道
        Channel channel = RabbitMqUtils.getChannelAndEnableConfirm();

        //声明队列。队列名称随机
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);
        //开始时间
        long startTime = System.currentTimeMillis();

        //批量确认消息大小
        int batchSize = 100;

        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = i+"";
            channel.basicPublish("",queueName,null,message.getBytes());

            //判断达到100条消息时确认一次
            if((i+1) % batchSize == 0){
                channel.waitForConfirms();
            }
        }

        //结束时间
        long endTime = System.currentTimeMillis();
        System.out.println("发布" + MESSAGE_COUNT + "个批量确认消息,耗时:" + (endTime - startTime) + "ms");
    }
6.3.3、异步发布确认策略
6.3.3.1、简介

相比于单个发布确认机制和批量发布确认机制,异步发布确认机制无论是可靠性和效率上都要高很多。该策略利用回调函数来进行确认不会造成阻塞,且能精准定位到失败的消息;
异步发布确认原理图:
在这里插入图片描述

6.3.3.2、代码演示
 //异步发布确认
    public static void publishMessageAsync() throws IOException {
        //获取一个渠道
        Channel channel = RabbitMqUtils.getChannelAndEnableConfirm();

        //声明队列。队列名称随机
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,false,false,false,null);

        //声明一个线程安全有序的哈希表,适用于高并发的情况下
        /**
         * 1、轻松的将序号与消息进行关联
         * 2、轻松的批量删除成功确认的消息
         * 3、支持高并发(多线程)
         */
        ConcurrentSkipListMap<Long,String> outstandingConfirms = new ConcurrentSkipListMap<>();


        //发消息的监听器,监听哪些消息成功,哪些消息失败
        /**
         * deliveryTag参数:消息的标记
         * multiple参数:是否批量应答
         */
        channel.addConfirmListener((deliveryTag, multiple) -> {
            //将确认成功的消息从map种清除,剩下的就是失败的了
            if(multiple){
                outstandingConfirms.headMap(deliveryTag).clear();
            }else {
                outstandingConfirms.remove(deliveryTag);
            }
        },(deliveryTag, multiple) -> {
            //消息确认失败的回调函数
            System.out.println("确认失败的消息:" + deliveryTag);
        });


        //开始时间
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = "消息"+i;
            //记录下所有要发送的消息
            outstandingConfirms.put(channel.getNextPublishSeqNo(),message);
            //发布消息
            channel.basicPublish("",queueName,null,message.getBytes());


        }

        //结束时间
        long endTime = System.currentTimeMillis();
        System.out.println("发布" + MESSAGE_COUNT + "个异步发布确认消息,耗时:" + (endTime - startTime) + "ms");


    }
6.3.3.3、三种策略速度比较:
   public static void main(String[] args) throws IOException, InterruptedException {
        //1、单个发布确认
        publishMessageIndividually();//发布1000个单独确认消息,耗时:502ms
        //2、批量确认
        publishMessageBatch();//发布1000个批量确认消息,耗时:77ms
        //3、异步确认
        publishMessageAsync();//发布1000个异步发布确认消息,耗时:31ms
    }

6.4、发布确认高级版本

6.4.1、简介

发布确认高级版本就是整合了springboot的发布确认机制;springboot配置文件中可以做如下配置:

#开启交换机确认机制
spring.rabbitmq.publisher-confirm-type=CORRELATED
有三个参数备选,CORRELATED,SIMPLE,NONE

  • CORRELATED:发布消息成功到交换器后会触发回调方法进行异步处理
  • SIMPLE:有两种效果,一种是和CORRELATED一样会触发回调函数,一种是
    同单个发布确认一样,使用rabbitTemplate 调用 waitForConfirms 或
    waitForConfirmsOrDie方法来进行等待,如果返回的为false,则
    channel会关闭,后续消息无法发送;
  • NONE:禁用发布确认模式,是默认值
6.4.2、消息回退和备份队列
6.4.2.1、消息回退

在仅仅开启了发布确认模式后,生产者能知道消息到底有没有发送给交换机,但是不知道交换能否成功将消息推送到指定队列,如果不能,那么此时这条消息将被丢弃。所以需要消息回退来将消息返回给生产者,让生产者知道这条消息没有发出去;

6.4.2.2、备份队列(交换机)

在交换机将消息回退给生产者后,此时生产者也仅仅是知道了这条消息路由失败了,并不能做什么额外操作,所以这时如果有个备份交换机,在主交换机无法将消息路由到指定队列时,把消息推送给备份交换机,然后备份交换机再将相应的消息推送给报警队列(是一个消费者,可以有多个),这时消费者就知道这条消息路由失败了;
所以备份队列对应了一个备份交换机,两个绑定,来达到消息防丢失;

6.4.2.3、备份交换机代码架构图

图中的备份交换机对应了一个备份队列和一个报警队列

6.4.2.4、配置类代码

配置了发布确认和备份交换机,将主交换机和备份交换机绑定,当消息路由失败时,会将消息推送到备份交换机,从而消费者会从报警队列和备份队列收到路由失败的消息;

@Configuration
public class ConfirmConfig {

    //确认交换机名称
    public static final  String CONFIRM_EXCHANGE_NAME = "confirm_exchange";

    //备份交换机
    public static final  String BACKUP_EXCHANGE_NAME = "backup_exchange_name";

    //确认队列名称
    public static final  String CONFIRM_QUEUE_NAME = "confirm_queue";

    //备份队列
    public static final  String BACKUP_QUEUE_NAME = "backup_queue";

    //报警队列
    public static final  String WARNING_QUEUE_NAME = "warning_queue";

    //RoutingKey
    public static final  String CONFIRM_ROUTING_KEY = "key1";

    //将交换机和备份交换机绑定
    @Bean
    public DirectExchange confirmExchange(){
        Map<String, Object> arguments = new HashMap<>(1);
        arguments.put("alternate-exchange",BACKUP_EXCHANGE_NAME);
        return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME).durable(true).withArguments(arguments).build();
    }

    //声明备份交换机
    @Bean
    public FanoutExchange backupExchange(){
        return new FanoutExchange(BACKUP_EXCHANGE_NAME);
    }

    //声明队列
    @Bean
    public Queue confirmQueue(){
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    //声明备份队列
    @Bean
    public Queue backupQueue(){
        return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
    }

    //声明报警队列
    @Bean
    public Queue waringQueue(){
        return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
    }

    //绑定
    @Bean
    public Binding queueBindingExchange(@Qualifier("confirmQueue")Queue queue, @Qualifier("confirmExchange")DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(CONFIRM_ROUTING_KEY);
    }

    //绑定备份交换机和备份队列
    @Bean
    public Binding BackupQueueBindingExchange(@Qualifier("backupQueue")Queue backupQueue, @Qualifier("backupExchange")FanoutExchange exchange){
        return BindingBuilder.bind(backupQueue).to(exchange);
    }

    //绑定备份交换机和报警队列
    @Bean
    public Binding WarningQueueBindingBackupExchange(@Qualifier("waringQueue")Queue backupQueue, @Qualifier("backupExchange")FanoutExchange exchange){
        return BindingBuilder.bind(backupQueue).to(exchange);
    }
}
6.4.2.5、发布确认和消息回退的回调函数:

该回调函数类有发布确认时的回调和消息回退时的回调

@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback{

    //将实现类注入到RabbitTemplate中
    @Autowired
    private RabbitTemplate rabbitTemplate;

    //注入,当前类实例化时会注入
    @PostConstruct
    public void  init(){
        //注入
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }


    /**
     * 交换机确认后的回调方法
     * 不管成功确认还是失败确认,都会调用
     * @param correlationData 保存回调消息的ID及相关信息
     * @param ack 交换是否成功接收  true:成功接收,false:失败
     * @param cause 如果成功,返回null,如果接收失败,返回失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if(ack){
            //交换机成功接收消息
           log.info("交换机已成功接收id为{}的消息",id);
        }else {
            log.info("交换机还未收到id为{}的消息,由于原因:{}",id,cause);
        }
    }

    /**
     * 在消息传递过程中,如果消息不可到达目的地时,将消息返回给生产者,同时调用此方法
     * 所以此方法只有在消息传递失败时才会触发
     * @param returned
     */
    @SneakyThrows
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        log.error("消息:{},被交换机{}退回,退回的原因:{},路由key:{}",
                new String(returned.getMessage().getBody(),"UTF-8"),
                returned.getExchange(),
                returned.getReplyText(),
                returned.getRoutingKey());
    }
}
6.4.2.6、消息生产者:

这里整合了springboot,消息生产者就是一个controller,该消息生产者中,发送的第二条消息RoutingKey是错误的,因为不存在;

@Slf4j
@RestController
@RequestMapping("/confirm")
public class ProducerController {

    @Autowired
    private  RabbitTemplate rabbitTemplate;

    //发消息
    @GetMapping("/sendMessage/{message}")
    public void sendMessage(@PathVariable String message){
        //设置消息ID
        CorrelationData correlationData1 = new CorrelationData("1");
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,ConfirmConfig.CONFIRM_ROUTING_KEY,message+"key1",correlationData1);
        log.info("发送消息内容为:{}",message+"key1");

		//该消息RoutingKey故意写错,方便测试
        CorrelationData correlationData2 = new CorrelationData("2");
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,ConfirmConfig.CONFIRM_ROUTING_KEY+"2",message+"key2",correlationData2);
        log.info("发送消息内容为:{}",message+"key2");
    }
}
6.4.2.7、报警队列(消费者)和正常消费者

①、正常的消费者代码:
用于消费者端接收正常消息

@Slf4j
@Component
public class ConfirmConsumer {
	//监听指定队列消息
    @RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE_NAME)
    public void receiveConfirmMessage(Message message) throws UnsupportedEncodingException {
        String msg = new String(message.getBody(),"UTF-8");
        log.info("接收到的confrim_queu队列消息为:{}", msg);
    }
}

②、报警队列消费者代码:
用于消费者端接收报警消息

@Slf4j
@Component
public class WarningConsumer {

    //接收报警消息
    @RabbitListener(queues = ConfirmConfig.WARNING_QUEUE_NAME)
    public void receiveWarningMsg(Message message){
        String msg = new String(message.getBody());
        log.error("报警队列发现不可路由消息:{}",msg);
    }
}

五、消息应答

1、消息应答概念:

交换机将消息推送到指定队列给消费者消费后,为了保证消息不丢失,消费者会给MQ服务一个回应,然后RabbitMq会将该条消息删除,这个过程称为消息应答;消息应答分为自动应答和手动应答两种;

2、自动应答

2.1、概念

消息发送后,立即被认为是已经传送成功,这种模式就是自动应答。但是当消息数量过多或者网络波动性大的时候不适用,因为网络波动大时,如果消费者channel连接关闭了,这条消息就会丢失,而消息数量过多,造成了消息积压,内存耗尽,操作系统会将消费者线程全部杀死,最终导致消息丢失;

2.2、开启自动应答

	    //false:采用手动应答,true:自动应答
        channel.basicConsume(TASK_QUEUE_NAME,true,...)}

3、手动应答

3.1、概念

消息发送给消费者后,消费者会一个方法,来告诉RabbitMQ,此消息是成功接收或者拒绝接收;手动应答虽然会效率不及自动应答,但是消息丢失的概率已将到最低。所以,当消息敏感性异常重要时,可以选择使用手动应答。当然,一般开发中,也建议使用手动应答

3.2、手动应答开启方式

         //false:采用手动应答,true:自动应答
        channel.basicConsume(TASK_QUEUE_NAME,false,...)}
3.2.1 三种应答方法
//RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了
Channel.basicAck(用于肯定确认)

Channel.basicNack(用于否定确认) 

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

示例:

 /**
 * 1、第一个参数:消息的标记,用来标识是哪条消息
 * 2、第二个参数:是否启用批量应答,true:启用,false:不启用,不批量应答可以避免消息丢失
  */
             channel.basicAck(message.getEnvelope().getDeliveryTag(),false);

关于批量应答(multiple):

  • true:表示开启批量应答,会将消费者同一时间收到的所有消息全部应答。可以减少网络拥堵,但会造成消息丢失;
  • false:不开启批量应答,消费者同一时间收到的所有消息只会一条一条的应答。 会保证消息不丢失,但会造成网络拥堵;
    在这里插入图片描述

4、消息重新入队

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

4.1、使用消息重新入队

由于RabbitMQ默认采用的是消费者手动应答,所以若要实现消息重新入队,则应该把自动应答该为手动应答,至于生产者的代码则和工作队列模式一样,无需改变;
注:采用轮询分发机制;

消费者1代码:延迟10秒

public class Worker03 {

    //队列名称
    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel(2);
        System.out.println("C1等待接收消息处理时间较短...");

        //采用手动应答
        channel.basicConsume(TASK_QUEUE_NAME,false,(consumerTag, message) -> {
            //沉睡10秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //接收消息
            System.out.println("接收到的消息:" + new String(message.getBody(),"utf-8"));
            //手动应答
            /**
             * 1、第一个参数:消息的标记,用来标识是哪条消息
             * 2、第二个参数:是否启用批量应答,true:启用,false:不启用,不批量应答可以避免消息丢失
             */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        },consumerTag -> {
            System.out.println(consumerTag + "C1消费者取消息时被中断的回调逻辑");
        });
    }
}

消费者2代码:延迟30秒

public class Worker04 {

    //队列名称
    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel(5);
        System.out.println("C2等待接收消息处理时间较长...");

        //采用手动应答
        channel.basicConsume(TASK_QUEUE_NAME,false,(consumerTag, message) -> {
            //沉睡30秒
            try {
                Thread.sleep(30000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //接收消息
            System.out.println("接收到的消息:" + new String(message.getBody(),"utf-8"));

            //手动应答
            /**
             * 1、第一个参数:消息的标记,用来标识是哪条消息
             * 2、第二个参数:是否启用批量应答,true:启用,false:不启用,不批量应答可以避免消息丢失
             */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        },consumerTag -> {
            System.out.println(consumerTag + "C2消费者取消息时被中断的回调逻辑");
        });
    }
}

此时给先给消费者1发送一条消息后,再给消费者2发送一条消息,在消费者2阻塞时将消费者2停掉,这时消费者2就没有进行消息应答,所以该条消息就会再次重新入队发送给消费者1,这就是消息重新入队;

6、死信队列

6.1、概念

死信队列相当于一种备份队列,当交换机推送到队列中的消息满足死信队列三个条件中的任一一个的话,即会进入死信队列;死信队列也有对应的消费者,推送到死信队列的消息也会被消费掉;

6.2、死信队列三个条件

  • 消息 TTL 过期
  • 队列达到最大长度(队列满了,无法再添加数据到 mq 中)
  • 消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false(不重新放入队列).

三个条件满足其中一个即可进入死信队列

6.3、死信队列图解:

在这里插入图片描述

6.4、代码示范

6.4.1、生产者

public class Producer01 {

    //普通交换机名称
    public static  final String NORMAL_EXCHANGE_NAME = "normal_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        //设置消息过期时间,过期成为死信消息
       // AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();

        for (int i = 1; i < 11; i++) {
            String message = "info" + i;
            channel.basicPublish(NORMAL_EXCHANGE_NAME,"zhangsan",null,message.getBytes(StandardCharsets.UTF_8));
        }
    }
}

6.4.2、消费者1代码:

在演示拒绝消息时,消费者1一定要把手动应答开启,不然不会生效!

public class Consumer01 {

    //声明普通交换机名称
    public static final String NORMAL_EXCHANGE_NAME = "normal_exchange";

    //声明死信队列交换机名称
    public static final String DEAD_EXCHANGE_NAME = "dead_exchange";

    //普通队列名称
    public static final String NORMAL_QUEUE_NAME = "normal_queue";

    //死信队列名称
    public static final String DEAD_QUEUE_NAME = "dead_queue";

    public static void main(String[] args) throws Exception {
        //获取一个渠道
        Channel channel = RabbitMqUtils.getChannel();

        //声明普通队列和死信队列的交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE_NAME,BuiltinExchangeType.DIRECT);

        //声明一个的普通队列
        Map<String, Object> arguments = new HashMap<>();
        //过期时间由生产者设置更好
        //arguments.put("x-message-ttl",100000);
        //正常队列设置过期时间后的死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE_NAME);
        //设置死信队列的Routingkey
        arguments.put("x-dead-letter-routing-key","lisi");
        //设置正常队列消息的最大长度
        //arguments.put("x-max-length",6);
        channel.queueDeclare(NORMAL_QUEUE_NAME,false,false,false,arguments);

        //声明死信队列
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);

        //绑定普通队列和普通交互机
        channel.queueBind(NORMAL_QUEUE_NAME,NORMAL_EXCHANGE_NAME,"zhangsan");
        //绑定死信队列和和死信交换机
        channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,"lisi");
        System.out.println("C01等待接收消息...");




        //接收消息
        channel.basicConsume(NORMAL_QUEUE_NAME,false,(consumerTag, message) -> {
            String msg = new String(message.getBody(), "UTF-8");
            //成功接收消息的回调
            if("info5".equals(msg)){
                //拒绝指定消息
                System.out.println("C01拒绝的消息是:" + msg);
                //拒绝消息,并且不放回普通队列
                channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
            }else {
                //不拒绝的消息
                System.out.println("C01接收的消息时:" + msg);
                //手动应答
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            }

        },consumerTag -> {
            //消息接收中断的回调

        });
    }
}

6.4.3、消费者2代码(死信队列):

public class Consumer02 {


    //声明死信队列交换机名称
    public static final String DEAD_EXCHANGE_NAME = "dead_exchange";

    //死信队列名称
    public static final String DEAD_QUEUE_NAME = "dead_queue";

    public static void main(String[] args) throws Exception {
        //获取一个渠道
        Channel channel = RabbitMqUtils.getChannel();


        System.out.println("C02等待接收消息...");


        //接收消息,
        channel.basicConsume(DEAD_QUEUE_NAME,true,(consumerTag, message) -> {
            //成功接收消息的回调,这个死信队列会收到info5的消息
            System.out.println("C02接收的消息是:"+new String(message.getBody(),"UTF-8"));
        },consumerTag -> {
            //消息接收中断的回调

        });
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值