消息队列基础
RabbitMQ入门
基本介绍
主要概念:
- publisher:生产者,即为发送消息的程序
- consumer:消费者,即为消费消息的程序
- queue:队列,存储消息的缓存区。生产者发送消息暂存在队列中,等待消费者来消费处理
- exchange:交换机,服务消息的录音。生产者发送的消息到交换机,并根据对应的key决定发送到哪个队列内
- virtual host:虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,根据对应的权限来决定可以操作的交换机和队列

安装部署
通过docker来进行mq的安装
docker run \
-e RABBITMQ_DEFAULT_USER=itheima \
-e RABBITMQ_DEFAULT_PASS=123321 \
-v mq-plugins:/plugins \
--name mq \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3.8-management
⭕
Tips:需要牢记的知识点
- 15672:RabbitMQ提供的管理控制台的端口
- 5672:RabbitMQ为程序内发送和处理消息的端口
依赖引入:由于RabbitMQ采用了AMQP(Advanced Message Queuing Protocol) 协议,官方文档内的Java实现代码较为复杂,在spring的环境中以及集成了一套模板工具SpringAMQP,使用较为方便
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
添加配置文件内容
spring:
rabbitmq:
host: 192.168.101.68 # 你的虚拟机IP
port: 5672 # 发送和处理消息端口
virtual-host: / # 虚拟主机
username: itheima # 用户名
password: 123321 # 密码
接收模式介绍
消息消费者在RabbitMQ中主要有两种接收消息的模式:
方式一、推模式(push):默认采用该模式来接收消息,一旦消费者监听了一个队列后,只有队列内有消息接收到,就会自动推到消费者对应的监听程序内,消费者无需持续访问队列是否有新消息
方式二、拉模式(pull):即消费者主动请求队列来获取消息
在实际使用过程内,推模式居多,但是根据业务场景的不同也可能会使用拉模式来控制获取消息,消费消息的时机;
数据隔离
在实际的开发过程中,为了节约成本,可能会有多个项目同时使用一个RabbitMQ服务器的情况,并且RabbitMQ也是支持多租户的。这个时候就需要避免各个项目直接出现互相干扰的情况,因此对于不同的项目就会设置不同的虚拟主机来解决这种情况
实体步骤:
①、通过默认用户登录控制台,点击Admin选项卡,出现用户管理界面

字段介绍
- name:即为用户名
- tags:为用户的角色,通过角色来实现权限的分配,administrator为超级管理员
- can access virtual host:可以范围的主机范围,/ 是默认的主机
②、点击add a user 来添加新的用户,一般用户名设置为对应的项目名称,权限设置为超级管理员
③、默认创建好的用户的没有任何主机的访问权限的,需要登录新建的该用户进行授权,登录新建的用户点击Virtual hosts菜单,给该用户创建项目单独的virtual host一般为 / 项目名
④、完成授权后即可查看到对应的can access virtual hosts 目录,之后通过此用户创建的队列和交换机都会在对应的虚拟主机目录下,实现数据隔离
RabbitMQ工作模型
工作队列模型(WorkQueues)
工作队列模型是处理消息最简单的一种模型方式,就是将消费者之间绑定到对应的队列,并且为了能够处理大量的消息,可以绑定多个消费者到队列中,从而提高效率

由于绑定了多个消费者,如果没有对消费者接收消息的限制,队列会把收到的消息平均分配给绑定了队列的消费者,但是这样会存在处理能力强的消费者在处理完成全部的消息,但是处理能力弱的消费者由于已经接收了消息了,不管快慢也需要全部处理,这样就无法充分利于每个消费者的能力,为了能够使每个人消费者的处理效率可以得到完全的发挥,就需要添加对消息获取的限制
可以在配置文件内添加如下的配置
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
添加了上述的配置后,由于必须处理完消息后才可以获取下一条消息,这样消费者就会根据自身的处理能力,来处理更多的消息。这样也可以避免处理能力慢导致的消息积压问题。
发布订阅模型
在工作队列模型内,由于消息直接发送到队列中,那么只能由一个消费者获取这个消息,处理完后这个消息就会在队列中消失,很多时候这样的工作模式无法满足业务的要求,比如发送消息方,在发送消息后,同时有两个业务需要接收这个消息同时执行不同的业务场景,这样单一的工作队列模式就无法实现,就需要采用发布订阅模型

在发布订阅模型中,引入了交换机,生产者将消息发送到交换机后,由交换机将消息推送到队列中,由交换机进行消息的转发到队列中,具体的转发规则就需要根据不同的交换机类型来配置
交换机类型
fanout交换机
可以理解为广播类型,会将消息交给所以绑定到交换机的队列
控制台配置步骤
①、创建一个交换机,并将类型选择为fanout
②、创建两个队列,并帮到到上面创建的交换机内
测试
模拟生产者发送消息
@Test
public void testFanoutExchange() {
// 交换机名称
String exchangeName = "project.fanout";
// 消息
String message = "hello, everyone!";
rabbitTemplate.convertAndSend(exchangeName, "", message);
}
查看控制台的队列消息接收

模拟不同业务消费消息
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
}
如果说同一个业务部署了集群,即同一个队列绑定了同一个业务的消费者,那么不是所有该业务的消费者都会收到消息,消息的推送会默认采用轮询的方式分配给不同的消费者处理
Direct交换机
在fanout交换机内,由于没有任何条件的限制,所有的绑定的队列上都会收到从交换机转发的消息,但是在部分业务场景下,消息可以指定某些队列来处理,而不是所有队列都处理,者就需要用到Direct交换机

在Direct交换机下:
- 队列在绑定交换机时,不能直接绑定需要指定一个RoutingKey,并且一个队列可以使用不同的RoutingKey来绑定同一个交换机
- 消息的发送方在发送消息到交换机时,也需要指定对应的RoutingKey
- 交换机不会再收到消息后广播所有的队列,而是根据对应的RoutingKey找到绑定了指定该key的队列,转发消息
如上图所示,来进行测试
①、创建一个交换机,指定类型为direct
②、创建两个队列,队列一通过red和blue作为RoutingKey绑定到上述交换机、队列二通过yellow和red作为RoutingKey绑定到上述交换机内
编写监听城西
@RabbitListener(queues = "direct.queue1")
public void listenDirectQueue1(String msg) {
System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}
@RabbitListener(queues = "direct.queue2")
public void listenDirectQueue2(String msg) {
System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}
场景一:发送key为red的消息到交换机
@Test
public void testSendDirectExchange() {
// 交换机名称
String exchangeName = "project.direct";
// 消息
String message = "红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";
// 发送消息
rabbitTemplate.convertAndSend(exchangeName, "red", message);
}
查看控制台入住输出,可以看到由于两个队列都通过red绑定了交换机,都收到了消息

场景二 :通过blue为key发送消息
@Test
public void testSendDirectExchange() {
// 交换机名称
String exchangeName = "project.direct";
// 消息
String message = "最新报道,哥斯拉是居民自治巨型气球,虚惊一场!";
// 发送消息
rabbitTemplate.convertAndSend(exchangeName, "blue", message);
}
查看日志只有通过blue为key绑定的队列获取到了消息

场景三:通过yellow为key发送消息(略)
Topic交换机
Topic在Direct交换机的基础上进行了一定的增强,它可以允许在队列通过key绑定交换机的时候使用通配符;
具体的规则如下:
- 一般key都是一个或者多个单词组成,多个单词可以使用
.来进行分割。比如A.B.C - 通配符使用规则
#: 表示匹配零个或者多个词*:匹配恰好一个词
比如:
china.#:表示能够匹配以china开头的所有keychina.*:表示只能匹配以china开头,后面只有一个词的key

创建队列和交换机的方式
在实际开发的过程中,使用控制台来直接创建交换机和队列,并绑定队列到交换机,这样的场景是很少的,因为项目在上线后,如果交给运维来在控制台创建上述的内容,很容易出问题,那么久可以选择通过程序代码的方式,在项目启动后自动检查队列与交换机是否存在,如果不存在则会创建需要的队列与交换机,这种做法是强烈推荐的。
基于API的方式(了解即可)
创建队列
SpringAMQP提供了一个Queue类,可以来创建队列

代码实现:首先需要定义一个配置类,在这个类里面来声明bean从而实现创建队列
// 创建队列
@Bean
public Queue directQueue1() {
return new Queue("direct.queue1");
}
@Bean
public Queue directQueue2() {
return QueueBuilder.durable("direct.queue2").build();
}
创建交换机
Spring还提供了一个Exchange接口,可以表示不同类型的交换机,只需要找到对应类型交换机的实现类即可声明

下面以Direct交换机为例子
// 创建交换机
@Bean
public DirectExchange directExchange() {
// return new DirectExchange("direct.exchange");
return ExchangeBuilder.directExchange("direct.exchange").build();
}
建立绑定关系
通过BindBuilder来创建Binding对象

具体实现:以之前的Direct为例子
// 建立绑定关系
@Bean
public Binding directQueue1ToDirectExchangeRed(Queue directQueue1, DirectExchange directExchange) {
return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
}
@Bean
public Binding directQueue1ToDirectExchangeBlue(Queue directQueue1, DirectExchange directExchange) {
return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");
}
@Bean
public Binding directQueue2ToDirectExchangeRed(Queue directQueue2, DirectExchange directExchange) {
return BindingBuilder.bind(directQueue2).to(directExchange).with("red");
}
@Bean
public Binding directQueue2ToDirectExchangeYellow(Queue directQueue2, DirectExchange directExchange) {
return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");
}
基于注解声明(熟练掌握)
在上述基于api通过@Bean的方式来声明交换机和队列可以发现,对应的代码的编写还是很麻烦的而且臃肿的,因此Spring还提供了基于注解的声明方式
使用的注解就是监听队列的注解@RabbitListener,但是这里声明需要定义对应的bindings属性
下面以Direct为例子
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
key = {"red", "blue"}
))
public void listenDirectQueue1(String msg){
System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg){
System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}

查看对应的注解源码可以发现,
- bindings的值为一个@QueueBinding的集合
- QueueBinding也存在几个属性
- value是一个@Queue注解,可以通过它的name属性来声明对应的队列名
- exchange是一个@Exchange注解,可以通过name属性来声明交换机名称,type属性类声明对应交换机的类型
- key则是一个数组,来声明交换机和队列之间的RoutingKey
其他类型的声明方式也可以以此类推

浙公网安备 33010602011771号