RabbitMQ一些配置

RabbitMQ一些配置

1、使用centos7安装

安装依赖环境

在线安装依赖环境:

yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz

安装Erlang

上传

erlang-18.3-1.el7.centos.x86_64.rpm

socat-1.7.3.2-5.el7.lux.x86_64.rpm

rabbitmq-server-3.6.5-1.noarch.rpm

# 安装
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm

如果出现如下错误

在这里插入图片描述

说明gblic 版本太低。我们可以查看当前机器的gblic 版本

strings /lib64/libc.so.6 | grep GLIBC

在这里插入图片描述

当前最⾼版本2.12,需要2.15.所以需要升级glibc

  • 使⽤yum更新安装依赖
sudo yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlitedevel readline-devel tk-devel gcc make -y
  • 下载rpm包
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-utils-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-static-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-common-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-devel-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-headers-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/nscd-2.17-55.el6.x86_64.rpm &
  • 安装rpm包
sudo rpm -Uvh socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps
  • 安装完毕后再查看glibc版本,发现glibc版本已经到2.17了
strings /lib64/libc.so.6 | grep GLIBC

在这里插入图片描述

安装RabbitMQ

# 安装
rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps
# 安装
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm

开启管理界⾯及配置

# 开启管理界⾯
rabbitmq-plugins enable rabbitmq_management
# 修改默认配置信息
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
# ⽐如修改密码、配置等等,例如:loopback_users 中的 <<"guest">>,只保留guest

启动

service rabbitmq-server start # 启动服务
service rabbitmq-server stop # 停⽌服务
service rabbitmq-server restart # 重启服务

设置配置⽂件

cd /usr/share/doc/rabbitmq-server-3.6.5/
cp rabbitmq.config.example /etc/rabbitmq/rabbitmq.config

2、docker安装rabbitMQ

找rabbitMQ版本

先去官网找rabbitmq版本,网址:http://www.rabbitmq.com/download.html

找到docker安装rabbitmq

在这里插入图片描述

找到你要安装的rabbitmq版本(注:带management为带管理界面的版本)

在这里插入图片描述

开始安装rabbitmq(没有下载会自动下载)

搜索镜像

docker search rabbitmq:3.9.13-management

安装镜像shel

docker pull rabbitmq:3.9.13-management

创建容器

docker run -d --hostname my-rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.9.13-management

访问测试http://ip地址:15672

RabbitMQ在安装好后,可以访问 http://ip地址:15672 ;其⾃带了guest/guest的⽤户名和密码;
在这里插入图片描述

如果需要创建⾃定义⽤户;那么也可以登录管理界⾯后,如下操作:

在这里插入图片描述

⻆⾊说明

1、 超级管理员(administrator)

可登陆管理控制台,可查看所有的信息,并且可以对⽤户,策略(policy)进⾏操作。

2、 监控者(monitoring)

可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使⽤情况,磁盘使⽤情况等)

3、 策略制定者(policymaker)

可登陆管理控制台, 同时可以对policy进⾏管理。但⽆法查看节点的相关信息(上图红框标识的部分)。

4、 普通管理者(management)

仅可登陆管理控制台,⽆法看到节点信息,也⽆法对策略进⾏管理。

5、 其他

⽆法登陆管理控制台,通常就是普通的⽣产者和消费者。

Virtual Hosts配置

像mysql拥有数据库的概念并且可以指定⽤户对库和表等操作的权限。RabbitMQ也有类似的权限管理;在RabbitMQ中可以虚拟消息服务器Virtual Host,每个Virtual Hosts相当于⼀个相对独⽴的RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通。 相当于mysql的db。Virtual Name⼀般以/开头。

3、5种工作模式

3.1、简单模式

在这里插入图片描述

在上图的模型中,有以下概念:

P:生产者,也就是要发送消息的程序

C:消费者:消息的接收者,会一直等待消息到来

queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息

添加依赖

往rabbitmq的pom.xml文件中添加如下依赖:

 <dependencies>
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.9.0</version>
        </dependency>
    </dependencies>

编写生产者

编写消息生产者com.sunzihan.rabbitmq.simple.Producer

package com.sunzihan.rabbitmq.simple;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/14 16:34
 * 4
 */
public class Producer {
    public static final String QUEUE_NAME ="simple_queue" ;

    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        // 声明(创建)队列
         /*参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接,只能有一个Consumer监听这个队列
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时,自动删除
         * 参数5:队列其它参数 */
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);

        //发送的消息
        String message = "你好,小兔子!!!";
        /* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
         * 参数2:路由key,简单模式可以传递队列名称
         * 参数3:消息其它属性
         * 参数4:消息内容 */
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        //测试
        System.out.println("已发送消息"+message);

        //关闭资源
        channel.close();
        connection.close();
    }
}

在执行上述的消息发送之后;可以登录rabbitMQ的管理控制台,可以发现队列和其消息:

在这里插入图片描述

编写消费者

抽取创建connection的工具类com.sunzihan.rabbitmq.utils.ConnectionUtil;

package com.sunzihan.rabbitmq.utils;

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

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/14 22:50
 * 4
 */
public class ConnectionUtil {
    public static Connection getConnection() throws IOException, TimeoutException {
        //创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //配置主机,默认是localhost
        connectionFactory.setHost("192.168.216.128");
        //配置端口,默认为5672
        connectionFactory.setPort(5672);
        //配置主机名称,默认是/
        connectionFactory.setVirtualHost("/szh");
        //配置用户名.默认是guest
        connectionFactory.setUsername("szh");
        //配置密码,默认是guest
        connectionFactory.setPassword("szh");

        //创建连接
        return connectionFactory.newConnection();
    }
}

编写消息的消费者com.sunzihan.rabbitmq.simple.Consumer

package com.sunzihan.rabbitmq.simple;

import com.rabbitmq.client.*;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/14 19:02
 * 4
 */
public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        //创建队列
        /*
         * 参数1:队列名称
         * 参数2:是否持久化
         * 参数3:是否独立连接
         * 参数4:是否自动删除
         * 参数5:其他参数
         */
        channel.queueDeclare(Producer.QUEUE_NAME,true,false,false,null);

        //接收参数
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:"+envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:"+envelope.getExchange());
                //消息id
                System.out.println("消息id为:"+envelope.getDeliveryTag());
                //收到的消息
                System.out.println("收到的消息为:"+new String(body,"utf-8"));
            }
        };
        channel.basicConsume(Producer.QUEUE_NAME,true,defaultConsumer);

        //注意不能关闭资源,消费者一直需要连接到rabbitmq上,当有消息,它就能马上消费到这个消息


    }
}

3.2 Work queues工作队列模式

模式说明

在这里插入图片描述

Work Queues与入门程序的 简单模式 相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。
应用场景:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

生产者

package com.sunzihan.rabbitmq.work;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/14 22:57
 * 4 在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
 */
public class Producer {
    public static final String WORK_QUEUE = "work_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        //创建队列
        /*
        * 参数1:队列名称
        * 参数2:是否持久化队列
        * 参数3:是否独占连接,只允许一个consumer使用
        * 参数4:是否自动删除,当没有consumer时自动删除
        * 参数5:其他参数
        */
        channel.queueDeclare(WORK_QUEUE,true,false,false,null);
        //发送消息
        for (int i = 0; i <30 ; i++) {
            String message = "你好,小兔子---work queue模式-----"+i;
            /*
             *参数1:交换机的名称 默认为Default Exchange
             * 参数2:路由key,简单模式可以传递队列名称
             * 参数3;消息其他属性
             * 参数4:消息内容
             */
            System.out.println("发送的消息为:"+message);
            channel.basicPublish("",WORK_QUEUE,null,message.getBytes());
        }


        //关闭资源
        channel.close();
        connection.close();

    }
}

消费者1

package com.sunzihan.rabbitmq.work;

import com.rabbitmq.client.*;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 15:57
 * 4
 */
public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        final Channel channel = connection.createChannel();
        //创建队列
        /*
         *参数1:队列名称
         * 参数2:是否持久化
         * 参数3:是否独占连接
         * 参数4是否自动删除
         * 参数5:其他参数
         */
        channel.queueDeclare(Producer.WORK_QUEUE, true, false, false, null);
        //接收消息
        //一次只能接收并处理一个消息
        channel.basicQos(1);

        //创建消费者;并设置消息处理
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            /*** consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * * envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重 传标志(收到消息失败后是否需要重新发送)
             * * properties 属性信息
             * * body 消息 */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                try {
                    //路由key
                    System.out.println("路由key为:" + envelope.getRoutingKey());
                    //交换机
                    System.out.println("交换机为:" + envelope.getExchange());
                    //消息id
                    System.out.println("消息id为:" + envelope.getDeliveryTag());
                    //收到的消息
                    System.out.println("消费者1的消息为:" + new String(body, "utf-8"));
                    Thread.sleep(1000);
                    //消息的id,false表示只签收当前消息
                    channel.basicAck(envelope.getDeliveryTag(), false);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };
        //签收消息
        /*
        参数1:队列名称
         * 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复 会删除消息,设置为false则需要手动确认
         * 参数3:消息接收到后回调 */
        channel.basicConsume(Producer.WORK_QUEUE, false, defaultConsumer);
    }
}

消费者2

package com.sunzihan.rabbitmq.work;

import com.rabbitmq.client.*;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 15:57
 * 4
 */
public class Consumer2 {
    public  static void main(String [] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        final Channel channel = connection.createChannel();
        //创建队列
        /*
         *参数1:队列名称
         * 参数2:是否持久化
         * 参数3:是否独占连接
         * 参数4是否自动删除
         * 参数5:其他参数
         */
        channel.queueDeclare(Producer.WORK_QUEUE,true,false,false,null);
        //接收消息
        //一次只能接收并处理一个消息
        channel.basicQos(1);

        //创建消费者;并设置消息处理
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            /*** consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * * envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重 传标志(收到消息失败后是否需要重新发送)
             * * properties 属性信息
             * * body 消息 */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                try {
                    //路由key
                    System.out.println("路由key为:" + envelope.getRoutingKey());
                    //交换机
                    System.out.println("交换机为:" + envelope.getExchange());
                    //消息id
                    System.out.println("消息id为:" + envelope.getDeliveryTag());
                    //收到的消息
                    System.out.println("消费者2的消息为:" + new String(body, "utf-8"));
                    Thread.sleep(1000);
                    //消息的id,false表示只签收当前消息
                    channel.basicAck(envelope.getDeliveryTag(), false);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };
        //监听消息
        /*
        参数1:队列名称
         * 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复 会删除消息,设置为false则需要手动确认
         * 参数3:消息接收到后回调 */
        channel.basicConsume(Producer.WORK_QUEUE, false, defaultConsumer);
    }
}

测试

启动两个消费者,然后再启动生产者发送消息;到IDEA的两个消费者对应的控制台查看是否竞争性的接收到消息。

在这里插入图片描述

在这里插入图片描述

小结

在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。

3.3订阅模式概述

订阅模式示例图:

在这里插入图片描述

前面2个案例中,只有3个角色:

  • P:生产者,也就是要发送消息的程序
  • C:消费者:消息的接受者,会一直等待消息到来。
  • queue:消息队列,图中红色部分

而在订阅模型中,多了一个exchange角色,而且过程略有变化:

P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
C:消费者,消息的接受者,会一直等待消息到来。
Queue:消息队列,接收消息、缓存消息。
Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:

  • Fanout:广播,将消息交给所有绑定到交换机的队列
  • Direct:定向,把消息交给符合指定routing key 的队列
  • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

3.4Publish/Subscribe发布与订阅模式

模式说明

在这里插入图片描述

发布订阅模式:

1、每个消费者监听自己的队列。

2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收 到消息

生产者

package com.sunzihan.rabbitmq.ps;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 17:13
 * 4 订阅和发布:交换机类型为fanout
 */
public class Producer {
    /**
     * 交换机与队列名称
     */
    public static final String FANOUT_EXCHANGE = "fanout_exchange";
    public static final String FANOUT_QUEUE_1 = "fanout_queue_1";
    public static final String FANOUT_QUEUE_2 = "fanout_queue_2";

    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        //声明交换机
        /*
         *参数1:交换机名称
         * 参数2:交换机类型
         */
        channel.exchangeDeclare(FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
        //声明队列
        /*
         *参数1:队列名称
         * 参数2:是否持久化队列
         * 参数3:是否独占连接
         * 参数4:是否自动删除
         * 参数5:其他参数
         */
        channel.queueDeclare(FANOUT_QUEUE_1, true, false, false, null);
        //交换机与队列绑定
        channel.queueBind(FANOUT_QUEUE_1, FANOUT_EXCHANGE, "");
        channel.queueBind(FANOUT_QUEUE_2, FANOUT_EXCHANGE, "");

        //发送消息
        for (int i = 0; i < 10; i++) {
            String message = "你好,小兔子---发布订阅模式--" + (i + 1);
            /*
             * 参数1:交换机名称,默认使用DeFault Exchange
             * 参数2:路由key,发布订阅模式不要写
             * 参数3:消息其他属性
             * 参数4:消息内容
             */

            channel.basicPublish(FANOUT_EXCHANGE, "", null, message.getBytes());
            System.out.println("已发送的内容:" + message);
        }

        //关闭资源
        channel.close();
        connection.close();
    }
}

消费者1

package com.sunzihan.rabbitmq.ps;

import com.rabbitmq.client.*;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 17:41
 * 4
 */
public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        //声明交换机
        channel.exchangeDeclare(Producer.FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
        //声明队列
        /*
         *参数1:队列名称
         * 参数2:是否持久化队列
         * 参数3:是否独占连接
         * 参数4:是否自动删除
         * 参数5:其他配置
         */
        channel.queueDeclare(Producer.FANOUT_QUEUE_1, true, false, false, null);
        //队列绑定交换机
        channel.queueBind(Producer.FANOUT_QUEUE_1, Producer.FANOUT_EXCHANGE, "");
        //设置消息处理
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             *
             * @param consumerTag 消息者标签,在channel.basicConsumer的时候可以配置
             * @param envelope 消息包的内容,可以从中获取消息id,消息的routing key,交换机.消息和重传的标志(收到消息失败后是否需要重新发送)
             * @param properties 属性消息
             * @param body 消息
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //消息内容
                System.out.println("消费者1接收的内容为:" + new String(body, "utf-8"));
            }
        };
        //获取消息
        /*
         *参数1:队列名称
         *参数2:是否自动确认,设置true为表示消息接收到自动向mq回复收到了,mq接收到回复自动删除消息,设置false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.FANOUT_QUEUE_1, true, consumer);
    }
}

消费者2

package com.sunzihan.rabbitmq.ps;

import com.rabbitmq.client.*;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 17:41
 * 4
 */
public class Consumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        //声明交换机
        channel.exchangeDeclare(Producer.FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
        //声明队列
        /*
         *参数1:队列名称
         * 参数2:是否持久化队列
         * 参数3:是否独占连接
         * 参数4:是否自动删除
         * 参数5:其他配置
         */
        channel.queueDeclare(Producer.FANOUT_QUEUE_2, true, false, false, null);
        //队列绑定交换机
        channel.queueBind(Producer.FANOUT_QUEUE_2, Producer.FANOUT_EXCHANGE, "");
        //设置消息处理
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             *
             * @param consumerTag 消息者标签,在channel.basicConsumer的时候可以配置
             * @param envelope 消息包的内容,可以从中获取消息id,消息的routing key,交换机.消息和重传的标志(收到消息失败后是否需要重新发送)
             * @param properties 属性消息
             * @param body 消息
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //消息内容
                System.out.println("消费者2接收的内容为:" + new String(body, "utf-8"));
            }
        };
        //获取消息
        /*
         *参数1:队列名称
         *参数2:是否自动确认,设置true为表示消息接收到自动向mq回复收到了,mq接收到回复自动删除消息,设置false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.FANOUT_QUEUE_2, true, consumer);
    }
}

测试

启动所有消费者,然后使用生产者发送消息;在每个消费者对应的控制台可以查看到生产者发送的所有消息;到达广播的效果。
在执行完测试代码后,其实到RabbitMQ的管理后台找到 Exchanges选项卡,点击 fanout_exchange的交换机,可以查看到如下的绑定:

在这里插入图片描述

小结

交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。
发布订阅模式与工作队列模式的区别
1、工作队列模式不用定义交换机,而发布/订阅模式需要定义交换机。
2、发布/订阅模式的生产方是面向交换机发送消息,工作队列模式的生产方是面向队列发送消息(底层使用默认交换机)。
3、发布/订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将队列绑 定到默认的交换机 。

3.5 Routing路由模式

模式说明

路由模式特点:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey(路由key)
  • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey。
  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key进行判断,只有队列的 Routingkey与消息的 Routing key完全一致,才会接收到消息

在这里插入图片描述

图解:

  • P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
  • X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
  • C1:消费者,其所在队列指定了需要routing key 为 error 的消息
  • C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息

示例

在这里插入图片描述

在编码上与 Publish/Subscribe发布与订阅模式 的区别是交换机的类型为:Direct,还有队列绑定交换机的时候需要指定routing key。

生产者

package com.sunzihan.rabbitmq.routing;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 18:31
 * 4    路由模式:与发布订阅模式差不多,只是在交换机绑定队列和发送消息的时候需要指定routing key
 */
public class Producer {
    /**
     * 交换机与队列名称
     */
    public static final String DIRECT_QUEUE_insert = "direct_queue_insert";
    public static final String DIRECT_QUEUE_update = "direct_queue_update";
    public static final String DIRECT_EXCHANGE = "direct_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        //创建队列
        /*
         *参数1:队列名称
         * 参数2:是否持久化队列
         * 参数3:是否独占连接
         * 参数4:是否自动删除
         * 参数5:队列其他配置
         */
        channel.queueDeclare(DIRECT_QUEUE_insert, true, false, false, null);
        channel.queueDeclare(DIRECT_QUEUE_update, true, false, false, null);
        //声明交换机
        /*
         *参数1:交换机名称
         * 参数2:交换机类型 fanout、direct、topic、headers
         */
        channel.exchangeDeclare(DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
        /*
         *队列绑定交换机
         * 参数1:队列名称
         * 参数2:交换机名称
         * 参数3:路由key
         */

            String message = "你好,兔子!!---路由模式-路由key=insert " ;
            //发送消息
            /*
             *参数1:交换机名称
             * 参数2:路由key
             * 参数3:消息其他配置
             * 参数4:消息
             */
            channel.basicPublish(DIRECT_EXCHANGE, "insert", null, message.getBytes());

            message = "你好,兔子!!---路由模式-路由key=update " ;
            channel.basicPublish(DIRECT_EXCHANGE, "update" , null, message.getBytes());


        //关闭资源
        channel.close();
        connection.close();
    }
}

消费者1

package com.sunzihan.rabbitmq.routing;

import com.rabbitmq.client.*;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 19:48
 * 4
 */
public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        //声明队列
        /*
         *参数1:队列名称
         * 参数2:是否持久化队列
         * 参数3:是否独占连接
         * 参数4:是否自动删除
         * 参数5:其他配置
         */
        channel.queueDeclare(Producer.DIRECT_QUEUE_insert,true,false,false,null);
        //声明交换机
        channel.exchangeDeclare(Producer.DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
        //交换机与队列绑定
        channel.queueBind(Producer.DIRECT_QUEUE_insert,Producer.DIRECT_EXCHANGE,"insert");
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             *
             * @param consumerTag 消息者标签,在channel.basicConsumer的时候可以配置
             * @param envelope 消息包的内容,可以从中获取消息id,消息的routing key,交换机.消息和重传的标志(收到消息失败后是否需要重新发送)
             * @param properties 属性消息
             * @param body 消息
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //消息内容
                System.out.println("消费者1接收的内容为:" + new String(body, "utf-8"));
            }
        };
        //获取消息
        /*
         *参数1:队列名称
         *参数2:是否自动确认,设置true为表示消息接收到自动向mq回复收到了,mq接收到回复自动删除消息,设置false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.DIRECT_QUEUE_insert,true,consumer);
    }
}

消费者2

package com.sunzihan.rabbitmq.routing;

import com.rabbitmq.client.*;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 19:48
 * 4
 */
public class Consumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        //声明队列
        /*
         *参数1:队列名称
         * 参数2:是否持久化队列
         * 参数3:是否独占连接
         * 参数4:是否自动删除
         * 参数5:其他配置
         */
        channel.queueDeclare(Producer.DIRECT_QUEUE_update,true,false,false,null);
        //声明交换机
        channel.exchangeDeclare(Producer.DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
        //交换机与队列绑定
        channel.queueBind(Producer.DIRECT_QUEUE_update,Producer.DIRECT_EXCHANGE,"update");
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             *
             * @param consumerTag 消息者标签,在channel.basicConsumer的时候可以配置
             * @param envelope 消息包的内容,可以从中获取消息id,消息的routing key,交换机.消息和重传的标志(收到消息失败后是否需要重新发送)
             * @param properties 属性消息
             * @param body 消息
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //消息内容
                System.out.println("消费者2接收的内容为:" + new String(body, "utf-8"));
            }
        };
        //获取消息
        /*
         *参数1:队列名称
         *参数2:是否自动确认,设置true为表示消息接收到自动向mq回复收到了,mq接收到回复自动删除消息,设置false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.DIRECT_QUEUE_update,true,consumer);
    }
}

测试

启动所有消费者,然后使用生产者发送消息;在消费者对应的控制台可以查看到生产者发送对应routing key对应队列的消息;到达按照需要接收的效果。
在执行完测试代码后,其实到RabbitMQ的管理后台找到 Exchanges选项卡,点击 direct_exchange的交换机,可以查看到如下的绑定:

在这里插入图片描述

小结

Routing模式要求队列在绑定交换机时要指定routing key,消息会转发到符合routing key的队列

3.6 Topics通配符模式

模式说明

opic类型与 Direct相比,都是可以根据 RoutingKey把消息路由到不同的队列。只不过 Topic类型Exchange可以让队列在绑定 Routing key 的时候使用通配符!
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:
#:匹配一个或多个词
*:匹配不多不少恰好1个词
举例:
item.#:能够匹配 item.insert.abc 或者 item.insert
item. *:只能匹配 item.insert
在这里插入图片描述

在这里插入图片描述

图解:

  • 红色Queue:绑定的是 usa.# ,因此凡是以 usa.开头的 routing key 都会被匹配到
  • 黄色Queue:绑定的是 #.news ,因此凡是以 .news结尾的 routing key 都会被匹配

示例

在这里插入图片描述

生产者

使用topic类型的Exchange,发送消息的routing key有3种: item.insert、 item.update、 item.delete:

package com.sunzihan.rabbitmq.topic;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 20:53
 * 4 通配符模式:与路由模式差不多,但更加灵活,多个routing key用.分割,如item.abc.topic
 * #:表示一个或多个
 * *:表示仅仅一个
 */
public class Producer {
    /**
     * 交换机与队列名称
     */
    public static final String TOPIC_QUEUE_1 = "topic_queue_1";
    public static final String TOPIC_QUEUE_2 = "topic_queue_2";
    public static final String TOPIC_EXCHANGE = "topic_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
       //创建队列,可以在消费者中创建队列,生产者仅发送消息
         /*
         *参数1:队列名称
         * 参数2:是否持久化队列
         * 参数3:是否独占连接
         * 参数4:是否自动删除
         * 参数5:队列其他配置
         */
//        channel.queueDeclare(TOPIC_QUEUE_insert, true, false, false, null);
//        channel.queueDeclare(TOPIC_QUEUE_update, true, false, false, null);
        //声明交换机

        /*
         *队列绑定交换机
         * 参数1:队列名称
         * 参数2:交换机名称
         * 参数3:路由key
         */

        String message = "新增了商品。Topic模式;routing key 为 item.insert ";
        //发送消息
        /*
         *参数1:交换机名称
         * 参数2:路由key
         * 参数3:消息其他配置
         * 参数4:消息
         */
        channel.basicPublish(TOPIC_EXCHANGE, "item.insert", null, message.getBytes());
        System.out.println("已发送消息:" + message);

        message = "修改了商品。Topic模式;routing key 为 item.update ";
        channel.basicPublish(TOPIC_EXCHANGE, "item.update", null, message.getBytes());
        System.out.println("已发送消息:" + message);

        message = "删除了商品。Topic模式;routing key 为 item.delete" ;
        channel.basicPublish(TOPIC_EXCHANGE, "item.delete", null, message.getBytes());
        System.out.println("已发送消息:" + message);


        //关闭资源
        channel.close();
        connection.close();
    }
}

消费者1

package com.sunzihan.rabbitmq.topic;

import com.rabbitmq.client.*;

import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 19:48
 * 4
 */
public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        //声明队列
        /*
         *参数1:队列名称
         * 参数2:是否持久化队列
         * 参数3:是否独占连接
         * 参数4:是否自动删除
         * 参数5:其他配置
         */
        channel.queueDeclare(Producer.TOPIC_QUEUE_1,true,false,false,null);
        //声明交换机
        channel.exchangeDeclare(Producer.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
        //交换机与队列绑定
        channel.queueBind(Producer.TOPIC_QUEUE_1, Producer.TOPIC_EXCHANGE,"item.*");
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             *
             * @param consumerTag 消息者标签,在channel.basicConsumer的时候可以配置
             * @param envelope 消息包的内容,可以从中获取消息id,消息的routing key,交换机.消息和重传的标志(收到消息失败后是否需要重新发送)
             * @param properties 属性消息
             * @param body 消息
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //消息内容
                System.out.println("消费者1接收的内容为:" + new String(body, "utf-8"));
            }
        };
        //获取消息
        /*
         *参数1:队列名称
         *参数2:是否自动确认,设置true为表示消息接收到自动向mq回复收到了,mq接收到回复自动删除消息,设置false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.TOPIC_QUEUE_1,true,consumer);
    }
}

消费者2

package com.sunzihan.rabbitmq.topic;

import com.rabbitmq.client.*;
import com.sunzihan.rabbitmq.utils.ConnectionUtil;

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

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/17 19:48
 * 4
 */
public class Consumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = ConnectionUtil.getConnection();
        //创建频道
        Channel channel = connection.createChannel();
        //声明队列
        /*
         *参数1:队列名称
         * 参数2:是否持久化队列
         * 参数3:是否独占连接
         * 参数4:是否自动删除
         * 参数5:其他配置
         */
        channel.queueDeclare(Producer.TOPIC_QUEUE_2,true,false,false,null);
        //声明交换机
        channel.exchangeDeclare(Producer.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
        //交换机与队列绑定
        channel.queueBind(Producer.TOPIC_QUEUE_2, Producer.TOPIC_EXCHANGE,"item.insert");
        channel.queueBind(Producer.TOPIC_QUEUE_2, Producer.TOPIC_EXCHANGE,"item.update");
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             *
             * @param consumerTag 消息者标签,在channel.basicConsumer的时候可以配置
             * @param envelope 消息包的内容,可以从中获取消息id,消息的routing key,交换机.消息和重传的标志(收到消息失败后是否需要重新发送)
             * @param properties 属性消息
             * @param body 消息
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //消息内容
                System.out.println("消费者2接收的内容为:" + new String(body, "utf-8"));
            }
        };
        //获取消息
        /*
         *参数1:队列名称
         *参数2:是否自动确认,设置true为表示消息接收到自动向mq回复收到了,mq接收到回复自动删除消息,设置false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.TOPIC_QUEUE_2,true,consumer);
    }
}

测试

启动所有消费者,然后使用生产者发送消息;在消费者对应的控制台可以查看到生产者发送对应routing key对应队列的消息;到达按照需要接收的效果;并且这些routing key可以使用通配符。
在执行完测试代码后,其实到RabbitMQ的管理后台找到 Exchanges选项卡,点击 topic_exchange的交换机,可以查看到如下的绑定:

在这里插入图片描述

小结

Topic主题模式可以实现 Publish/Subscribe发布与订阅模式 和 Routing路由模式 的功能;只是Topic在配置routing key 的时候可以使用通配符,显得更加灵活。

模式总结

RabbitMQ工作模式:

1、简单模式 HelloWorld 一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)
2、工作队列模式 Work Queue 一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)
3、发布订阅模式 Publish/subscribe 需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列
4、路由模式 Routing 需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
5、通配符模式 Topic 需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列

4、Spring 整合RabbitMQ

4.1、搭建生产者工程

添加依赖

修改pom.xml文件内容为如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sunzihan</groupId>
    <artifactId>spring-rabbitmq</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>2.2.11.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

配置整合

创建 spring-rabbitmq-producer\src\main\resources\properties\rabbitmq.properties连接参数等配置文件;

rabbitmq.host=192.168.216.128
rabbitmq.port=5672
rabbitmq.virtual-host=/szh
rabbitmq.username=szh
rabbitmq.password=szh

创建 spring-rabbitmq-producer\src\main\resources\spring\spring-rabbitmq.xml 整合配置文件;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/rabbit
        http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--    加载配置文件-->
    <context:property-placeholder location="classpath:properties/rabbitmq.properties"/>
<!--    定义rabbitmq connectionFactory-->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}" 
                               port="${rabbitmq.port}" 
                               virtual-host="${rabbitmq.virtual-host}" 
                               username="${rabbitmq.username}" 
                               password="${rabbitmq.password}"/>
<!--    定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>
    <!--定义持久化队列,,不存在则自动创建;不绑定到交换机则绑定到默认交换机
    默认交换机类型为direct,名字为:"",路由键为队列的名称-->
    <rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
    
<!--    广播:所有队列都能收到消息-->
<!--    定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>
<!--    定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
    
<!--    定义广播类型交换机:并绑定上述两个队列-->
    <rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding queue="spring_fanout_queue_1"/>
            <rabbit:binding queue="spring_fanout_queue_2"/>
        </rabbit:bindings>
    </rabbit:fanout-exchange>
<!--    通配符:*匹配一个单词,#匹配多个单词-->
<!--    定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
    <!--    定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
    <!--    定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>

    <rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true" auto-delete="true">
        <rabbit:bindings>
            <rabbit:binding pattern="szh.*" queue="spring_topic_queue_star"/>
            <rabbit:binding pattern="szh.#" queue="spring_topic_queue_well"/>
            <rabbit:binding pattern="jue.#" queue="spring_topic_queue_well2"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>

<!--    定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

发送消息

创建测试文件 spring-rabbitmq- producer\src\test\java\com\sunzihan\rabbitmq\ProducerTest.java

package com.sunzihan.rabbitmq;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/20 17:31
 * 4
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-rabbitmq.xml")
public class ProducerTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /*** 只发队列消息 * 默认交换机类型为 direct * 交换机的名称为空,路由键为队列的名称 */
    @Test
    public void queueTest() {
        //路由键与队列同名
        rabbitTemplate.convertAndSend("spring_queue", "", "只发队列sprig_queue的消息");

    }

    /**
     * 发送广播
     * 交换机类型为 fanout
     * 绑定到该交换机的所有队列都能够收到消息
     */
    @Test
    public void fanoutTest() {
        /*
         *参数1:交换机名称
         * 参数2:路由键名(广播设置为空)
         * 参数3:发送的消息内容
         */
        rabbitTemplate.convertAndSend("spring_fanout_exchange", "", "发送到spring_fanout_exchange交换机的广播消息");
    }

    /*** 通配符 * 交换机类型为 topic * 匹配路由键的通配符,*表示一个单词,#表示多个单词 * 绑定到该交换机的匹配队列能够收到对应消息 */
    @Test
    public void topicTest() {
        /* 参数1:交换机名称
        * 参数2:路由键名
        * 参数3:发送的消息内容 */
        rabbitTemplate.convertAndSend("spring_topic_exchange", "szh.bj", "发送到 spring_topic_exchange交换机szh.bj的消息");
        rabbitTemplate.convertAndSend("spring_topic_exchange", "szh.bj.1", "发送 到spring_topic_exchange交换机szh.bj.1的消息");
        rabbitTemplate.convertAndSend("spring_topic_exchange", "szh.bj.2", "发送 到spring_topic_exchange交换机szh.bj.2的消息");
        rabbitTemplate.convertAndSend("spring_topic_exchange", "jue.cn", "发送到 spring_topic_exchange交换机jue.cn的消息");
    }

}

4.2、搭建消费者工程

添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sunzihan</groupId>
    <artifactId>spring-rabbitmq-consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>2.2.11.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

</project>

配置整合

创建 spring-rabbitmq-consumer\src\main\resources\properties\rabbitmq.properties连接参数等配置文件;

rabbitmq.host=192.168.216.128
rabbitmq.port=5672
rabbitmq.virtual-host=/szh
rabbitmq.username=szh
rabbitmq.password=szh

创建 spring-rabbitmq-consumer\src\main\resources\spring\spring-rabbitmq.xml 整合配置文件;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
        http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--    加载配置文件-->
    <context:property-placeholder location="classpath:properties/rabbitmq.properties"/>
    <!--    定义rabbitmq connectionFactory-->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}" port="${rabbitmq.port}"
                               virtual-host="${rabbitmq.virtual-host}" username="${rabbitmq.username}"
                               password="${rabbitmq.password}"/>
    <bean id="springQueueListener" class="com.sunzihan.rabbitmq.listener.SpringQueueListener"/>
    <bean id="fanoutListener1" class="com.sunzihan.rabbitmq.listener.FanoutListener1"/>
    <bean id="fanoutListener2" class="com.sunzihan.rabbitmq.listener.FanoutListener2"/>
    <bean id="topicListenerStar" class="com.sunzihan.rabbitmq.listener.TopicListenerStar"/>
    <bean id="topicListenerWell" class="com.sunzihan.rabbitmq.listener.TopicListenerWell"/>
    <bean id="topicListenerWell2" class="com.sunzihan.rabbitmq.listener.TopicListenerWell2"/>
    <rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
        <rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
        <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>
        <rabbit:listener ref="fanoutListener2" queue-names="spring_fanout_queue_2"/>
        <rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>
        <rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>
        <rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>
    </rabbit:listener-container>
</beans>

消息监听器

1)队列监听器

创建 spring-rabbitmq- consumer\src\main\java\com\sunzihan\rabbitmq\listener\SpringQueueListener.java

package com.sunzihan.rabbitmq.listener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/20 18:45
 * 4
 */
public class SpringQueueListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        try {
            String msg = new String(message.getBody(), "utf-8");
            System.out.printf("接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
                    message.getMessageProperties().getReceivedExchange(),
                    message.getMessageProperties().getReceivedRoutingKey(),
                    message.getMessageProperties().getConsumerQueue(), msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2)广播监听器1

创建 spring-rabbitmq-consumer\src\main\java\com\sunzihan’\rabbitmq\listener\FanoutListener1.java

package com.sunzihan.rabbitmq.listener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/20 18:45
 * 4
 */
public class FanoutListener1 implements MessageListener {
    @Override
    public void onMessage(Message message) {
        try {
            String msg = new String(message.getBody(), "utf-8");
            System.out.printf("接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
                    message.getMessageProperties().getReceivedExchange(),
                    message.getMessageProperties().getReceivedRoutingKey(),
                    message.getMessageProperties().getConsumerQueue(), msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3)广播监听器2

创建 spring-rabbitmq-consumer\src\main\java\com\sunzihan\rabbitmq\listener\FanoutListener2.java

package com.sunzihan.rabbitmq.listener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/20 18:45
 * 4
 */
public class FanoutListener2 implements MessageListener {
    @Override
    public void onMessage(Message message) {
        try {
            String msg = new String(message.getBody(), "utf-8");
            System.out.printf("接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
                    message.getMessageProperties().getReceivedExchange(),
                    message.getMessageProperties().getReceivedRoutingKey(),
                    message.getMessageProperties().getConsumerQueue(), msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4)星号通配符监听器

创建 spring-rabbitmq- consumer\src\main\java\com\sunzihan\rabbitmq\listener\TopicListenerStar.java

package com.sunzihan.rabbitmq.listener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/20 18:45
 * 4
 */
public class TopicListenerStar implements MessageListener {
    @Override
    public void onMessage(Message message) {
        try {
            String msg = new String(message.getBody(), "utf-8");
            System.out.printf("接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
                    message.getMessageProperties().getReceivedExchange(),
                    message.getMessageProperties().getReceivedRoutingKey(),
                    message.getMessageProperties().getConsumerQueue(), msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5)井号通配符监听器

创建 spring-rabbitmq- consumer\src\main\java\com\sunzihan\rabbitmq\listener\TopicListenerWell.java

package com.sunzihan.rabbitmq.listener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/20 18:45
 * 4
 */
public class TopicListenerWell implements MessageListener {
    @Override
    public void onMessage(Message message) {
        try {
            String msg = new String(message.getBody(), "utf-8");
            System.out.printf("接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
                    message.getMessageProperties().getReceivedExchange(),
                    message.getMessageProperties().getReceivedRoutingKey(),
                    message.getMessageProperties().getConsumerQueue(), msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6)井号通配符监听器

创建 spring-rabbitmq- consumer\src\main\java\com\sunzihan\rabbitmq\listener\TopicListenerWell2.java

package com.sunzihan.rabbitmq.listener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/20 18:45
 * 4
 */
public class TopicListenerWell2 implements MessageListener {
    @Override
    public void onMessage(Message message) {
        try {
            String msg = new String(message.getBody(), "utf-8");
            System.out.printf("接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
                    message.getMessageProperties().getReceivedExchange(),
                    message.getMessageProperties().getReceivedRoutingKey(),
                    message.getMessageProperties().getConsumerQueue(), msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5、Spring Boot整合RabbitMQ

一般在开发过程中:

生产者工程:

  1. application.yml文件配置RabbitMQ相关信息;

  2. 在生产者工程中编写配置类,用于创建交换机和队列,并进行绑定

  3. 注入RabbitTemplate对象,通过RabbitTemplate对象发送消息到交换机

消费者工程:

  1. application.yml文件配置RabbitMQ相关信息

  2. 创建消息处理类,用于接收队列中的消息并进行处理

5.1、搭建生产者工程

添加依赖

pom.xml文件内容为如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sunzihan</groupId>
    <artifactId>springboot-rabbitmq-producer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

</project>

启动类

package com.sunzihan.rabbitmq;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/21 22:08
 * 4
 */
@SpringBootApplication
public class ProducerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProducerApplication.class);
    }
}

配置RabbitMQ

配置文件

创建application.yml,内容如下:

spring:
  rabbitmq:
    host: 192.168.216.128
    port: 5672
    virtual-host: /szh
    username: szh
    password: szh

绑定交换机和队列

创建RabbitMQ队列与交换机绑定的配置类com.sunzihan.rabbitmq.config.RabbitMQConfig

package com.sunzihan.rabbitmq.config;


import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/21 22:12
 * 4 配置类
 */
@Configuration
public class RabbitmqConfig {
/**
 * 交换机与队列名称
 *
 */
    public final static String ITEM_TOPIC_EXCHANGE = "springboot_item_topic_exchange";
    public final static String ITEM_QUEUE="springboot_item_queue";

    /**
     * 声明交换机
     *
     */
    @Bean
    public TopicExchange itemTopicExchange(){
        return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
    }
    /**
     * 声明队列
     */
    @Bean
    public Queue itemQueue(){
        return QueueBuilder.durable(ITEM_QUEUE).build();
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding itemQueueExchange(){
        return BindingBuilder.bind(itemQueue()).to( itemTopicExchange()).with("item.#");
    }
}

测试

在生产者工程springboot-rabbitmq-producer中创建测试类,发送消息:

package com.sunzihan.rabbitmq;

import com.sunzihan.rabbitmq.config.RabbitmqConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/21 22:40
 * 4
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public  void test(){
        rabbitTemplate.convertAndSend(RabbitmqConfig.ITEM_TOPIC_EXCHANGE,"item.insert","商品新增,routing key 为item.insert");
        rabbitTemplate.convertAndSend(RabbitmqConfig.ITEM_TOPIC_EXCHANGE,"item.update","商品修改,routing key 为item.update");
        rabbitTemplate.convertAndSend(RabbitmqConfig.ITEM_TOPIC_EXCHANGE,"item.delete","商品删除,routing key 为item.delete");
    }
}

先运行上述测试程序(交换机和队列才能先被声明和绑定),然后启动消费者;在消费者工程springboot-rabbitmq-consumer中控制台查看是否接收到对应消息。

另外;也可以在RabbitMQ的管理控制台中查看到交换机与队列的绑定:

在这里插入图片描述

5.2、搭建消费者工程

添加依赖

pom.xml文件内容为如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sunzihan</groupId>
    <artifactId>springboot-rabbitmq-consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

</project>

启动类

package com.sunzihan.rabbitmq;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/21 23:15
 * 4
 */
@SpringBootApplication
public class RabbitmqConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RabbitmqConsumerApplication.class);
    }
}

配置RabbitMQ

创建application.yml,内容如下:

spring:
  rabbitmq:
    host: 192.168.216.128
    port: 5672
    virtual-host: /szh
    username: szh
    password: szh

消息监听处理类

编写消息监听器com.sunzihan.rabbitmq.listener.MyListener

package com.sunzihan.rabbitmq.listener;


import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * 2  @Author: sunzihan
 * 3  @Date: 2022/2/21 23:21
 * 4
 */
@Component
public class MyListener {

    @RabbitListener(queues = "springboot_item_queue")
    public void  myListener1(String message){
        System.out.println("消费者接收到的消息为:"+message);
    }
}

6、高级特性

6.1、消息的可靠投递

在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我

们提供了两种方式用来控制消息的投递可靠性模式。

  • confirm 确认模式
  • return 退回模式

rabbitmq 整个消息投递的路径为:

producer—>rabbitmq broker—>exchange—>queue—>consum

  • 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
  • 消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。

我们将利用这两个 callback 控制消息的可靠性投递

确认模式

消息从 producer 到 exchange 则会返回一个 confirmCallback

/*确认模式: * 步骤:
     * * 1. 确认模式开启:ConnectionFactory中开启publisher-confirms="true"
     * * 2. 在rabbitTemplate定义ConfirmCallBack回调函数
     * */
    @Test
    public void testConfirm() {
        //2. 定义回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**** @param correlationData 相关配置信息
             * * @param ack exchange交换机 是否成功收到了消息。true 成功,false代表失败
             * * @param cause 失败原因 */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm方法被执行了....");
                if (ack) {
                    //接收成功
                    System.out.println("接收成功消息" + cause); } else {
                    //接收失败
                    System.out.println("接收失败消息" + cause);
                    //做一些处理,让消息再次发送。
                    }
            }
        });

        //3. 发送消息
        rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");}

退回模式

消息从 exchange–>queue 投递失败则会返回一个 returnCallback

/*** 回退模式: 当消息发送给Exchange后,Exchange路由到Queue失败是 才会执行 ReturnCallBack
* 步骤: 
* 1. 开启回退模式:publisher-returns="true"
* 2. 设置ReturnCallBack 
* 3. 设置Exchange处理消息失败的模式:setMandatory 

* 1. 如果消息没有路由到Queue,则丢弃消息(默认) 
* 2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack */
@Test 
public void testReturn() {
    //设置交换机处理失败消息的模式
    rabbitTemplate.setMandatory(true); 
    //2.设置ReturnCallBack 
    rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() { 
        /**
        *
        * @param message 消息对象
        * @param replyCode 错误码 
        * @param replyText 错误信息
        * @param exchange 交换机 
        * @param routingKey 路由键 
        */
        @Override 
        public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { 
            System.out.println("return 执行了....");
            System.out.println(message);
            System.out.println(replyCode);
            System.out.println(replyText); 
            System.out.println(exchange);
            System.out.println(routingKey); 
            
            //处理 
        } 
    });
    
    //3. 发送消息 
    rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm234", "message confirm...."); }
6.2 、Consumer Ack

ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。

有三种确认方式:

  • 自动确认:acknowledge=“none”

  • 手动确认:acknowledge=“manual”

  • 根据异常情况确认:acknowledge=“auto”,(这种方式使用麻烦,不作讲解)

其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

package com.sun.zihan.listener; 

import com.rabbitmq.client.Channel; 
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; 
import org.springframework.stereotype.Component; 
import java.io.IOException;
/*** Consumer ACK机制: 
* 1. 设置手动签收。acknowledge="manual" 
* 2. 让监听器类实现ChannelAwareMessageListener接口 
* 3. 如果消息成功处理,则调用channel的 basicAck()签收 
* 4. 如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer 
*/ 
@Component
public class AckListener implements ChannelAwareMessageListener {
    @Override 
    public void onMessage(Message message, Channel channel) throws Exception { 
        long deliveryTag = message.getMessageProperties().getDeliveryTag(); 
        try {
            //1.接收转换消息 
            System.out.println(new String(message.getBody()));
            //2. 处理业务逻辑 
            System.out.println("处理业务逻辑..."); 
            int i = 3/0;//出现错误
            //3. 手动签收 
            channel.basicAck(deliveryTag,true); 
        } catch (Exception e) {
            //e.printStackTrace(); 
            //4.拒绝签收 
            /*第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会 重新发送该消息给消费端 */ 
            channel.basicNack(deliveryTag,true,true); 
            //channel.basicReject(deliveryTag,true);
        }
    }
}
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual"> .....
6.3、消费端限流

在这里插入图片描述

package com.sunzihan.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; import org.springframework.stereotype.Component;
/**
* Consumer 限流机制
* 1. 确保ack机制为手动确认。 
* 2. listener-container配置属性 
* perfetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续 拉去下一条消息。
*/
@Component
public class QosListener implements ChannelAwareMessageListener {
    @Override 
    public void onMessage(Message message, Channel channel) throws Exception {
        // Thread.sleep(1000);
        //1.获取消息 
        
        //2. 处理业务逻辑 
        //3. 签收 
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    } 
}
6.4、TTL

Time To Live,消息过期时间设置

管控台中设置队列TTL

在这里插入图片描述

代码实现

配置文件

<!--ttl--> 
<rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
    <!--设置queue的参数--> 
    <rabbit:queue-arguments> 
        <!--x-message-ttl指队列的过期时间-->
        <entry key="x-message-ttl" value="100000" value- type="java.lang.Integer"></entry> 
    </rabbit:queue-arguments> 
</rabbit:queue>

<rabbit:topic-exchange name="test_exchange_ttl" > 
    <rabbit:bindings> 
        <rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding> 
    </rabbit:bindings> 
</rabbit:topic-exchange>

代码

/*** TTL:过期时间 
* 1. 队列统一过期 
** 2. 消息单独过期 
*** 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
* 队列过期后,会将队列所有消息全部移除。
* 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)
**/
@Test
public void testTtl() {
    /* for (int i = 0; i < 10; i++) { 
    // 发送消息 
    rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl...."); 
    }*/ 
    // 消息后处理对象,设置一些消息的参数信息
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() { 
        @Override 
        public Message postProcessMessage(Message message) throws AmqpException { 
        //1.设置message的信息 
            message.getMessageProperties().setExpiration("5000");//消息的过期 时间 
            //2.返回该消息 
            return message; 
        }
    };
    
    //消息单独过期 
    rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....", messagePostProcessor); 
    // for (int i = 0; i < 10; i++) { 
    // if(i == 5){
    // //消息单独过期 
    // rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor); 
    // }else{ 
    // 
    //不过期的消息 
    // rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl...."); 
    // } 
    // }
}
6.5、死信队列

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Deadmessage后,可以被重新发送到另一个交换机,这个交换机就是DLX。

在这里插入图片描述

  1. 队列消息长度到达限制;
  2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
  3. 原队列存在消息过期设置,消息到达超时时间未被消费;

消息成为死信的三种情况:

  1. 队列消息长度到达限制;

  2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;

  3. 原队列存在消息过期设置,消息到达超时时间未被消费;

队列绑定死信交换机:

给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key

在这里插入图片描述

代码实现

配置

<!--1. 声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx) --> 
<rabbit:queue name="test_queue_dlx" id="test_queue_dlx"> 
    <!--3. 正常队列绑定死信交换机--> 
    <rabbit:queue-arguments> 
        <!--3.1 x-dead-letter-exchange:死信交换机名称--> 
        <entry key="x-dead-letter-exchange" value="exchange_dlx" /> 
        <!--3.2 x-dead-letter-routing-key:发送给死信交换机的routingkey--> 
        <entry key="x-dead-letter-routing-key" value="dlx.hehe" />
        <!--4.1 设置队列的过期时间 ttl--> 
        <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" /> 
        <!--4.2 设置队列的长度限制 max-length -->
        <entry key="x-max-length" value="10" value-type="java.lang.Integer" /> 
    </rabbit:queue-arguments> 
</rabbit:queue> 
<rabbit:topic-exchange name="test_exchange_dlx">
    <rabbit:bindings> 
        <rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"> </rabbit:binding> 
    </rabbit:bindings>
</rabbit:topic-exchange> 

<!--2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx) -->
<rabbit:queue name="queue_dlx" id="queue_dlx"></rabbit:queue> 
<rabbit:topic-exchange name="exchange_dlx"> 
    <rabbit:bindings> 
        <rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding> 
    </rabbit:bindings> 
</rabbit:topic-exchange>

测试代码

生产端测试

/*** 发送测试死信消息: 
* 1. 过期时间 
* 2. 长度限制 
* 3. 消息拒收 
*/
@Test
public void testDlx(){
    //1. 测试过期时间,死信消息
    //rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条 消息,我会死吗?"); 
    //2. 测试长度限制后,消息死信 
    /* for (int i = 0; i < 20; i++) { 
    	rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一 条消息,我会死吗?"); 
    	}*/ 
    //3. 测试消息拒收 
    rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条消 息,我会死吗?"); 
}

消费端监听

package com.sunzihan.listener;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

@Component 
public class DlxListener implements ChannelAwareMessageListener { 
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag(); 
        try {
            //1.接收转换消息 
            System.out.println(new String(message.getBody())); 
            //2. 处理业务逻辑
            System.out.println("处理业务逻辑..."); 
            int i = 3/0;//出现错误 
            //3. 手动签收 
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            //e.printStackTrace(); 
            System.out.println("出现异常,拒绝接受"); 
            //4.拒绝签收,不重回队列requeue=false
            channel.basicNack(deliveryTag,true,false);
        } 
    } 
}
<rabbit:listener ref="dlxListener" queue-names="test_queue_dlx"> </rabbit:listener>

6.6、延迟队列

延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。

需求:

  1. 下单后,30分钟未支付,取消订单,回滚库存。

  2. 新用户注册成功7天后,发送短信问候。

实现方式:

  1. 定时器

  2. 延迟队列

在这里插入图片描述

很可惜,在RabbitMQ中并未提供延迟队列功能。

但是可以使用:TTL+死信队列 组合实现延迟队列的效果。

在这里插入图片描述

代码实现

配置

<!--延迟队列:
 1. 定义正常交换机(order_exchange)和队列(order_queue) 
2. 定义死信交换机(order_exchange_dlx)和队列(order_queue_dlx)
 3. 绑定,设置正常队列过期时间为30分钟 
--> 
<!-- 1. 定义正常交换机(order_exchange)和队列(order_queue)--> 
<rabbit:queue id="order_queue" name="order_queue"> 
    <!-- 3. 绑定,设置正常队列过期时间为30分钟--> 
    <rabbit:queue-arguments> 
        <entry key="x-dead-letter-exchange" value="order_exchange_dlx" /> 
        <entry key="x-dead-letter-routing-key" value="dlx.order.cancel" /> 
        <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
    </rabbit:queue-arguments>
</rabbit:queue> 
<rabbit:topic-exchange name="order_exchange"> 
    <rabbit:bindings>
        <rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
    </rabbit:bindings>
</rabbit:topic-exchange> 
<!-- 2. 定义死信交换机(order_exchange_dlx)和队列(order_queue_dlx)--> 
<rabbit:queue id="order_queue_dlx" name="order_queue_dlx"></rabbit:queue> 
<rabbit:topic-exchange name="order_exchange_dlx"> 
    <rabbit:bindings> 
        <rabbit:binding pattern="dlx.order.#" queue="order_queue_dlx"> 
        </rabbit:binding> 
    </rabbit:bindings>
</rabbit:topic-exchange>

生产端测试

@Test
public void testDelay() throws InterruptedException { 
    //1.发送订单消息。 将来是在订单系统中,下单成功后,发送消息
    rabbitTemplate.convertAndSend("order_exchange","order.msg","订单信息: id=1,time=2019年8月17日16:41:47");
    /*//2.打印倒计时10秒 
    for (int i = 10; i > 0 ; i--) { 
    System.out.println(i+"..."); 
    Thread.sleep(1000); 
    }*/ 
}

消费端监听

package com.sunzihan.listener; 

import com.rabbitmq.client.Channel; 
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; 
import org.springframework.stereotype.Component; 

@Component 
public class OrderListener implements ChannelAwareMessageListener { 
    @Override 
    public void onMessage(Message message, Channel channel) throws Exception { 
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            //1.接收转换消息
            System.out.println(new String(message.getBody())); 
            //2. 处理业务逻辑 
            System.out.println("处理业务逻辑..."); 
            System.out.println("根据订单id查询其状态..."); 
            System.out.println("判断状态是否为支付成功"); 
            System.out.println("取消订单,回滚库存...."); 
            //3. 手动签收 
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) { 
            //e.printStackTrace(); 
            System.out.println("出现异常,拒绝接受"); 
            //4.拒绝签收,不重回队列 requeue=false
            channel.basicNack(deliveryTag,true,false);
        } 
    } 
}
<rabbit:listener ref="orderListener" queue-names="order_queue_dlx"> </rabbit:listener>

7、RabbitMQ 集群两种模式

说到集群,小伙伴们可能第一个问题是,如果我有一个 RabbitMQ 集群,那么是不是我的消息集群中的每一个实例都保存一份呢?

这其实就涉及到 RabbitMQ 集群的两种模式:

  • 普通集群
  • 镜像集群
7.1、普通集群

普通集群模式,就是将 RabbitMQ 部署到多台服务器上,每个服务器启动一个 RabbitMQ 实例,多个实例之间进行消息通信。

此时我们创建的队列 Queue,它的元数据(主要就是 Queue 的一些配置信息)会在所有的 RabbitMQ 实例中进行同步,但是队列中的消息只会存在于一个 RabbitMQ 实例上,而不会同步到其他队列。

当我们消费消息的时候,如果连接到了另外一个实例,那么那个实例会通过元数据定位到 Queue 所在的位置,然后访问 Queue 所在的实例,拉取数据过来发送给消费者。

这种集群可以提高 RabbitMQ 的消息吞吐能力,但是无法保证高可用,因为一旦一个 RabbitMQ 实例挂了,消息就没法访问了,如果消息队列做了持久化,那么等 RabbitMQ 实例恢复后,就可以继续访问了;如果消息队列没做持久化,那么消息就丢了。

大致的流程图如下图:
在这里插入图片描述

7.2、镜像集群

它和普通集群最大的区别在于 Queue 数据和原数据不再是单独存储在一台机器上,而是同时存储在多台机器上。也就是说每个 RabbitMQ 实例都有一份镜像数据(副本数据)。每次写入消息的时候都会自动把数据同步到多台实例上去,这样一旦其中一台机器发生故障,其他机器还有一份副本数据可以继续提供服务,也就实现了高可用。

大致流程图如下图:

在这里插入图片描述

7.3 、节点类型

RabbitMQ 中的节点类型有两种:

  • RAM node:内存节点将所有的队列、交换机、绑定、用户、权限和 vhost 的元数据定义存储在内存中,好处是可以使得交换机和队列声明等操作速度更快。

  • Disk node:将元数据存储在磁盘中,单节点系统只允许磁盘类型的节点,防止重启 RabbitMQ 的时候,丢失系统的配置信息

RabbitMQ 要求在集群中至少有一个磁盘节点,所有其他节点可以是内存节点,当节点加入或者离开集群时,必须要将该变更通知到至少一个磁盘节点。如果集群中唯一的一个磁盘节点崩溃的话,集群仍然可以保持运行,但是无法进行其他操作(增删改查),直到节点恢复。为了确保集群信息的可靠性,或者在不确定使用磁盘节点还是内存节点的时候,建议直接用磁盘节点。

7.4、搭建普通集群

预备知识

大致的结构了解了,接下来我们就把集群给搭建起来。先从普通集群开始,我们就使用 docker 来搭建。

搭建之前,有两个预备知识需要大家了解:

  1. 搭建集群时,节点中的 Erlang Cookie 值要一致,默认情况下,文件在 /var/lib/rabbitmq/.erlang.cookie,我们在用 docker 创建 RabbitMQ 容器时,可以为之设置相应的 Cookie 值。
  2. RabbitMQ 是通过主机名来连接服务,必须保证各个主机名之间可以 ping 通。可以通过编辑 /etc/hosts 来手工添加主机名和 IP 对应关系。如果主机名 ping 不通,RabbitMQ 服务启动会失败(如果我们是在不同的服务器上搭建 RabbitMQ 集群,大家需要注意这一点,接下来我们将通过 Docker 的容器连接 link 来实现容器之间的访问,略有不同)。

开始搭建

执行如下命令创建三个 RabbitMQ 容器:

docker run -d --hostname rabbit01 --name mq01 -p 5671:5672 -p 15671:15672 -e RABBITMQ_ERLANG_COOKIE="javaboy_rabbitmq_cookie" rabbitmq:3-management
docker run -d --hostname rabbit02 --name mq02 --link mq01:mylink01 -p 5672:5672 -p 15672:15672 -e RABBITMQ_ERLANG_COOKIE="javaboy_rabbitmq_cookie" rabbitmq:3-management
docker run -d --hostname rabbit03 --name mq03 --link mq01:mylink02 --link mq02:mylink03 -p 5673:5672 -p 15673:15672 -e RABBITMQ_ERLANG_COOKIE="javaboy_rabbitmq_cookie" rabbitmq:3-management

三个节点现在就启动好了,注意在 mq02 和 mq03 中,分别使用了 --link 参数来实现容器连接,关于这个参数,如果不懂,查一下。另外还需要注意,mq03 容器中要既能够连接 mq01 也能够连接 mq02。

接下来进入到 mq02 容器中,首先查看一下 hosts 文件,可以看到我们配置的容器连接已经生效了:

docker exec -ti mq02 /bin/bash
cat ect/hosts

将来在 mq02 容器中,就可以通过 mylink01 或者 rabbit01 访问到 mq01 容器了。

接下来我们开始集群的配置。

分别执行如下命令将 mq02 容器加入集群中:

rabbitmqctl stop_app
rabbitmqctl join_cluster rabbit@rabbit01
rabbitmqctl start_app

接下来输入如下命令我们可以查看集群的状态:

rabbitmqctl cluster_status

可以看到,集群中已经有两个节点了。

接下来通过相同的方式将 mq03 也加入到集群中:

rabbitmqctl stop_app
rabbitmqctl join_cluster rabbit@rabbit01
rabbitmqctl start_app

可以看到,此时集群中已经有三个节点了。

其实,这个时候,我们也可以通过网页来查看集群信息,在三个 RabbitMQ 实例的 Web 端首页,都可以看到如下内容:

在这里插入图片描述

代码测试

接下来我们来简单测试一下这个集群。

我们创建一个名为 mq_cluster_demo 的父工程,然后在其中创建两个子工程。

第一个子工程名为 provider,是一个消息生产者,创建时引入 Web 和 RabbitMQ 依赖,如下:

在这里插入图片描述

然后配置 applicaiton.properties,内容如下(注意集群配置):

spring.rabbitmq.addresses=localhost:5671,localhost:5672,localhost:5673
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

接下来提供一个简单的队列,如下:

@Configuration
public class RabbitConfig {
    public static final String MY_QUEUE_NAME = "my_queue_name";
    public static final String MY_EXCHANGE_NAME = "my_exchange_name";
    public static final String MY_ROUTING_KEY = "my_queue_name";

    @Bean
    Queue queue() {
        return new Queue(MY_QUEUE_NAME, true, false, false);
    }

    @Bean
    DirectExchange directExchange() {
        return new DirectExchange(MY_EXCHANGE_NAME, true, false);
    }

    @Bean
    Binding binding() {
        return BindingBuilder.bind(queue())
                .to(directExchange())
                .with(MY_ROUTING_KEY);
    }
}

这个没啥好说的,都是基本内容,接下来我们在单元测试中进行消息发送测试:

@SpringBootTest
class ProviderApplicationTests {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    void contextLoads() {
        rabbitTemplate.convertAndSend(null, RabbitConfig.MY_QUEUE_NAME, "hello 江南一点雨");
    }

}

这条消息发送成功之后,在 RabbitMQ 的 Web 管理端,我们会看到三个 RabbitMQ 实例上都会显示有一条消息,但是实际上消息本身只存在于一个 RabbitMQ 实例。

接下来我们再创建一个消息消费者,消息消费者的依赖以及配置和消息生产者都是一模一样,我就不重复了,消息消费者中增加一个消息接收器:

@Component
public class MsgReceiver {

    @RabbitListener(queues = RabbitConfig.MY_QUEUE_NAME)
    public void handleMsg(String msg) {
        System.out.println("msg = " + msg);
    }
}

当消息消费者启动成功后,这个方法中只收到一条消息,进一步验证了我们搭建的 RabbitMQ 集群是没问题的。

反向测试

确保三个 RabbitMQ 实例都是启动状态,关闭掉 Consumer,然后通过 provider 发送一条消息,发送成功之后,关闭 mq01 实例,然后启动 Consumer 实例,此时 Consumer 实例并不会消费消息,反而会报错说 mq01 实例连接不上,这个例子就可以说明消息在 mq01 上,并没有同步到另外两个 MQ 上。相反,如果 provider 发送消息成功之后,我们没有关闭 mq01 实例而是关闭了 mq02 实例,那么你就会发现消息的消费不受影响。

7.5搭建镜像集群

所谓的镜像集群模式并不需要额外搭建,只需要我们将队列配置为镜像队列即可。

这个配置可以通过网页配置,也可以通过命令行配置,我们分别来看。

网页配置镜像队列

先来看看网页上如何配置镜像队列。

点击 Admin 选项卡,然后点击右边的 Policies,再点击 Add/update a policy,如下图:

在这里插入图片描述

接下来添加一个策略,如下图:

在这里插入图片描述

各参数含义如下:

  • Name: policy 的名称。

  • Pattern: queue 的匹配模式(正则表达式)。

  • Definition:镜像定义,主要有三个参数:ha-mode, ha-params, ha-sync-mode。

    • ha-mode:指明镜像队列的模式,有效值为 all、exactly、nodes。其中 all 表示在集群中所有的节点上进行镜像(默认即此);exactly 表示在指定个数的节点上进行镜像,节点的个数由 ha-params 指定;nodes 表示在指定的节点上进行镜像,节点名称通过 ha-params 指定。
    • ha-params:ha-mode 模式需要用到的参数。
    • ha-sync-mode:进行队列中消息的同步方式,有效值为 automatic 和 manual
  • priority 为可选参数,表示 policy 的优先级。

配置完成后,点击下面的 add/update policy 按钮,完成策略的添加,如下:

在这里插入图片描述

添加完成后,我们可以进行一个简单的测试。

首先确认三个 RabbitMQ 都启动了,然后用上面的 provider 向消息队列发送一条消息。

发完之后关闭 mq01 实例。

接下来启动 consumer,此时发现 consumer 可以完成消息的消费(注意和前面的反向测试区分),这就说明镜像队列已经搭建成功了。

命令行配置镜像队列

命令行的配置格式如下:

rabbitmqctl set_policy [-p vhost] [--priority priority] [--apply-to apply-to] {name} {pattern} {definition}

举一个简单的配置案例:

rabbitmqctl set_policy -p / --apply-to queues my_queue_mirror "^" '{"ha-mode":"all","ha-sync-mode":"automatic"}'

c static final String MY_QUEUE_NAME = "my_queue_name";
    public static final String MY_EXCHANGE_NAME = "my_exchange_name";
    public static final String MY_ROUTING_KEY = "my_queue_name";

    @Bean
    Queue queue() {
        return new Queue(MY_QUEUE_NAME, true, false, false);
    }

    @Bean
    DirectExchange directExchange() {
        return new DirectExchange(MY_EXCHANGE_NAME, true, false);
    }

    @Bean
    Binding binding() {
        return BindingBuilder.bind(queue())
                .to(directExchange())
                .with(MY_ROUTING_KEY);
    }
}

这个没啥好说的,都是基本内容,接下来我们在单元测试中进行消息发送测试:

@SpringBootTest
class ProviderApplicationTests {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    void contextLoads() {
        rabbitTemplate.convertAndSend(null, RabbitConfig.MY_QUEUE_NAME, "hello 江南一点雨");
    }

}

这条消息发送成功之后,在 RabbitMQ 的 Web 管理端,我们会看到三个 RabbitMQ 实例上都会显示有一条消息,但是实际上消息本身只存在于一个 RabbitMQ 实例。

接下来我们再创建一个消息消费者,消息消费者的依赖以及配置和消息生产者都是一模一样,我就不重复了,消息消费者中增加一个消息接收器:

@Component
public class MsgReceiver {

    @RabbitListener(queues = RabbitConfig.MY_QUEUE_NAME)
    public void handleMsg(String msg) {
        System.out.println("msg = " + msg);
    }
}

当消息消费者启动成功后,这个方法中只收到一条消息,进一步验证了我们搭建的 RabbitMQ 集群是没问题的。

反向测试

确保三个 RabbitMQ 实例都是启动状态,关闭掉 Consumer,然后通过 provider 发送一条消息,发送成功之后,关闭 mq01 实例,然后启动 Consumer 实例,此时 Consumer 实例并不会消费消息,反而会报错说 mq01 实例连接不上,这个例子就可以说明消息在 mq01 上,并没有同步到另外两个 MQ 上。相反,如果 provider 发送消息成功之后,我们没有关闭 mq01 实例而是关闭了 mq02 实例,那么你就会发现消息的消费不受影响。

7.5搭建镜像集群

所谓的镜像集群模式并不需要额外搭建,只需要我们将队列配置为镜像队列即可。

这个配置可以通过网页配置,也可以通过命令行配置,我们分别来看。

网页配置镜像队列

先来看看网页上如何配置镜像队列。

点击 Admin 选项卡,然后点击右边的 Policies,再点击 Add/update a policy,如下图:

在这里插入图片描述

接下来添加一个策略,如下图:

在这里插入图片描述

各参数含义如下:

  • Name: policy 的名称。

  • Pattern: queue 的匹配模式(正则表达式)。

  • Definition:镜像定义,主要有三个参数:ha-mode, ha-params, ha-sync-mode。

    • ha-mode:指明镜像队列的模式,有效值为 all、exactly、nodes。其中 all 表示在集群中所有的节点上进行镜像(默认即此);exactly 表示在指定个数的节点上进行镜像,节点的个数由 ha-params 指定;nodes 表示在指定的节点上进行镜像,节点名称通过 ha-params 指定。
    • ha-params:ha-mode 模式需要用到的参数。
    • ha-sync-mode:进行队列中消息的同步方式,有效值为 automatic 和 manual
  • priority 为可选参数,表示 policy 的优先级。

配置完成后,点击下面的 add/update policy 按钮,完成策略的添加,如下:

在这里插入图片描述

添加完成后,我们可以进行一个简单的测试。

首先确认三个 RabbitMQ 都启动了,然后用上面的 provider 向消息队列发送一条消息。

发完之后关闭 mq01 实例。

接下来启动 consumer,此时发现 consumer 可以完成消息的消费(注意和前面的反向测试区分),这就说明镜像队列已经搭建成功了。

命令行配置镜像队列

命令行的配置格式如下:

rabbitmqctl set_policy [-p vhost] [--priority priority] [--apply-to apply-to] {name} {pattern} {definition}

举一个简单的配置案例:

rabbitmqctl set_policy -p / --apply-to queues my_queue_mirror "^" '{"ha-mode":"all","ha-sync-mode":"automatic"}'

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值