栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SSM框架之RabbitMQ——消息中间件

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SSM框架之RabbitMQ——消息中间件

目录

一、MQ的基本概念

1.1MQ概述

1.2MQ的优缺点

1.3MQ的劣势

1.4常见的MQ产品

1.5RabbitMQ简介

1.6JMS

二、RabbitMQ安装和配置

2.1准备安装包

2.2安装依赖环境

2.3安装Erlang

2.4安装RabbitMQ

2.5开启管理界面及配置

2.6RabbitMQ管理控制台使用

三、RabbitMQ快速入门

四、RabbitMQ工作模式

4.1Hello World

4.2Work queues

4.3Pub/Sub订阅模式

4.4Routing路由模式

4.5Topics通配符模式

4.6工作模式总结

五、Spring整合RabbitMQ

六、SpringBoot整合RabbitMQ


一、MQ的基本概念

1.1MQ概述

MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。

多用于分布式系统之间进行通信。

在分布式系统中,通信主要包含两种方式:直接调用和借助第三方进行间接通信。

MQ消息队列就是这个第三方,我们也成为消息中间件,而发送数据的或者提供服务的A系统我们称为生产者,接收数据的B系统我们称为消费者。

1.2MQ的优缺点

MQ的优点主要包括:

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

1、应用解耦

对于用户下订单这样的应用场景,下订单时需要查询库存,支付状态,物流状态等,如果我们的订单系统直接去调用这些子系统,那么当用户下订单时,会通过订单系统去访问库存系统,若库存系统出bug时,订单系统也会出故障,导致下订单失败。这样整个系统耦合性较高,对应的容错性比较低、可维护性也比较差。

 如果我们在中间添加MQ作为中间件,当用户下订单时,会将订单的信息放在MQ消息中间件中,以供库存、支付、物流等系统来消费,当库存系统发生异常时,如果能在短时间内恢复那么也不会影响订单系统。这样提升了系统的解耦,相应的容错性和可维护性大大提高。

2、异步提速

假设还是下订单操作,用户在点击下订单时,首先会访问一下数据库保存数据,接着如果是直接调用,则会同步的依次访问库存、支付、物流等系统,总消耗920ms才可以完成下订单操作。

而如果将MQ作为消息中间件,则仅需要在订单系统访问玩数据库后,将订单的数据发送给MQ消息中间件,供其他系统消费即可,此时就可以直接返回用户下单成功的消息,总消耗25ms。即用户并不关心库存、支付和物流系统中的操作是否执行完成或者是否正确执行。

3、削峰填谷

比如假设某个购物A系统每秒能最大处理1000个请求,到了双十一的时候,请求瞬间增多,此时A系统就极易出现崩溃宕机,导致用户体验较差,

如果我们使用MQ作为中间件,那么用户的请求直接是存储到了MQ中,MQ是可以每秒处理5000个请求的,那么A系统只需要每秒从MQ中拉取1000个请求慢慢处理即可,就不会宕机了。

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

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

1.3MQ的劣势

MQ同样还有以下劣势:

系统可用性降低

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

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

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

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

    生产者不需要从消费者处获得反馈。容许短暂的不一致性。解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本。

1.4常见的MQ产品

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

RabbitMQ

ActiveMQ

RocketMQ

Kafka

公司/社区

Rabbit

Apache

阿里

Apache

开发语言

Erlang

Java

Java

Scala&Java

协议支持

AMQP,XMPP,SMTP,STOMP

OpenWire,STOMP,REST,XMPP,AMQP

自定义

自定义协议,社区封装了http协议支持

客户端支持语言

官方支持Erlang,Java,Ruby等,社区产出多种API,几乎支持所有语言

Java,C,C++,Python,PHP,Perl,.net等

Java,C++(不成熟)

官方支持Java,社区产出多种API,如PHP,Python等

单机吞吐量

万级(其次)

万级(最差)

十万级(最好)

十万级(次之)

消息延迟

微妙级

毫秒级

毫秒级

毫秒以内

功能特性

并发能力强,性能极其好,延时低,社区活跃,管理界面丰富

老牌产品,成熟度高,文档较多

MQ功能比较完备,扩展性佳

只支持主要的MQ功能,毕竟是为大数据领域准备的。

1.5RabbitMQ简介

在RabbitMQ中,有一个支持的协议为AMQP,在这里我们首先对这个协议进行介绍。

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

这里的Exchange(交换机)是用来分发消息的,通过Routes(路由)将消息分发到Queue(队列容器)中存储,而这些消息是由Publisher(生产者)发布到交换机上,最后Consumer(消费者)从队列中监听消息,然后取出消息进行消费。


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

RabbitMQ 基础架构如下图:

Broker:接收和分发消息的应用,RabbitMQ Server就是 Message BrokerVirtual 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 的开销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

1.6JMS

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

二、RabbitMQ安装和配置

win10系统的安装详细可见:https://blog.csdn.net/weixin_39478524/article/details/122074153

这里我们用centos来做实验,所以以下都是在linux系统上进行安装的。

2.1准备安装包

首先我们下载安装包,

链接:https://pan.baidu.com/s/1_bnCCkPMbAp_6A-3mbJl1Q 
提取码:cbxt 

下载以后我们放置到我们的 windows目录下,

然后通过SercureCRT的alt+p命令进行文件传输,输入put -r

传输完成后在根目录下检查,返现该文件夹已经传到虚拟机centos中了,

然后我们就可以开始安装了。

2.2安装依赖环境

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

在命令行直接输入命令,稍等几分钟即可。

2.3安装Erlang

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

首先我们进入到rabbitmq目录,输入命令进行安装,

如果出现如下错误,说明gblic 版本太低,

我们可以查看当前机器的gblic 版本, 

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

我的版本是2.2,需要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

2.4安装RabbitMQ

rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm

然后我们把服务启动一下,

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

2.5开启管理界面及配置

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

我们把"<<>>"去掉, 

修改完配置后我们重启一下rabbitmq,

service rabbitmq-server restart

关闭一下防火墙,方便后面我们从windows系统中能访问虚拟机,

systemctl stop firewalld

然后我们输入网址就可以访问,http://192.168.49.200:15672/

 默认的登录用户名和密码都是guest,登录之后就可以看到如下的管理控制台界面。

2.6RabbitMQ管理控制台使用

首先我们可以看一下管理控制台的界面,

我们点击Admin管理员,对RabbitMQ可以进行管理,我们可以新建用户,

新建完成之后我们可以查看用户信息,

然后我们点击右边的Virtual Hosts,新建一个虚拟机,

新建完了之后,可以查看到虚拟机的信息,然后我们点击,对aoylaotang用户进行授权,

点击进去之后我们可以在Permissions这一栏设置权限,让aoylaotang可以访问test虚拟机,

回到Users界面,就可以看到aoylaotang可以操作test虚拟机了,


 在Overview概述中,有一些文件的路径,其中配置文件提示不存在,

我们进入到虚拟机的rabbitmq安装目录,

cd /user/share/doc/rabbitmq-server-3.6.5/

可以看到有rabbitmq.config.example这个配置文件,

我们把它拷贝到指定的/etc/rabbitmq/的路径下,并改名为rabbitmq.config,

cp ./rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
service rabbitmq-server restart # 重启rabbitmq服务

再刷新页面就可以看到不是not found了,

在Ports and contexts中可以看到三个端口信息,

在import/export definitions中可以对rabbitmq进行导入导出,需要制定配置文件,

三、RabbitMQ快速入门

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

我们在1.5节简介的时候介绍了RabbitMQ 提供了 6 种工作模式:简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing 路由模式、Topics 主题模式、RPC 远程调用模式。

官网对应模式介绍:https://www.rabbitmq.com/getstarted.html,这里我们可以上官网查看一下简单模式对应的图:

从图中我们可以看到只有三部分组成,一个是生产者,一个是消费者,还有一个就是消息队列。

所以简单模式完成消息传递的步骤如下:

    创建工程(生产者、消费者)分别添加依赖编写生产者发送消息编写消费者接收消息

第一步,首先我们在一个空工程里面添加生产者和消费者两个模块,

第二步,我们分别在两个子工程中都添加上rabbitmq的依赖,


    
    
        com.rabbitmq
        amqp-client
        5.6.0
    



    
        
        
            org.apache.maven.plugins
            maven-compiler-plugin
            3.8.0
            
                1.8
                1.8
            
        
    

第三步,我们编写生产者的代码,发送消息数据,

public class Producer_HelloWorld {
    public static void main(String[] args) throws IOException, TimeoutException {
        //建立与RabbitMQ的连接
        //1、创建连接工厂
        ConnectionFactory factory=new ConnectionFactory();
        //2、设置连接参数
        factory.setHost("192.168.49.200");//设置ip地址,默认值为localhost即127.0.0.1
        factory.setPort(5672);//设置端口,默认值也为5672
        factory.setVirtualHost("/test");//设置虚拟机,默认值为/
        factory.setUsername("aoylaotang");//设置用户名,默认值为guest
        factory.setPassword("3837");//设置密码,默认值为guest
        //3、创建连接
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel=connection.createChannel();
        //5、创建队列Queue
        
        channel.queueDeclare("helloworld",true,false,false,null);
        //6、发送消息到队列中
        
        String body="hello rabbitmq!";//定义发送的消息数据
        channel.basicPublish("","helloworld",null,body.getBytes());
        //7、释放资源
        channel.close();
        connection.close();
    }
}

我们运行一下生产者的main方法,然后查看rabbitmq的后台,点击Queues可以看到helloworld的队列被创建成功了, 

如果我们不释放Connection和Channel的资源,那么在这个界面也可以看到对应的信息。

第四步,我们创建消费者用来接受数据,消费者同样也要连接到消息队列,所以也要创建connection和channel对象,

public class Consumer_HelloWorld {
    public static void main(String[] args) throws IOException, TimeoutException {
        //建立与RabbitMQ的连接
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置连接参数
        factory.setHost("192.168.49.200");//设置ip地址,默认值为localhost即127.0.0.1
        factory.setPort(5672);//设置端口,默认值也为5672
        factory.setVirtualHost("/test");//设置虚拟机,默认值为/
        factory.setUsername("aoylaotang");//设置用户名,默认值为guest
        factory.setPassword("3837");//设置密码,默认值为guest
        //3、创建连接
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();
        //5、创建队列Queue(在消费者中其实已经创建了,这里也可以省去该部分代码)
        
        channel.queueDeclare("helloworld", true, false, false, null);
        //6、接受消息
        
        Consumer consumer = new DefaultConsumer(channel) {
            
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                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));
            }
        };//创建一个回调对象
        channel.basicConsume("helloworld", true, consumer);
        //消费者不需要关闭资源,否则无法实时监听消息
    }
}

我们运行之后可以发现消息已经被消费者监听到了,

四、RabbitMQ工作模式

RabbitMQ 一共提供了 6 种工作模式:简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing 路由模式、Topics 主题模式、RPC 远程调用模式。详细可以见官网介绍。

其实不同的工作模式之间,就是消息路由的策略和方式不同。

4.1Hello World

第一个"Hello World"就是我们快速入门中体验到的简单模式,这里不再进行说明了。

4.2Work queues

Work Queues与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。

应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

所以Work queues模式完成消息传递的步骤如下:

    创建工程(生产者、消费者)分别添加依赖编写生产者发送消息编写消费者接收消息

其实和简单模式是一样的,就只有个别地方不同,工程和依赖我们之前已经添加好了,直接创建生产者,代码其实大部分也都一样,在创建队列的时候需要修改队列的名称,然后发送消息时我们这次多发几条消息,模拟多任务情况。

//5、创建队列Queue
channel.queueDeclare("workqueues",true,false,false,null);

//6、发送消息到队列中
for (int i = 1; i <= 10; i++) {
    String body=i+":hello rabbitmq!";//定义发送的消息数据
    channel.basicPublish("","workqueues",null,body.getBytes());
}

然后我们创建消费者,相对于简单模式,我们需要修改队列名称,然后接受消息时我们只输出消息数据,

//5、创建队列Queue(在消费者中其实已经创建了,这里也可以省去该部分代码)
channel.queueDeclare("workqueues", true, false, false, null);

//6、接受消息
Consumer consumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        super.handleDelivery(consumerTag, envelope, properties, body);
        System.out.println("body" + new String(body));//只输出消息信息
    }
};//创建一个回调对象
channel.basicConsume("workqueues", true, consumer);

这里我们创建两个消费者,代码完全相同,分别启动这两个消费者进行消息监听,

然后我们启动生产者发送10条消息数据看看输出情况,

可以看到这两个消费者是按序消费的,消费者1处理一条消息,然后消费者2处理一条消息,以此类推。

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

4.3Pub/Sub订阅模式

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

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

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

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

我们创建一个生产者来发送信息,前四步跟前面一样,首先创建连接工厂,设置连接参数,创建连接以及创建channel,然后我们此时需要把消息发送到交换机上,所以接下来我们要创建交换机,

public class Producer_PubSub {
    public static void main(String[] args) throws IOException, TimeoutException {
        //建立与RabbitMQ的连接
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置连接参数
        factory.setHost("192.168.49.200");//设置ip地址,默认值为localhost即127.0.0.1
        factory.setPort(5672);//设置端口,默认值也为5672
        factory.setVirtualHost("/test");//设置虚拟机,默认值为/
        factory.setUsername("aoylaotang");//设置用户名,默认值为guest
        factory.setPassword("3837");//设置密码,默认值为guest
        //3、创建连接
        Connection connection = factory.newConnection();
        //4、创建Channel
        Channel channel = connection.createChannel();
        //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、绑定队列和交换机
        
        channel.queueBind(queue1Name, exchangeName, "");
        channel.queueBind(queue2Name, exchangeName, "");
        //8、发送消息
        String body = "日志信息:aoy调用了service方法...日志级别:info...";//模拟一条日志信息数据
        channel.basicPublish(exchangeName, "", null, body.getBytes());
        //9、释放资源
        channel.close();
        connection.close();
    }
}

我们在rabbitmq管理界面可以看到交换机已经被创建好了,

点进去可以查看交换机的绑定关系,可以看到该交换机绑定了两个队列,

进入队列界面可以看到对应的队列也创建好了,并且交换机已经把数据发送到了绑定的队列中, 

消费者的代码基本都相同,不同的是监听的队列名不同,以及收到消息后的执行方法,

我们创建消费者1监听队列1的消息,并将日志信息打印到控制台(这里直接模拟操作即可),接着创建消费者2监听队列2的消息,并将日志信息保存到数据库(同样是模拟操作)。

//6、接受消息

Consumer consumer = new DefaultConsumer(channel) {
    
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        super.handleDelivery(consumerTag, envelope, properties, body);
        System.out.println("body" + new String(body));
        System.out.println("将日志信息打印到控制台...");
    }
};//创建一个回调对象
channel.basicConsume(queue1Name, true, consumer);//监听队列1的消息
//消费者不需要关闭资源,否则无法实时监听消息
//6、接受消息

Consumer consumer = new DefaultConsumer(channel) {
    
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        super.handleDelivery(consumerTag, envelope, properties, body);
        System.out.println("body" + new String(body));
        System.out.println("将日志信息存储到数据库...");
    }
};//创建一个回调对象
channel.basicConsume(queue2Name, true, consumer);//监听队列2的消息
//消费者不需要关闭资源,否则无法实时监听消息

然后我们启动这两个消费者,查看输出信息。

可以看到两个消费者都监听到了同一条消息,并作出了不同的响应。

4.4Routing路由模式

在Routing路由模式中,队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey。

并且消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey,

Exchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的Routingkey 与消息的 Routing key 完全一致,才会接收到消息。

在上面的例子中,我们将info级别的日志信息都存储到了数据库中,但是这样会占用数据库的空间,所以一般我们只存储比较重要的日志信息,比如error级别的日志,所以我们希望对于一般的日志信息,让消费者2直接打印在控制台上,而碰到error级别的日志,则让消费者1存储到数据库。

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

生产者的代码主要从创建交换机开始不同,我们需要创建DIRECT类型的交换机(即Routing模式),然后在绑定队列和交换机时需要指定routingKey路由键,然后发送消息时也要指定对应的routingKey,这样交换机就知道该消息需要发送到哪个队列中去。

//5、创建交换机

String exchangeName = "test_direct";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, false, null);//定义交换机的类型为定向,即Routing模式
//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、绑定队列和交换机

//队列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");
//8、发送消息
String infoBody = "日志信息:aoy调用了service方法...日志级别:info...";//模拟一条info级别日志信息数据
channel.basicPublish(exchangeName, "info", null, infoBody.getBytes());
String errorBody = "日志信息:Failed to load class XXXX...日志级别:error...";//模拟一条error级别日志信息数据
channel.basicPublish(exchangeName, "error", null, errorBody.getBytes());
String warningBody = "日志信息:变量XXX没有被使用...日志级别:warning...";//模拟一条warning级别日志信息数据
channel.basicPublish(exchangeName, "warning", null, warningBody.getBytes());
//9、释放资源
channel.close();
connection.close();

在生产者中,我们发送了三条消息,分别为info、error和warning级别的,然后我们启动生产者。

可以看到队列1中只有1条消息,队列2中有3条消息,

接着我们创建消费者,让消费者1监听队列1的消息,并将监听到的日志信息保存到数据库;消费者2监听队列2的消息,并将监听到的日志信息打印到控制台。

//5、创建队列Queue(在消费者中其实已经创建了,这里也可以省去该部分代码)
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
//6、接受消息
Consumer consumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        super.handleDelivery(consumerTag, envelope, properties, body);
        System.out.println("body" + new String(body));
        System.out.println("将日志信息保存到数据库...");
    }
};//创建一个回调对象
channel.basicConsume(queue1Name, true, consumer);//监听队列1的消息
//5、创建队列Queue(在消费者中其实已经创建了,这里也可以省去该部分代码)
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
//6、接受消息
Consumer consumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        super.handleDelivery(consumerTag, envelope, properties, body);
        System.out.println("body" + new String(body));
        System.out.println("将日志信息打印到控制台...");
    }
};//创建一个回调对象
channel.basicConsume(queue2Name, true, consumer);//监听队列2的消息

然后我们启动这两个消费者,可以看到消费者1只处理类error级别的日志消息,

消费者2处理了info、error和warning级别的日志消息,

4.5Topics通配符模式

Topic 类型与 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 都会被匹配
绿色 Queue:绑定的是 #.weather ,因此凡是以 .weather 结尾的 routing key 都会被匹配
蓝色 Queue:绑定的是 europe.# ,因此凡是以 europe. 开头的 routing key 都会被匹配到

所以如果routing key为usa.news,则红色和黄色队列都可以收到该消息。

下面则是Topics消息传递的示意图:

假设我们认为定义routing key由两部分组成:系统的名称.日志的级别。

我们想将order订单系统的所有日志信息以及任意系统的error级别信息存入数据库,而所有的日志信息都可以打印在控制台。

首先是创建生产者,大部分代码和Routing模式相同,但是需要修改交换机的类型,队列和交换机的绑定方式等,

//5、创建交换机
String exchangeName = "test_topic";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC, true, false, false, null);//定义交换机的类型为通配符方式,即Topics模式

//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、绑定队列和交换机
//队列1绑定error级别日志信息和order系统的所有级别日志信息
channel.queueBind(queue1Name, exchangeName, "#.error");
channel.queueBind(queue1Name, exchangeName, "order.*");
//队列2绑定所有的日志信息
channel.queueBind(queue2Name, exchangeName, "*.*");

//8、发送消息
String orderinfoBody = "日志信息:aoy调用了service方法...日志级别:info...";//模拟一条order系统info级别日志信息数据
channel.basicPublish(exchangeName, "order.info", null, orderinfoBody.getBytes());
String usererrorBody = "日志信息:Failed to load class XXXX...日志级别:error...";//模拟一条user系统error级别日志信息数据
channel.basicPublish(exchangeName, "user.error", null, usererrorBody.getBytes());
String userwarningBody = "日志信息:变量XXX没有被使用...日志级别:warning...";//模拟一条user系统warning级别日志信息数据
channel.basicPublish(exchangeName, "user.warning", null, userwarningBody.getBytes());

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

我们发送了三条日志信息,分别为order系统的info级别日志、user系统的error级别日志、user系统的warning级别日志。我们启动一下生产者,看到队列1接收到了2条消息,队列2接收了3条消息。

然后我们创建消费者,还是让消费者1监听队列1的消息,并将消息存储到数据库,让消费者2监听队列2的消息,并将消息打印到控制台,代码和Routing的差不多,改一下监听的队列名称就行。然后我们启动消费者,查看输出,

消费者1监听到了order.info和user.error的日志消息,

消费者2监听到了所有的日志消息,

4.6工作模式总结

简单模式 HelloWorld

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

一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)。发布订阅模式 Publish/subscribe

需要设置类型为 fanout 的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列。路由模式 Routing

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

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

五、Spring整合RabbitMQ

需求:使用Spring整合RabbitMQ

步骤:

创建生产者、消费者工程添加依赖配置整合生产者编写代码发送消息、消费者编写消息监听器

首先我们创建生产者和消费者工程,

然后我们对两个工程都添加上对应的依赖以及编译插件,


        
            org.springframework
            spring-context
            5.1.7.RELEASE
        
        
            org.springframework.amqp
            spring-rabbit
            2.1.8.RELEASE
        
        
            junit
            junit
            4.12
        
        
            org.springframework
            spring-test
            5.1.7.RELEASE
        
    

    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.8.0
                
                    1.8
                    1.8
                
            
        
    

接着我们需要对rabbitmq进行配置,创建rabbitmq.properties配置文件,主要包括rabbitmq的主机地址,端口号,用户名密码以及虚拟机名等信息,将配置文件放在资源文件夹下,

rabbitmq.host=192.168.49.200
rabbitmq.port=5672
rabbitmq.username=aoylaotang
rabbitmq.password=3837
rabbitmq.virtual-host=/test

然后是创建spring-rabbitmq-producer.xml来读取rabbitmq的配置文件,并通过配置的方式来创建连接、交换机、队列等。



    
    

    
    
    
    

    
    
    

    
    
    
    

    
    
        
            
            
        
    

    
    
    
    
    

    
    
        
        
            
            
            
        
    

    
    

然后我们定义一个测试类,利用RabbitmqTemplate的convertAndSend方法,以简单模式来给spring_queue队列发送消息,

@RunWith(SpringJUnit4ClassRunner.class)//使用Spring容器测试环境
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")//加载配置文件
public class ProducerTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;//注入RabbitTemplate对象

    @Test//使用简单模式发送消息
    public void testHelloWorld(){
        String message="hello world!";
        rabbitTemplate.convertAndSend("spring_queue",message);//向spring_queue队列发送消息
    }
}

打开rabbitmq控制台,我们可以看到交换机以及队列都被创建好了(在配置文件中我们都设置的是自动创建),然后我们看到spring_queue队列中也接收到了我们发送的消息。 


然后我们测试一下使用订阅模式,即广播模式来发送消息,广播模式需要发送消息到指定的交换机,routing key设置为空,这样与交换机绑定的所有队列都能收到消息,

@Test//使用订阅(广播)模式发送消息
public void testFanout(){
    String message="fanout message";
    rabbitTemplate.convertAndSend("spring_fanout_exchange","",message);//向spring_fanout_exchange交换机发送消息,与该交换机绑定的队列都能收到消息
}

运行一下,打开rabbitmq后台查看,绑定的两个队列都受到了消息,


接着我们测试通配符模式(Routing方式其实和通配符模式差不多,只不过通配符的routing key可以使用通配符*和#,本质都是让交换机通过routing key来决定哪个消息发送到哪个队列)

@Test//使用通配符模式发送消息
public void testTopic(){
    String message1="topic message1";
    String message2="topic message2";
    String message3="topic message3";
    //生产者先将消息发送到交换机,然后交换机通过routing key将消息发送到指定队列
    rabbitTemplate.convertAndSend("spring_topic_exchange","java.exe",message1);
    rabbitTemplate.convertAndSend("spring_topic_exchange","java.1.exe",message2);
    rabbitTemplate.convertAndSend("spring_topic_exchange","c++.exe",message3);
}

按道理来说,spring_topic_queue_star会收到第一条消息,spring_topic_queue_well会受到第一第二两条消息,spring_topic_queue_well2会受到第三条消息。

我们启动运行一下,看一下果然spring_topic_queue_well收到了两条消息,其他队列各收到了一条消息,后面我们可以通过消费者取出消息来进一步验证是否正确。 


写完了生产者,接下来我们写一下消费者,首先也是要对消费者工程进行配置,我们把rabbitmq.properties配置文件直接复制过来,然后创建spring-rabbitmq-consumer.xml配置文件,



    
    

    
    

    
    
    
    
    
    

    
        
        
        
        
        
        
    

这里的监听器类都是需要我们自己定义的,以SpringQueueListener类为例,我们这里直接打印监听到的消息,(其他监听器类代码完全相同,只是类名不同)

public class SpringQueueListener implements MessageListener {
    @Override//回调方法,监听到消息后执行的方法
    public void onMessage(Message message) {
        System.out.println("spring_queue队列消息:"+new String(message.getBody()));//打印监听到的消息
    }
}

都写好之后我们创建一个测试类,加载配置文件进行队列消息监听,查看输出:

验证了一下都是正确的。

六、SpringBoot整合RabbitMQ

在使用SpringBoot整合RabbitMQ时,我们分两个部分进行整合:生产者和消费者。


生产者的整合步骤如下:

1. 创建生产者SpringBoot工程
2. 引入starter依赖坐标
3. 编写yml配置,基本信息配置
4. 定义交换机,队列以及绑定关系的配置类
5. 注入RabbitTemplate,调用方法,完成消息发送

首先我们创建一个SpringBoot的消费者工程,

然后第二步引入对应的依赖,


    org.springframework.boot
    spring-boot-starter-parent
    2.1.4.RELEASE



    
    
        org.springframework.boot
        spring-boot-starter-amqp
    
    
        org.springframework.boot
        spring-boot-starter-test
    

然后我们创建一个rabbitmq的配置文件application.yml,

# 配置rabbitmq的基本信息spring:spring:
spring:
  rabbitmq:
    host: 192.168.49.200
    port: 5672
    username: aoylaotang
    password: 3837
    virtual-host: /test

接着我们定义一个启动类ProducerApplication,

@SpringBootApplication//标志当前类为SpringBoot启动类
public class ProducerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProducerApplication.class);
    }
}

然后是创建一个rabbitmq的配置类RabbitMQConfig,配置交换机、队列及对应的绑定关系。

@Configuration//标志当前类为配置类
public class RabbitMQConfig {
    public static final String EXCHANGE_NAME="boot_topic_exchange";//定义交换机的名字常量
    public static final String QUEUE_NAME="boot_queue";//定义队列的名字常量

    //配置交换机
    @Bean("bootExchange")//将当前方法的返回值作为Bean对象注入Spring容器中
    public Exchange bootExchange(){
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();//创建一个topic类型的交换机
    }

    //配置队列
    @Bean("bootQueue")//将当前方法的返回值作为Bean对象注入Spring容器中
    public Queue bootQueue(){
        return QueueBuilder.durable(QUEUE_NAME).build();//创建队列
    }

    //配置队列和交换机的绑定关系
    @Bean
    //以参数的方式将交换机和队列注入到该方法中,并且指定Bean对象的名称
    public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();//将queue队列绑定到exchange交换机,通配符为boot.#,并且没有参数
    }
}

然后我们创建一个测试类,使用RabbitTemplate对象来发送消息到交换机,并由交换机分发到队列

@SpringBootTest//检索配置文件以及配置类,并加载所有被管理的Bean对象
@RunWith(SpringRunner.class)
public class ProducerTest {
    //注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSend(){
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.666","springboot rabbitmq!");//向交换机发送消息
    }
}

启动测试,打开rabbitmq控制台,查看boot_queue队列已经创建好了,并且也接收到了消息,

我们的生产者这一端就创建好了。


消费者的整合步骤如下:

1. 创建消费者SpringBoot工程
2. 引入starter依赖坐标
3. 编写yml配置,基本信息配置
4. 定义监听类,使用@RabbitListener注解完成队列监听。

首先我们要创建SpringBoot消费者工程,

同样的我们还是引入依赖坐标(和生产者工程一样),还有对应的rabbitmq的配置信息,

然后我们创建一下SpringBoot的引导类ConsumerApplication,

@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class);
    }
}

都配置好了之后我们开始定义监听类RabbitMQListener,

@Component//将当前类实例化到spring容器中
public class RabbitMQListener {
    @RabbitListener(queues = "boot_queue")//监听boot_queue队列的消息,并将消息封装到方法参数message中
    public void ListenerQueue(Message message){
        System.out.println(message);
    }
}

然后我们启动引导类的main方法,启动整个SpringBoot工程,查看输出,

可以看到消息成功从指定队列取出来了

小结:

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/736065.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号