RabbitMQ基础

MQ 的基本概念

MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。

在这里插入图片描述在这里插入图片描述
小结:

  • MQ,消息队列,存储消息的中间件
  • 分布式系统通信两种方式:直接远程调用 和 借助第三方 完成间接通信
  • 发送方称为生产者,接收方称为消费者

优势:

  • 应用解耦
  • 异步提速
  • 削峰填谷

劣势:

  • 系统可用性降低
  • 系统复杂度提高
  • 一致性问题
MQ优势:
1. 应用解耦

在这里插入图片描述
系统的耦合性越高,容错性就越低,可维护性就越低。
在这里插入图片描述
使用 MQ 使得应用间解耦,提升容错性和可维护性。

2. 异步提速

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

3. 削峰填谷

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

使用了 MQ 之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在 MQ 中,高峰就被“削”掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做“填谷”。

使用MQ后,可以提高系统稳定性。

小结:

  • 应用解耦:提高系统容错性和可维护性
  • 异步提速:提升用户体验和系统吞吐量
  • 削峰填谷:提高系统稳定性
MQ劣势:

在这里插入图片描述

1、系统可用性降低

系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。如何保证MQ的高可用?

2、系统复杂度提高

MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何
保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?

3、一致性问题

A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理
失败。如何保证消息数据处理的一致性?

小结:
既然 MQ 有优势也有劣势,那么使用 MQ 需要满足什么条件呢?

① 生产者不需要从消费者处获得反馈。引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明
明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能。
② 容许短暂的不一致性。
③ 确实是用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本。

常见的 MQ 产品

目前业界有很多的 MQ 产品,例如 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,也有直接使用 Redis 充当消息队列的案例,而这些消息队列产品,各有侧重,在实际选型时,需要结合自身需求及 MQ 产品特征,综合考虑。

在这里插入图片描述
由于 RabbitMQ 综合能力强劲,所以我们将主要学习 RabbitMQ。

RabbitMQ 简介

AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP 规范发布。类比HTTP。

在这里插入图片描述

2007年,Rabbit 技术公司基于 AMQP 标准开发的 RabbitMQ 1.0 发布。RabbitMQ 采用 Erlang 语言开发。Erlang 语言由 Ericson 设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。

RabbitMQ 基础架构如下图:
在这里插入图片描述

RabbitMQ 中的相关概念:

  • Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
  • Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
  • Connection:publisher/consumer 和 broker 之间的 TCP 连接
  • Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection,极大减少了操作系统建立 TCP connection 的开销

RabbitMQ 中的相关概念:

  • Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
  • Queue:消息最终被送到这里等待 consumer 取走
  • Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据

RabbitMQ 提供了 6 种工作模式:简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing路由模式、Topics 主题模式、RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
在这里插入图片描述

JMS
  • JMS 即 Java 消息服务(JavaMessage Service)应用程序接口,是一个 Java 平台中关于面向消息中间件的API(java程序和消息对列通信的一套接口)
  • JMS 是 JavaEE 规范中的一种,类比JDBC
  • 很多消息中间件都实现了JMS规范,例如:ActiveMQ。RabbitMQ 官方没有提供 JMS 的实现包,但是开源社区有

小结:

  1. RabbitMQ 是基于 AMQP 协议使用 Erlang 语言开发的一款消息队列产品。
  2. RabbitMQ提供了6种工作模式,我们学习5种。这是今天的重点。
  3. AMQP 是协议,类比HTTP。
  4. JMS 是 API 规范接口,类比 JDBC。

RabbitMQ 的安装和配置

1. 安装依赖环境

在线安装依赖环境:

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

2. 安装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 sqlite-devel readline-devel tk-devel gcc make -y
    
  • 下载rpm包

    wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepo-el6/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/myrepo-el6/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/myrepo-el6/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/myrepo-el6/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/myrepo-el6/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/myrepo-el6/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/myrepo-el6/epel-6-x86_64/glibc-2.17-55.fc20/nscd-2.17-55.el6.x86_64.rpm &
    
  • 安装rpm包

    sudo rpm -Uvh *-2.17-55.el6.x86_64.rpm --force --nodeps
    
  • 安装完毕后再查看glibc版本,发现glibc版本已经到2.17了

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

在这里插入图片描述

3. 安装RabbitMQ

# 安装
rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm

# 安装
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm

4. 开启管理界面及配置

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

5. 启动

# 启动服务
service rabbitmq-server start 或者
systemctl restart rabbitmq-server
# 停止服务
service rabbitmq-server stop 或者
systemctl stop rabbitmq-server
 # 重启服务
service rabbitmq-server restart 或者
systemctl restart rabbitmq-server
  • 设置配置文件
cd /usr/share/doc/rabbitmq-server-3.6.5/

cp rabbitmq.config.example /etc/rabbitmq/rabbitmq.config

6. 配置虚拟主机及用户

6.1. 用户角色

RabbitMQ在安装好后,可以访问http://ip地址:15672 ;其自带了guest/guest的用户名和密码;如果需要创建自定义用户;那么也可以登录管理界面后,如下操作:

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

角色说明

1、 超级管理员(administrator)

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

2、 监控者(monitoring)

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

3、 策略制定者(policymaker)

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

4、 普通管理者(management)

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

5、 其他

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

6.2. Virtual Hosts配置

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

6.2.1. 创建Virtual Hosts

在这里插入图片描述

6.2.2. 设置Virtual Hosts权限

在这里插入图片描述

在这里插入图片描述

RabbitMQ 快速入门

入门程序

需求:使用简单模式完成消息传递

步骤:
① 创建工程(生成者、消费者)
② 分别添加依赖
③ 编写生产者发送消息
④ 编写消费者接收消息

导入配置文件:

<dependencies>
        <!--        rabbitmq java客户端-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.6.0</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

代码如下:

/**
 * 发送消息
 */
public class Producer_HelloWorld {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();
        //5、创建Queue
        /*
        queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
        参数:
            1、queue:队列名称
            2、durable:是否持久化,当mq重启之后,它还在
            3、exclusive:
                *是否独占,只能有一个消费者监听着对列
                *当connection关闭时,是否删除对列
            4、autoDelete:是否自动删除。当没有Consumer时,自动删除掉
            5、arguments:参数
        */
        /*如果没有hello_world的对列,则会创建该对列,如果有则不会创建*/
        channel.queueDeclare("hello_world",true,false,false,null);

        //6、发送消息(把消息发给queue)
        /*
         basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
         参数:
            1、exchange:交换机名称。简单模式下交换机会使用默认的""
            2、routingKey:路由名称
            3、props:配置信息
            4、body:发送的消息数据
         */
        String body = "hello rabbitmq~~~";
        channel.basicPublish("","hello_world",null,body.getBytes());

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

/**
 * 接收消息
 */
public class Consumer_HelloWorld {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();
        //5、创建Queue
        /*
        queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
        参数:
            1、queue:队列名称
            2、durable:是否持久化,当mq重启之后,它还在
            3、exclusive:
                *是否独占,只能有一个消费者监听着对列
                *当connection关闭时,是否删除对列
            4、autoDelete:是否自动删除。当没有Consumer时,自动删除掉
            5、arguments:参数
        */
        /*如果没有hello_world的对列,则会创建该对列,如果有则不会创建*/
        channel.queueDeclare("hello_world",true,false,false,null);

        //6、接收消息
        /*public String basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            queue:对列名称
            autoAck:是否自动确认
            callback:回调对象(可以监听一些方法,自动执行方法)
        */
        /*public DefaultConsumer(Channel channel) {
        this._channel = channel;}*/
        Consumer consumer = new DefaultConsumer(channel){//DefaultConsumer是一个空的实现,需要对它复写方法,所以使用匿名内
            //部类的方式去实现里边的方法Alt + Insert 复写handleDelivery:处理收到的消息
            /*回调方法,当收到消息后,会自动执行改方法
              参数:
                1、consumerTag:消息标识
                2、envelope:获取一些信息,比如:交换机信息,routeringKey(路由key)信息..。
                3、properties:配置信息
                4、body:数据
            */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumerTag:"+consumerTag);
                System.out.println("Exchange:"+envelope.getExchange());
                System.out.println("RoutingKey:"+envelope.getRoutingKey());
                System.out.println("properties:"+properties);
                System.out.println("body:"+new String(body));//body转成字符串
            }
        };
        channel.basicConsume("hello_world",true,consumer);
        //消费者需要关闭资源吗?不需要
    }
}

小结:
上述的入门案例中其实使用的是如下的简单模式:

在这里插入图片描述

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

  • P:生产者,也就是要发送消息的程序
  • C:消费者:消息的接收者,会一直等待消息到来
  • queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息

RabbitMQ 的工作模式

Work queues 工作队列模式

1、模式说明
在这里插入图片描述

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

2. 代码编写

Work Queues 与入门程序的简单模式的代码几乎是一样的。可以完全复制,并多复制一个消费者进行多个消费者同时对消费消息的测试。

public class Consumer_WorkQueues1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();
        //5、创建Queue
        /*
        queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
        参数:
            1、queue:队列名称
            2、durable:是否持久化,当mq重启之后,它还在
            3、exclusive:
                *是否独占,只能有一个消费者监听着对列
                *当connection关闭时,是否删除对列
            4、autoDelete:是否自动删除。当没有Consumer时,自动删除掉
            5、arguments:参数
        */
        /*如果没有hello_world的对列,则会创建该对列,如果有则不会创建*/
        channel.queueDeclare("work_queues",true,false,false,null);

        //6、接收消息

        /*public String basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            queue:对列名称
            autoAck:是否自动确认
            callback:回调对象(可以监听一些方法,自动执行方法)
        */
        /*public DefaultConsumer(Channel channel) {
        this._channel = channel;}*/
        Consumer consumer = new DefaultConsumer(channel){//DefaultConsumer是一个空的实现,需要对它复写方法,所以使用匿名内
            //部类的方式去实现里边的方法Alt + Insert 复写handleDelivery:处理收到的消息
            /*回调方法,当收到消息后,会自动执行改方法
              参数:
                1、consumerTag:消息标识
                2、envelope:获取一些信息,比如:交换机信息,routeringKey(路由key)信息..。
                3、properties:配置信息
                4、body:数据
            */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
               /* System.out.println("consumerTag:"+consumerTag);
                System.out.println("Exchange:"+envelope.getExchange());
                System.out.println("RoutingKey:"+envelope.getRoutingKey());
                System.out.println("properties:"+properties);*/
                System.out.println("body:"+new String(body));//body转成字符串
            }
        };       channel.basicConsume("work_queues",true,consumer);
        //消费者需要关闭资源吗?不需要
    }
}

运行结果:

在这里插入图片描述
在这里插入图片描述
3. 小结

  • 在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
  • Work Queues 对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。例如:短信服务部署多个,只需要有一个节点成功发送即可

Pub/Sub 订阅模式

在这里插入图片描述

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

  • P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
  • C:消费者,消息的接收者,会一直等待消息到来
  • Queue:消息队列,接收消息、缓存消息
  • Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
    ➢ Fanout:广播,将消息交给所有绑定到交换机的队列
    ➢ Direct:定向,把消息交给符合指定routing key 的队列
    ➢ Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
    Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合路由规则的队列,那么消息会丢失!
public class Producer_PubSub {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();
        /*exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable,
        boolean autoDelete, boolean internal, Map<String, Object> arguments)
        参数:
            1、exchange:交换机名称
            2、type:交换机类型
            3、durable:是否持久化
            4、autoDelete:是否自动删除
            5、internal:内部使用,一般是false
            6、arguments:参数列表
        */
        /*交换机四种类型:public enum BuiltinExchangeType {
        DIRECT("direct"),定向
        FANOUT("fanout"),广播,发送消息到每一个与之绑定的对列
        TOPIC("topic"),通配符的方式
        HEADERS("headers"); 参数匹配*/
        //5、创建交换机
        String exchangeName = "test_fanout";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);

        //6、创建对列
        String queue1Name = "test_fanout_queue1";
        String queue2Name = "test_fanout_queue2";
        channel.queueDeclare(queue1Name,true,false,false,null);
        channel.queueDeclare(queue2Name,true,false,false,null);

        //7、绑定对列和交换机
        /*queueBind(String queue, String exchange, String routingKey)
        参数:
            queue:对列名称
            exchange:交换机名称
            routingKey:路由键,绑定规则
            如果交换机的类型为:fanout这个类型,它的routingKey设置为""
                    */
        channel.queueBind(queue1Name,exchangeName,"");
        channel.queueBind(queue2Name,exchangeName,"");

        String body = "日志信息:张三调用了findAll方法...日志级别:info。。。";
        //8、发送消息
        channel.basicPublish(exchangeName,"",null,body.getBytes());

        //9、释放资源
        channel.close();
        connection.close();
    }
}

public class Consumer_PubSub1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();

        String queue1Name = "test_fanout_queue1";
        String queue2Name = "test_fanout_queue2";
        //6、接收消息

        /*public String basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            queue:对列名称
            autoAck:是否自动确认
            callback:回调对象(可以监听一些方法,自动执行方法)
        */
        /*public DefaultConsumer(Channel channel) {
        this._channel = channel;}*/
        Consumer consumer = new DefaultConsumer(channel){//DefaultConsumer是一个空的实现,需要对它复写方法,所以使用匿名内
            //部类的方式去实现里边的方法Alt + Insert 复写handleDelivery:处理收到的消息
            /*回调方法,当收到消息后,会自动执行改方法
              参数:
                1、consumerTag:消息标识
                2、envelope:获取一些信息,比如:交换机信息,routeringKey(路由key)信息..。
                3、properties:配置信息
                4、body:数据
            */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));//body转成字符串
                System.out.println("将日志信息打印到控制台~~~");

            }
        };
        channel.basicConsume(queue1Name,true,consumer);
        //消费者需要关闭资源吗?不需要
    }
}

public class Consumer_PubSub2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();

        String queue1Name = "test_fanout_queue1";
        String queue2Name = "test_fanout_queue2";
        //6、接收消息

        /*public String basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            queue:对列名称
            autoAck:是否自动确认
            callback:回调对象(可以监听一些方法,自动执行方法)
        */
        /*public DefaultConsumer(Channel channel) {
        this._channel = channel;}*/
        Consumer consumer = new DefaultConsumer(channel){//DefaultConsumer是一个空的实现,需要对它复写方法,所以使用匿名内
            //部类的方式去实现里边的方法Alt + Insert 复写handleDelivery:处理收到的消息
            /*回调方法,当收到消息后,会自动执行改方法
              参数:
                1、consumerTag:消息标识
                2、envelope:获取一些信息,比如:交换机信息,routeringKey(路由key)信息..。
                3、properties:配置信息
                4、body:数据
            */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));//body转成字符串
                System.out.println("将日志信息保存到数据库~~~");

            }
        };
        channel.basicConsume(queue1Name,true,consumer);
        //消费者需要关闭资源吗?不需要
    }
}

小结:

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

Routing 路由模式

1. 模式说明:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个 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 的消息
    P
/**
 * 发送消息
 */
public class Producer_Routing {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();
        /*exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable,
        boolean autoDelete, boolean internal, Map<String, Object> arguments)
        参数:
            1、exchange:交换机名称
            2、type:交换机类型
            3、durable:是否持久化
            4、autoDelete:是否自动删除
            5、internal:内部使用,一般是false
            6、arguments:参数列表
        */
        /*交换机四种类型:public enum BuiltinExchangeType {
        DIRECT("direct"),定向
        FANOUT("fanout"),广播,发送消息到每一个与之绑定的对列
        TOPIC("topic"),通配符的方式
        HEADERS("headers"); 参数匹配*/
        //5、创建交换机
        String exchangeName = "test_direct";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,false,null);

        //6、创建对列
        String queue1Name = "test_direct_queue1";
        String queue2Name = "test_direct_queue2";
        channel.queueDeclare(queue1Name,true,false,false,null);
        channel.queueDeclare(queue2Name,true,false,false,null);

        //7、绑定对列和交换机
        /*queueBind(String queue, String exchange, String routingKey)
        参数:
            queue:对列名称
            exchange:交换机名称
            routingKey:路由键,绑定规则
            如果交换机的类型为:fanout这个类型,它的routingKey设置为""
                    */
        //对列1的绑定 error
        channel.queueBind(queue1Name,exchangeName,"error");

        //对列2的绑定 info error warning
        channel.queueBind(queue2Name,exchangeName,"info");
        channel.queueBind(queue2Name,exchangeName,"error");
        channel.queueBind(queue2Name,exchangeName,"warning");

        String body = "日志信息:张三调用了delete方法...日志级别:error。。。";
        //8、发送消息
        channel.basicPublish(exchangeName,"error",null,body.getBytes());

        //9、释放资源
        channel.close();
        connection.close();
    }
}

public class Consumer_Routing1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();

        String queue1Name = "test_direct_queue1";
        String queue2Name = "test_direct_queue2";
        //6、接收消息

        /*public String basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            queue:对列名称
            autoAck:是否自动确认
            callback:回调对象(可以监听一些方法,自动执行方法)
        */
        /*public DefaultConsumer(Channel channel) {
        this._channel = channel;}*/
        Consumer consumer = new DefaultConsumer(channel){//DefaultConsumer是一个空的实现,需要对它复写方法,所以使用匿名内
            //部类的方式去实现里边的方法Alt + Insert 复写handleDelivery:处理收到的消息
            /*回调方法,当收到消息后,会自动执行改方法
              参数:
                1、consumerTag:消息标识
                2、envelope:获取一些信息,比如:交换机信息,routeringKey(路由key)信息..。
                3、properties:配置信息
                4、body:数据
            */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));//body转成字符串
                System.out.println("将日志信息打印到控制台~~~");

            }
        };

        channel.basicConsume(queue2Name,true,consumer);
        //消费者需要关闭资源吗?不需要
    }
}

public class Consumer_Routing2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();

        String queue1Name = "test_direct_queue1";
        String queue2Name = "test_direct_queue2";
        //6、接收消息

        /*public String basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            queue:对列名称
            autoAck:是否自动确认
            callback:回调对象(可以监听一些方法,自动执行方法)
        */
        /*public DefaultConsumer(Channel channel) {
        this._channel = channel;}*/
        Consumer consumer = new DefaultConsumer(channel){//DefaultConsumer是一个空的实现,需要对它复写方法,所以使用匿名内
            //部类的方式去实现里边的方法Alt + Insert 复写handleDelivery:处理收到的消息
            /*回调方法,当收到消息后,会自动执行改方法
              参数:
                1、consumerTag:消息标识
                2、envelope:获取一些信息,比如:交换机信息,routeringKey(路由key)信息..。
                3、properties:配置信息
                4、body:数据
            */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));//body转成字符串
                System.out.println("将日志信息存储到数据库~~~");

            }
        };

        channel.basicConsume(queue1Name,true,consumer);
        //消费者需要关闭资源吗?不需要
    }
}

小结:

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

Topics 通配符模式

1. 模式说明

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

在这里插入图片描述

public class Producer_Topics {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();
        /*exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable,
        boolean autoDelete, boolean internal, Map<String, Object> arguments)
        参数:
            1、exchange:交换机名称
            2、type:交换机类型
            3、durable:是否持久化
            4、autoDelete:是否自动删除
            5、internal:内部使用,一般是false
            6、arguments:参数列表
        */
        /*交换机四种类型:public enum BuiltinExchangeType {
        DIRECT("direct"),定向
        FANOUT("fanout"),广播,发送消息到每一个与之绑定的对列
        TOPIC("topic"),通配符的方式
        HEADERS("headers"); 参数匹配*/
        //5、创建交换机
        String exchangeName = "test_topic";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);

        //6、创建对列
        String queue1Name = "test_topic_queue1";
        String queue2Name = "test_topic_queue2";
        channel.queueDeclare(queue1Name,true,false,false,null);
        channel.queueDeclare(queue2Name,true,false,false,null);

        //7、绑定对列和交换机
        /*queueBind(String queue, String exchange, String routingKey)
        参数:
            queue:对列名称
            exchange:交换机名称
            routingKey:路由键,绑定规则
            如果交换机的类型为:fanout这个类型,它的routingKey设置为""
                    */
        //routing key  系统的名称.日志的级别
        //需求:所有error级别的日志存数据库,所有order系统的日志存如数据库(所有的#.error,order.*级别都能被queue1对列收到)
        channel.queueBind(queue1Name,exchangeName,"#.error");
        channel.queueBind(queue1Name,exchangeName,"order.*");
        //不管什么级别的信息都打印到控制台上
        channel.queueBind(queue2Name,exchangeName,"*.*");

        String body = "日志信息:张三调用了findAll方法...日志级别:info。。。";
        //8、发送消息(queue1和queue2都可以收到)
        channel.basicPublish(exchangeName,"goods.error",null,body.getBytes());

        //9、释放资源
        channel.close();
        connection.close();
    }
}

public class Consumer_Topic1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();

        String queue1Name = "test_topic_queue1";
        String queue2Name = "test_topic_queue2";
        
        //6、接收消息
        /*public String basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            queue:对列名称
            autoAck:是否自动确认
            callback:回调对象(可以监听一些方法,自动执行方法)
        */
        /*public DefaultConsumer(Channel channel) {
        this._channel = channel;}*/
        Consumer consumer = new DefaultConsumer(channel){//DefaultConsumer是一个空的实现,需要对它复写方法,所以使用匿名内
            //部类的方式去实现里边的方法Alt + Insert 复写handleDelivery:处理收到的消息
            /*回调方法,当收到消息后,会自动执行改方法
              参数:
                1、consumerTag:消息标识
                2、envelope:获取一些信息,比如:交换机信息,routeringKey(路由key)信息..。
                3、properties:配置信息
                4、body:数据
            */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));//body转成字符串
                System.out.println("将日志信息存入数据库~~~");
            }
        };
        channel.basicConsume(queue1Name,true,consumer);
        //消费者需要关闭资源吗?不需要
    }
}

public class Consumer_Topic2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置参数
        factory.setHost("192.168.23.140");//ip
        factory.setPort(5672);//端口号 默认值:5672
        factory.setVirtualHost("/qqhru");//虚拟机 默认值/
        factory.setUsername("hwf");//用户名 默认:guest
        factory.setPassword("hwf");//密码 默认:guest

        //3、创建连接 connection
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();

        String queue1Name = "test_topic_queue1";
        String queue2Name = "test_topic_queue2";
        //6、接收消息

        /*public String basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            queue:对列名称
            autoAck:是否自动确认
            callback:回调对象(可以监听一些方法,自动执行方法)
        */
        /*public DefaultConsumer(Channel channel) {
        this._channel = channel;}*/
        Consumer consumer = new DefaultConsumer(channel){//DefaultConsumer是一个空的实现,需要对它复写方法,所以使用匿名内
            //部类的方式去实现里边的方法Alt + Insert 复写handleDelivery:处理收到的消息
            /*回调方法,当收到消息后,会自动执行改方法
              参数:
                1、consumerTag:消息标识
                2、envelope:获取一些信息,比如:交换机信息,routeringKey(路由key)信息..。
                3、properties:配置信息
                4、body:数据
            */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));//body转成字符串
                System.out.println("将日志信息打印到控制台~~~");
            }
        };
        channel.basicConsume(queue2Name,true,consumer);
        //消费者需要关闭资源吗?不需要
    }
}

小结:

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

工作模式总结

1. 简单模式 HelloWorld
一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)。

2. 工作队列模式 Work Queue
一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)。

3. 发布订阅模式 Publish/subscribe
需要设置类型为 fanout 的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列。

4. 路由模式 Routing
需要设置类型为 direct 的交换机,交换机和队列进行绑定,并且指定 routing key,当发送消息到交换机后,交换机会根据 routing key 将消息发送到对应的队列。

5. 通配符模式 Topic
需要设置类型为 topic 的交换机,交换机和队列进行绑定,并且指定通配符方式的 routing key,当发送消息到交换机后,交换机会根据 routing key 将消息发送到对应的队列

Spring 整合 RabbitMQ

Spring 整合 RabbitMQ

需求:使用 Spring 整合 RabbitMQ
步骤:
生产者
① 创建生产者工程
② 添加依赖
③ 配置整合
④ 编写代码发送消息

消费者
① 创建生产者工程
② 添加依赖
③ 配置整合
④ 编写消息监听器

1、生产者:

导入依赖:

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

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

配置文件:spring-rabbitmq-producer.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
       https://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:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>
    <!--定义管理交换机、队列-->
    <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">
        <rabbit:bindings>
            <rabbit:binding pattern="hwf.*" queue="spring_topic_queue_star"/>
            <rabbit:binding pattern="hwf.#" queue="spring_topic_queue_well"/>
            <rabbit:binding pattern="qqhru.#" queue="spring_topic_queue_well2"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>

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

配置文件:rabbitmq.properties

rabbitmq.host=你的ip地址
rabbitmq.port=5672
rabbitmq.username=hwf
rabbitmq.password=hwf
rabbitmq.virtual-host=/qqhru

测试代码:

@RunWith(SpringJUnit4ClassRunner.class)//加载spring配置文件
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")//加载配置文件路径
public class ProducerTest {
    //1、注入 RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testHelloWorld(){
        //2、发送消息
        rabbitTemplate.convertAndSend("spring_queue","hello world spring ...");
    }

    /**
     * 发送fanout消息
     */
    @Test
    public void testFanout(){
        //2、发送消息
        rabbitTemplate.convertAndSend("spring_fanout_exchange","","spring fanout...");
    }

    /**
     * 发送topic消息
     */
    @Test
    public void testTopic(){
        //2、发送消息
        rabbitTemplate.convertAndSend("spring_topic_exchange","hwf.hehe.heihei","spring topic...");
    }
}

1、消费者

代码:

public class SpringQueueListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        //打印消息
        System.out.println(new String(message.getBody()));
    }
}


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {

    @Test
    public void test1(){
        boolean flag = true;
        while (flag){

        }
    }

依赖文件与生产者一样

配置文件:rabbitmq.properties

rabbitmq.host=192.168.23.140
rabbitmq.port=5672
rabbitmq.username=hwf
rabbitmq.password=hwf
rabbitmq.virtual-host=/qqhru 

配置文件:spring-rabbitmq-consumer.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
       https://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:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

    <bean id="springQueueListener" class="com.qqhru.rabbitmq.listener.SpringQueueListener"/>
    <bean id="fanoutListener1" class="com.qqhru.rabbitmq.listener.FanoutListener1"/>
    <bean id="fanoutListener2" class="com.qqhru.rabbitmq.listener.FanoutListener2"/>
    <bean id="topicListenerStar" class="com.qqhru.rabbitmq.listener.TopicListenerStar"/>
    <bean id="topicListenerWell" class="com.qqhru.rabbitmq.listener.TopicListenerWell"/>
    <bean id="topicListenerWell2" class="com.qqhru.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>

SpringBoot 整合 RabbitMQ

生产端:

  1. 创建生产者SpringBoot工程
  2. 引入starter,依赖坐标
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  1. 编写yml配置,基本信息配置
  2. 定义交换机,队列以及绑定关系的配置类
  3. 注入RabbitTemplate,调用方法,完成消息发送
# 配置rabbitmq的基本信息
spring:
  rabbitmq:
    host: 192.168.23.140
    username: guest
    password: guest
    virtual-host: /
    port: 5672
@Configuration
public class RabbitMQConfig {

    public static final String EXCHANGE_NAME = "boot_topic_exchange";
    public static final String QUEUE_NAME = "boot_queue";

    //1、交换机配置
    @Bean("bootExchange")
    public Exchange bootExchange(){
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }

    //2、Queue 对列配置
    @Bean("bootQueue")
    public Queue bootQueue(){
        return QueueBuilder.durable(QUEUE_NAME).build();
    }

    //3、对列和交换机绑定关系 Binding
    /*
    知道哪个对列
    知道哪个交换机
    routing key
    */
    @Bean
    public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue,@Qualifier("bootExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
    }
}

//测试类
@SpringBootTest
class ProducerSpringbootApplicationTests {
    //注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    void contextLoads() {
        //参数:交换机名称,routing key,消息数据
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.haha","boot hello world~~~");
    }
}

消费端:

  1. 创建消费者SpringBoot工程
  2. 引入starter,依赖坐标
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  1. 编写yml配置,基本信息配置
  2. 定义监听类,使用@RabbitListener注解完成队列监听
# 配置rabbitmq的基本信息
spring:
  rabbitmq:
    host: 192.168.23.140
    username: guest
    password: guest
    virtual-host: /
    port: 5672

配置类(RabbitMQListener):

@Component//被spring识别
public class RabbitMQListener {

    @RabbitListener(queues = "boot_queue")
    public void ListenerQueue(Message message){
        //System.out.println(message);
        /*目前获取不到了,需要重新发消息*/
        System.out.println(new String(message.getBody()));
    }
}

小结:

  • SpringBoot提供了快速整合RabbitMQ的方式
  • 基本信息再yml中配置,队列交互机以及绑定关系在配置类中使用Bean的方式配置
  • 生产端直接注入RabbitTemplate完成消息发送
  • 消费端直接使用@RabbitListener完成消息接收
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值