RabbitMq官网:https://www.rabbitmq.com/
一、消息队列的使用场景以及介绍
- 异步处理
- 应用解耦
- 流量削锋
- 日志处理
- 消息通讯
【1】异步处理:场景说明:用户注册后,需要发注册邮件和注册短信。
引入消息队列后架构如下:
引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理。
用户的响应时间=注册信息写入数据库的时间,例如50毫秒。发注册邮箱、发注册短信写入消息队列后,直接返回客户端,因写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。
按照传统的做法:
①、串行方式,将注册信息写入数据库成功后,发注册邮件,再发送注册短信,以上三个成功后,返回客户端。可能需要150毫秒,这样使用消息队列提高了3倍。
②、并行方式,将注册信息写入数据库成功后,发送注册邮件,同时发送注册短信。也可能需要100毫秒,这样使用消息队列提高了2倍。
【2】应用解耦:场景说明:用户下单后,订单系统需要通知库存系统。如下图:
传统模式的缺点:①、库存系统无法访问时,则订单减库存业务将会失败,从而导致订单失败;②、订单系统与库存系统耦合;
引入消息队列:①、用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。②、库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。
☛ 当库存系统不能正常使用时,也不会影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的解耦。
【3】流量削锋:场景说明:秒杀或团抢活动中使用广泛。秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。一般需要在应用前端加入消息队列。
用户请求:服务器接受后,首先写入消息队列。当消息队列长度超出最大数量,则直接抛弃用户请求或跳转至错误页面。
秒杀业务处理:根据消息队列中的请求信息,再做后续处理。
▁▂▃ 这样可以有效的控制活动人数和有效缓解短时间内的高流量冲击,防止压垮应用系统。
【4】日志处理:指将消息队列用在日志处理中,比如 Kafka 的应用,解决大量日志传输的问题。
▷ 日志采集客户端:负责日志数据采集,定时写入 Kafka队列。
▷ kafka消息队列:负责日志数据的接收,存储和转发。
▷ 日志处理应用:订阅并消费 kafka 队列中的日志数据。
【5】消息通信:消息队列一般都内置了高效的通信机制,因此也可以用纯消息通信。比如实现点对点消息队列,或者聊天室。
①、点对点通讯:客户端A和客户端B使用同一队列,进行消息通讯。
②、聊天室通讯(发布订阅模式):客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。
二、消息中间件的工作流程
【1】发送端 MQ-Product
(消息生产者)将消息发送给 MQ-server
;
【2】MQ-server
将消息落地,持久化到数据库等;
【3】MQ-server
回 ACK
给 MQ-Producer
;
【4】MQ-server
将消息发送给消息接收端 MQ-Consumer
(消息消费者);
【5】MQ-Consumer
消费接收到消息后发送 ACK
给 MQ-server
;
【5】MQ-server
将落地消息删除;
https://blog.csdn.net/qq_31484941/article/details/79667018
- 1、主动方应用先把消息发给消息中间件,消息状态标记为“待确认”;
- 2、消息中间件收到消息后,把消息持久化到消息存储中,但并不向被动方应用投递消息;
- 3、消息中间件返回消息持久化结果(成功/失败),主动方应用根据返回结果进行判断如何进行业务操作处理:
- a) 失败:放弃业务操作处理,结束(必要时向上层返回失败结果);
- b) 成功:执行业务操作处理;
- 4、业务操作完成后,把业务操作结果(成功/失败)发送给消息中间件;
- 5、消息中间件收到业务操作结果后,根据业务结果进行处理;
- a) 失败:删除消息存储中的消息,结束;
- b) 成功:更新消息存储中的消息状态为“待发送(可发送)”;
- 6、被动方应用监听并接收“待发送”状态的消息,执行业务处理;
- 7、业务处理完成后,向消息中间件发送ACK,确认消息已经收到(消息中间件将从队列中删除该消息)。
三、RabbitMq基本概念以及组件解释:
RabbitMQ
是一个流行的开源消息队列系统,是AMQP(高级消息队列协议)标准的实现,由以高性能、健壮、可伸缩性出名的Erlang语言开发,并继承了这些优点。
RabbitMQ
是一种消息中间件,用于处理来自客户端的异步消息。服务端将要发送的消息放入到队列池中。接收端可以根据RabbitMQ
配置的转发机制接收服务端发来的消息。RabbitMQ
依据指定的转发规则进行消息的转发、缓冲和持久化操作,主要用在多服务器间或单服务器的子系统间进行通信,是分布式系统标准的配置。
RabbitMq的原理图:
具体组件的详细说明参考:
https://www.cnblogs.com/williamjie/p/9481774.html
1、消息Message
消息。消息是一个不具名的,它是由消息头和消息体组成。
消息体是不透明的,而消息头则是由一系列可选属性组成,这些属性包括:routing-key
(路由键)、priority
(相对于其他消息的优先权)、delivery-mode
(指出消息可能持久性存储)等。
我们可以根据这些属性设置消息的属性。
Message
:消息,服务器和应用程序之间传送的数据,由Properties
和Body
组成。Properties
可以对消息进行修饰, 比如消息的优先级、延迟等高级特性; Body
则就是消息体内容。
2、Message Queue消息队列
我们发送给RabbitMQ的消息最后都会到达各种queue,并且存储在其中(如果路由找不到相应的queue则数据会丢失),等待消费者来取。
消息队列。用来保存消息直到发送给消费者。他是消息的容器,也是消息的终点。
一个消息可以投入一个或者多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
3、Exchange
交换器。
用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
三种常用的交换器类型(7个可以根据它的服务端点击exchange查看):
- direct(发布与订阅 完全匹配)
- fanout(广播)也可以叫扇形交换机
- topic(主题,规则匹配)
- headers
也就是生产者生产的消息不是直接发给队列的,而是由交换器来决定的,由它来决定发给哪一个队列。
问题:那么他是根据什么决定发给哪一个消息队列的呢?
binding(绑定)
4、Binding(绑定)
绑定。用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
5、Routing-key路由键
生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则。这个routing key需要与Exchange Type及binding key联合使用才能生,我们的生产者只需要通过指定routing key来决定消息流向哪里。
路由键,RabbitMQ决定消息该投递到哪一个队列的规则。
队列通过路由键绑定到交换器。
消息发送到MQ服务器时,消息将拥有一个路由键,即便是空的,RabbitMQ也会将其和绑定使用的路由键进行匹配。
如果想匹配,消息将会投递到该队列。
如果不匹配,消息将会进入黑洞。
6、Connection 链接
指的是RabbitMQ服务器和服务建立的TCP链接。
7、Channel信道
Channel中文叫做信道,是TCP里面的虚拟链接。例如:电缆相当于TCP,信道是一个独立的光纤束,一条TCP链接上创建多条信道是没有问题的。
TCP一旦打开,就会创建AMQP信道。
无论是发布消息、接收消息、订阅消息、这些动作都是通过信道完成的。
8、Virtual Host 虚拟主机
表示一批交换器,消息队列和相关对象。虚拟主机是共享的身份认证和加密环境的独立服务器域。
每一个Vhost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。
Vhost是AMQP概念的基础,必须在连接时制定,RabbitMQ默认的Vhost是
9、Broker
表示消息队列服务器实体。
10、交换器和队列的关系
交换器是通过路由键和队列绑定在一起的,如果消息拥有的路由键跟队列和交换器的路由键匹配,那么消息就会被路由到该绑定的队列中。
也就是说,消息到队列的过程中,消息首先会经过交换器,接下来交换器在通过路由键匹配分发到具体的队列中。
路由键可以理解为匹配的规则。
11、RabbitMQ为什么需要信道?为什么不是TCP直接通信?
- TCP的创建和销毁开销特别大。创建需要3次握手,销毁需要4次分手。
- 如果不用信道,那应用程序就会以TCP链接RabbitMQ,高峰时每秒成千上万条链接会造成资源巨大的浪费,而且操作系统每秒处理TCP链接数也是有限制的,必定造成性能瓶颈。
- 信道的原理是一条线程一条通道,多条线程多条通道同用一条TCP链接。一条链接可以容纳无限的信道,即使每秒成千上万的请求也不会成为性能的瓶颈。
五、rabbitmq六种工作模式
- 简单模式
- 工作模式
- 发布订阅模式
- 路由模式
- 主题模式
- RPC模式
生产者发送消息到RabbitMq服务器出现的问题
想想看,当生产者将消息发送给 RabbitMq
时,会出现什么问题?
- 1、正常情况下,生产者直接将消息发送给对应的 exchange(交换机) 以及对应的 队列(queue)
- 2、非正常情况
- ① 消息推送到server,但是在server里找不到交换机
- ② 消息推送到server,找到交换机了,但是没找到队列
- ③ 消息推送到sever,交换机和队列啥都没找到(跟 ① 一样)
- ④ 如果 RabbitMq 服务器挂掉了服务器里面的数据丢失,怎么办?
- ⑤
RabbitMQ 事务机制
存在一个问题:当生产者将消息发送出去之后,如何去确认消息到底有没有到达服务器。
- 1、通过事务机制实现;
- 2、通过发送方确认(publisher confirm )机制实现。
这边先介绍事务机制。
- 相关的方法有三个: channel.txSelect;channel.txCommit和channel.txRollback
- 在发送消息前开启事务:channel.txSelect;
- 消息发送之后提交事务:channel.txCommit;
- 发生异常组要回滚:channel.txRollback
模型图:
- 1、客户端发送Tx.Select. 将信道置为事务模式;
- 2、Broker 回复Tx. Select-Ok. 确认己将信道置为事务模式:
- 3、在发送完消息之后,客户端发送Tx.Commit 提交事务;
- 4、Broker 回复Tx. Commi t-Ok. 确认事务提交
代码:
try {
channel.txSelect();
channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY,false, properties, message.getBytes());
channel.txCommit();
} catch (Exception e) {
e.printStackTrace();
channel . txRollback();
}
事务回滚:当发生异常未能执行channel.txCommit();执行channel . txRollback();即可回滚
模型图:
try {
channel.txSelect();
channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY,false, properties, message.getBytes());
int i = 1/0;
channel.txCommit();
} catch (Exception e) {
e.printStackTrace();
channel . txRollback();
}
1.发送多条消息时,只要用channel.txSelect();channel.txCommit();将发送消息包裹起来即可
2.只有消息成功被RabbitMQ 接收,事务才能提交成功,否则便可在捕获异常之后进行事务回滚,与此同时可以进行消息重发。
3.但是使用事务机制会"吸干" RabbitMQ所以RabbitMQ提供了一个改进方案,即发送方确认机制。
六、生产者弄丢了数据怎么办?
生产者将数据发送到rabbitmq
的时候,可能数据就在半路给搞丢了,因为网络啥的问题,都有可能。
1、使用事务机制
此时可以选择用rabbitmq
提供的事务功能,就是生产者发送数据之前开启rabbitmq
事务(channel.txSelect
),然后发送消息,如果消息没有成功被rabbitmq
接收到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback
),然后重试发送消息;如果收到了消息,那么可以提交事务(channel.txCommit
)。但是问题是,rabbitmq
事务机制一搞,基本上吞吐量会下降,因为太耗性能。
2、confirm 机制(在生产端设置confirm模式)
所以一般来说,如果你要确保说写rabbitmq
的消息别丢,可以开启confirm
模式,在生产者那里设置开启confirm
模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq
中,rabbitmq
会给你回传一个ack
消息,告诉你说这个消息ok了。如果rabbitmq
没能处理这个消息,会回调你一个nack
接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
3、事务机制和confirm机制的区别
事务机制和confirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm
机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息rabbitmq
接收了之后会异步回调你一个接口通知你这个消息接收到了。
所以一般在生产者这块避免数据丢失,都是生成者channel
(通道)调成confirm
模式。下面来看看这个confirm 消息确认机制
Confirm消息确认机制
- 1、消息的确认,是指生产者投递消息后,如果
Broker
收到消息,则会给我们生产者一个应答。 - 2、生产者进行接收应答,用来确定这条消息是否正常的发送到
Broker
,这种方式也是消息的可靠性投递的核心保障。
生产者发送消息给MQ
,MQ
会给生产者一个应答,在生产者的内部有一个Confirm Listener
(监听者),会监听这个应答
这个监听应答是异步的,也就是生产者自己只是发送了消息就不用管了,它内部的监听者会异步监听。
如何实现confirm确认消息?(在生产者上面添加)
- 1、第一步:在
channel
上开启确认模式:channel.confirmSelect()
- 2、第二步:在
channel
上添加监听:addConfirmListener
,监听成功和失败的返回结果,根据具体的结果对消息进行重新发送,或者记录日志等后续处理!
Return消息机制
- 1、
Return Listener
用于处理一些不可路由的消息 - 2、我们的消息生产者,通过指定一个
Exchange
和RoutingKey
,把消息送达到某一个队列中去,然后我们的消息监听队列,进行消费处理操作。 - 3、但是在某些情况下,如果我们在发送消息的时候,当前的
exchange
不存在或者指定的路由key
路由不到,这个时候如果我们需要监听这种不可达的消息,就要使用Return Listener
基础API配置项
- 1、在基础API中有一个关键的配置项
- 2、
Mandatory
:如果true,则监听器会接收到路由不可达的消息,然后进行后续处理,如果为false,那么broker端自动删除该消息
消费端限流
1、什么是消费端的限流?
假设一个场景,首先,我们RabbitMq服务器上有上千上万条未处理的消息,我们随便打开一个消费客户端,会出现下面的情况:
巨量的消息瞬间全部推送过来,但是我们单个客户端无法同时处理这么多的数据,这个时候很有可能导致服务器崩溃,甚至导致整个线上故障
一般生产端没办法限制的,就像流量削封,你根本么有办法去进行限制,它那个时候就是有这么多的消息产生
2、怎么进行限流
RabbitMq
提供一种qos
(服务质量保证)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过基于consume
或者channel
设置Qos
的值)未被确认前,不进行消费新的消息。
RabbitMQ中的消息确认ACK机制
这里说明一下ACK和NACK机制:
其中ACK机制是消费端成功处理消息了,NACK机制表示消费端处理消息失败,会重新发送一个同样的消息
1.什么是消息确认ACK?
如果在处理消息的过程中,消费者的服务器在处理消息时出现异常,那可能这条正在处理的消息就没有完成消息消费,数据就会丢失。为了确保数据不会丢失,RabbitMQ支持消息确认——ACK
2.ACK的消息确认机制
ACK机制是消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ,RabbitMQ收到反馈后才将此消息从队列中删除。
如果一个消费者在处理消息出现了网络不稳定、服务器异常等现象,那么就不会有ACK反馈,RabbitMQ会认为这个消息没有正常消费,会将消息重新放入队列中。
如果在集群的情况下:RabbitMQ会立即将这个消息推送给这个在线的其他消费者。这种机制保证了在消费者服务端故障的时候,不丢失任何消息和任务。
消息永远不会从RabbitMQ中删除:只有当消费者正确发送ACK 反馈,RabbitMQ确认收到后,消息才会从RabbitMQ服务器的数据中删除。
消息的ACK确认记住默认是打开的。
3.ACK机制的开发注意事项
如果忘记了ACK,那么后果很严重。当Consumer退出时,Message会一直重新分发。然后RabbitMQ会占用越来越多的内容,由于RabbitMQ会长时间运行,因此这个“内容泄露”是致命的。
4.修改Consumer配置文件解决ACK反馈问题
怎么防止ACK忘记出现内存泄露呢?
- 方式一:在处理消息当中加上try catch,通过try catch来捕获异常保证消费方正确的运行。
- 方式二:在配置文件里添加重试次数的设定。
消费者配置文件内容:
#开启重试
spring.rabbitmq.listener.retry.enabled=true
#重试次数。默认为3次
spring.rabbitmq.listener.retry.max-attempts=5
消费端的重回队列机制
消费端重回队列是为了对没有处理成功的消息,把消息重新会递给broker
一般我们在实际应用中,都会关闭重回队列,也就是设置为false
TTL队列/消息
1、TTL是Time To Live的缩写,也就是生存空间
2、RabbieMq支持消息的过期时间,在消息发送时可以进行指定
3、RabbitMq支持队列的过期时间,从消息入队列开始计算,只要超过了队列的超时时间配置,那么消息会自动的清除
死信队列
死信队列:DLX, Dead-Letter-Exchange
1、当一个消息没有消费者消费的时候,这个消息在这个队列中就变成死信(dead message),变成死信之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX
2、消息变成死信有一下几种情况
- 消息被拒绝(basic.reject/ basic,nack)并且requeue=false(没有重回队列)
- 消息TTL过期
- 队列已经满了,达到最大长度
3、死信队列的说明
DLX也是一个正常的Exchange,和一般的exchange没有区别,它能在任何队列上被指定,实际上就是设置某一个队列的属性
当这个队列中有死信时,RabbitMq就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列
可以监听这个队列中消息做相应的出理,这个特性可以弥补RabbitMq3.0以前支持的immediate参数的功能
死信队列设置
首先需要设置死信队列的exchange和queue,然后进行绑定
Exchange: dlx.exchange
Queue: dlx.queue
RoutingKey:#
然后我们进行正常声明交换机、队列、绑定,只不过我们需要在队列加上一个参数即可:arguments.put(“x-dead-letter-exchange”,“dlx.exchange”)
这样消息在过期、requeue、队列在达到最大长度时,消息就可以直接路由到死信队列
七、rabbitmq弄丢了数据
就是rabbitmq
自己弄丢了数据,这个你必须开启rabbitmq
的持久化,就是消息写入之后会持久化到磁盘,哪怕是rabbitmq
自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,rabbitmq
还没持久化,自己就挂了,可能导致少量数据会丢失的,但是这个概率较小。
设置持久化有两个步骤
第一个是创建queue
的时候将其设置为持久化的,这样就可以保证rabbitmq
持久化queue
的元数据,但是不会持久化queue
里的数据;
第二个是发送消息的时候将消息的deliveryMode
设置为2,就是将消息设置为持久化的,此时rabbitmq
就会将消息持久化到磁盘上去。
必须要同时设置这两个持久化才行,rabbitmq
哪怕是挂了,再次重启,也会从磁盘上重启恢复queue
,恢复这个queue
里的数据。
而且持久化可以跟生产者那边的confirm机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack了,所以哪怕是在持久化到磁盘之前,rabbitmq挂了,数据丢了,生产者收不到ack,你也是可以自己重发的。
哪怕是你给rabbitmq开启了持久化机制,也有一种可能,就是这个消息写到了rabbitmq中,但是还没来得及持久化到磁盘上,结果不巧,此时rabbitmq挂了,就会导致内存里的一点点数据会丢失。
八、消费端弄丢了数据
简单来说就是:关掉消费者的autoAck机制。
rabbitmq如果丢失了数据,主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,rabbitmq认为你都消费了,这数据就丢了。
这个时候得用rabbitmq提供的ack机制,简单来说,就是你关闭rabbitmq自动ack,可以通过一个api来调用就行,然后每次你自己代码里确保处理完的时候,再程序里ack一把。这样的话,如果你还没处理完,不就没有ack?那rabbitmq就认为你还没处理完,这个时候rabbitmq会把这个消费分配给别的consumer去处理,消息是不会丢的。
九、如何保证消息队列高可用性
rabbitmq镜像集群模式:创建的queue,无论元数据还是queue消息都会存在多个服务器节点上,每次写queue消息都会同步到其他节点上,好处在任何一个节点宕机了还有其他节点可用,不会导致系统也挂掉,坏处在于网络带宽压力大,没什么扩展性而言。怎么开启镜像模式?rabbitmq管理后台有个镜像集群策略,创建queue的时候应用这个策略就行。
十、消费了重复数据怎么解决?(其实就是问如何保证幂等性)
会产出消费重复数据原因:生成者发送了123三条数据,消费者消费12数据 整备提交给生成者说我已消费,但还没提交时候消费者服务器挂了重启,生成者未接收到消费者已消费的消息,于是生成者又发送了123三条数据,就导致了重复消费。
解决:
- 如果是插入数据库,每次插入之前先根据主键id去数据库查下,如果有数据就update
- 如果是插入redis,那把主键id set保存,天然幂等性。如果上面两种情况,那在里面加个全局唯一的id,消费到了后先去redis中去查下,如果消费掉了就处理掉。
十一、如何保证消息按顺序执行?
造成原因:生产者123三条数据发送给三个消费者,本来应该按照123顺序消费,但是应为2数据最先接收到消费了,导致消费顺序错乱了。
解决:把一个queue拆分成多个queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点,单对单模式,把123数据给一个queue,发送给一个消费者执行。
十二、.autodelete(这个属性在@queue注解和@Exchange注解上面都可以加)
- @queue:当所有消费者客户端连接断开后,是否自动删除队列
true:删除
false:不删除 - @Exchange:当所有绑定队列都不在使用时,是否自动删除交换器。
true:删除
false:不删除
这两个的区别在于:删除队列还是删除交换器。
RabbitMQ整合Spring AMQP实战
- RabbitAdmin
- SpringAMQP声明
- RabbitTemplate
- SimpleMessageListenerContainer
- MessageListenerAdapter
- MessageConverter
1、RabbitAdmin
RabbitAdmin类可以很好的操作RabbitMQ,在spring中直接进行注入即可
@bean
public RabbitAdmin rabbitAdmin(ConnentionFactory connectionFactory){
RabbitAdmin rabbitAdmin = new RabbitAdmin(connentionFactory);
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
注意:autoStartup必须要设置为true,否则spring容器不会加载RabbitAdmin类
RabbitAdmin底层实现就是从spring容器中获取Exchange、Binding、RoutingKey以及Queue的@bean声明
然后底层内部使用RabbitTemplate的excute()方法执行对应的声明、修改、删除等一系列RabbitMQ基础功能操作
例如:添加一个交换机、删除一个绑定、清空一个队列里的消息等等
案例:用RabbitAdmin清空一个队列
@Configuration
@ComponentScan({"com.bfxy.spring.*"})
public class RabbitMQConfig {
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses("192.168.11.76:5672");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
return connectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Test
public void contextLoads() {
}
@Autowired
private RabbitAdmin rabbitAdmin;
@Test
public void testAdmin() throws Exception {
rabbitAdmin.declareExchange(new DirectExchange("test.direct", false, false));
rabbitAdmin.declareExchange(new TopicExchange("test.topic", false, false));
rabbitAdmin.declareExchange(new FanoutExchange("test.fanout", false, false));
rabbitAdmin.declareQueue(new Queue("test.direct.queue", false));
rabbitAdmin.declareQueue(new Queue("test.topic.queue", false));
rabbitAdmin.declareQueue(new Queue("test.fanout.queue", false));
rabbitAdmin.declareBinding(new Binding("test.direct.queue",
Binding.DestinationType.QUEUE,
"test.direct", "direct", new HashMap<>()));
rabbitAdmin.declareBinding(
BindingBuilder
.bind(new Queue("test.topic.queue", false)) //直接创建队列
.to(new TopicExchange("test.topic", false, false)) //直接创建交换机 建立关联关系
.with("user.#")); //指定路由Key
rabbitAdmin.declareBinding(
BindingBuilder
.bind(new Queue("test.fanout.queue", false))
.to(new FanoutExchange("test.fanout", false, false)));
//清空队列数据
rabbitAdmin.purgeQueue("test.topic.queue", false);
}
2、SpringAMQP声明
在Rabbit基础API里面声明一个Exchange、声明一个绑定、一个队列
使用SpringAMQP去声明,就需要使用SpringAMQP的如下模式,即声明@Bean方式
案例:
/**
* 针对消费者配置
* 1. 设置交换机类型
* 2. 将队列绑定到交换机
FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念
HeadersExchange :通过添加属性key-value匹配
DirectExchange:按照routingkey分发到指定队列
TopicExchange:多关键字匹配
*/
@Bean
public TopicExchange exchange001() {
return new TopicExchange("topic001", true, false);
}
@Bean
public Queue queue001() {
return new Queue("queue001", true); //队列持久
}
@Bean
public Binding binding001() {
return BindingBuilder.bind(queue001()).to(exchange001()).with("spring.*");
}
@Bean
public TopicExchange exchange002() {
return new TopicExchange("topic002", true, false);
}
@Bean
public Queue queue002() {
return new Queue("queue002", true); //队列持久
}
@Bean
public Binding binding002() {
return BindingBuilder.bind(queue002()).to(exchange002()).with("rabbit.*");
}
@Bean
public Queue queue003() {
return new Queue("queue003", true); //队列持久
}
@Bean
public Binding binding003() {
return BindingBuilder.bind(queue003()).to(exchange001()).with("mq.*");
}
@Bean
public Queue queue_image() {
return new Queue("image_queue", true); //队列持久
}
@Bean
public Queue queue_pdf() {
return new Queue("pdf_queue", true); //队列持久
}
测试启动会直接在RabbitMq上面生成相应的exchange和queue
3、RabbitTemplate,即消息模板
1)RabbitTemplate,是我们在与SpringAMQP整合的时候进行发送消息的关键类
2)该类提供了丰富的发送消息方法,包括可靠性投递消息方法、回调监听消息接口ConfirmCallback、返回值确认接口ReturnCallback等等。同样能我们需要进行注入到Spring容器中,然后直接使用
3)在与spring整合时需要进行实例化,但是在与SpringBoot整合时,在配置文件里添加配置即可
怎么使用?
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
return rabbitTemplate;
}
单元测试:
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage() throws Exception {
//1 创建消息
MessageProperties messageProperties = new MessageProperties();
messageProperties.getHeaders().put("desc", "信息描述..");
messageProperties.getHeaders().put("type", "自定义消息类型..");
Message message = new Message("Hello RabbitMQ".getBytes(), messageProperties);
rabbitTemplate.convertAndSend("topic001", "spring.amqp", message, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
System.err.println("------添加额外的设置---------");
message.getMessageProperties().getHeaders().put("desc", "额外修改的信息描述");
message.getMessageProperties().getHeaders().put("attr", "额外新加的属性");
return message;
}
});
}
4、SimpleMessageListenerContainer——简单消息监听容器
这个类非常的强大,我们可以对他进行很多的设置,对于消费者的配置项,这个类都可以满足
1)监听队列(多个队列)、自动启动、自动声明功能
2)设置事务特性、事务管理器、事务属性、事务容量(并发)、是否开启事务、回滚消息等
3)设置消费者数量、最小最大数量、批量消费
4)设置消息确认和自动确认模式、是否重回队列、异常捕获handler函数
5)设置消费者标签生成策略、是否独占模式、消费者属性等
6)设置具体的监听器、消息转换器等等
注意:SimpleMessageListenerContainer可以进行动态设置,比如在运行中的应用可以动态的修改其消费者数量的大小、接收消息的模式等
很多基于RabbitAMQP的自制定化后端管控台在进行动态设置的时候,也是根据这一特性去实现的。所以可以看出SpringAMQP非常大的强大
问题:
SimpleMessageListenerContainer为什么可以动态感知配置变更?就是在服务器启动之后修改它的配置项目,为什么能感知到呢?他是以什么方式去做呢?
@Bean
public SimpleMessageListenerContainer messageContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
//SimpleMessageListenerContainer 可以同时去监听多个队列
container.setQueues(queue001(), queue002(), queue003(), queue_image(), queue_pdf());
//当前的消费者数量
container.setConcurrentConsumers(1);
//当前最多的消费者数量
container.setMaxConcurrentConsumers(5);
//是否重回队列,false表示不回队列 true表示回队列
container.setDefaultRequeueRejected(false);
//自动签收
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
//
container.setExposeListenerChannel(true);
//消费端标签策略,在消费端产生标签的话根据自己的策略
container.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String queue) {
return queue + "_" + UUID.randomUUID().toString();
}
});
/**
//消息发送之后通过ChannelAwareMessageListener这个接口进行监听
container.setMessageListener(new ChannelAwareMessageListener() {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
String msg = new String(message.getBody());
System.err.println("----------消费者: " + msg);
}
});
5、MessageListenerAdapter——消息监听适配器
通过MessageListenerAdapter的代码我们可以看出如下核心属性
defaultListenerMethod默认监听方法名称:用于设置监听方法名称
Delegate委托对象:实际真实的委托对象,用于处理消息
queueOrTagToMethodName 队列标识与方法名称组成的集合
可以一 一进行队列与方法名称的匹配
队列和方法名称绑定,即指定队列里的消息会被绑定的方法所接受处理
6、MessageConverter——消息转换器
我们在进行发送消息的时候,正常情况下消息体为二进制的数据方法进行传输,如果希望内部帮我们进行转换,或者指定自定义的转换器,就需要用到MessageConverter(这是一个接口)
1)这是常用的String类型的转换器
自定义常用转换器:MessageConverter,一般来讲都需要实现这个接口
并重写下面两个方法:
toMessage:java对象转换为Message
fromMessage: Message对象转换为java对象
2)Json转换器:Jackson2JsonMessageConverter:可以进行java对象转化成jason的转换功能
3)DefaultJackson2JavaMapper映射器:可以进行java对象的映射关系
4)自定义二进制转换器:比如图片类型、PDF、PPT、流媒体
SpringBoot整合RabbitMq
生产端配置
1、publisher-confirms,实现一个监听器用于监听Broker端给我们返回的确认请求:RabbitTemplate.ConfirmCallback()
2、publisher-returns,保证消息对Broker端是可达的,如果出现路由键不可达的情况,则使用监听器对不可达的消息进行后续的处理,保证消息的路由成功:RabbitTemplate.ReturnCallback(), 注意: 这是一个失败回调, 只有消息从Exchange路由到Queue失败才会回调这个方法
注意:
注意一点,在发送消息的时候对template进行配置mandatory=true保证监听有效
// 触发setReturnCallback回调必须设置mandatory=true, 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发回调
rabbitTemplate.setMandatory(true);
上面三种都可以在配置文件中配置:
//也可以在配置文件中设置
spring.rabbitmq.publisher-confirms= true
spring.rabbitmq.publisher-returns= true
spring.rabbitmq.template.mandatory = true
3、生产端还可以配置其他属性,比如发送重试,超时时间、次数、间隔等
消费端核心配置
# 设置手动确认(ack) Queue -> C
spring.rabbitmq.listener.simple.acknowledge-mode=MANUAL
# 监听消费者的个数
spring.rabbitmq.listener.simple.concurrency=1
# 监听消费者的最大个数
spring.rabbitmq.listener.simple.max-concurrency=5
首先配置手工确认模式,用于ACK的手工处理,这样我们可以保证消息的可靠性送达,或者再消费端失败的时候可以做到重回队列、根据业务记录日志等处理
可以设置消费端的监听个数和最大个数,用于控制消费端的并发情况
@RabbitListener注解使用
消费端监听@RabbitListener注解,这个对于在实际工作中非常的好用
@RabbitListener是一个组合注解,里面可以注解配置@QueueBinding、@Queue、@Exchange直接通过这个组合注解一次性搞定消费端交换机、队列、绑定、路由、并且配置监听功能等
SpringBoot整合RabbitMQ二
https://blog.csdn.net/qq_35387940/article/details/100514134
https://www.jianshu.com/p/8614ff9ff0f1
这个参考不是很复杂,但是能给一点启发:
https://zhuanlan.zhihu.com/p/109368630
Spring Cloud Stream整合RabbitMq
Spring Cloud Stream 如何与RabbitMq进行集成的?
一个基于 RabbitMQ 的可复用的分布式事务消息架构方案!
参考:https://mp.weixin.qq.com/s/h3ycG7Vm3Oa_PZKQ2VNU5w
未看完:
视频参考地址:https://www.bilibili.com/video/BV1ck4y1r77k?p=1
此视频的笔记地址:https://blog.csdn.net/napoluen/article/details/106492867
次视频的源码文件:本地 E:\workspace\mq\coding-262
https://blog.csdn.net/fox_bert/article/details/101431115
四、rabbitmq安装(9步)
1、Linux上的安装
在centos7上安装rabbitmq
1.安装erlang语言库
RabbitMQ使用了Erlang开发语言,Erlang是为电话交换机开发的语言,天生自带高并发光环,和高可用特性
Erlang是一种通用的面向并发的编程语言。
rabbitmq官方精简的Erlang语言包
0依赖rpm安装包
https://github.com/rabbitmq/erlang-rpm
下载和安装
安装路径:/usr/local/src
# 下载Erlang语言包
wget https://github.com/rabbitmq/erlang-rpm/releases/download/v21.2.6/erlang-21.2.6-1.el7.x86_64.rpm
# 安装Erlang
rpm -ivh erlang-21.2.6-1.el7.x86_64.rpm --force --nodeps
2.安装socat依赖
socat依赖包
https://pkgs.org/download/socat
https://centos.pkgs.org/7/centos-x86_64/socat-1.7.3.2-2.el7.x86_64.rpm.html
下载和安装
# 下载 socat rpm
wget http://mirror.centos.org/centos/7/os/x86_64/Packages/socat-1.7.3.2-2.el7.x86_64.rpm
# 安装 socat 依赖包
rpm -ivh socat-1.7.3.2-2.el7.x86_64.rpm
3.安装rabbitmq
rabbitmq安装包
https://www.rabbitmq.com/install-rpm.html#downloads
下载和安装
# 下载 rpm 包
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.13/rabbitmq-server-3.7.13-1.el7.noarch.rpm
# 安装 rpm 包
rpm -ivh rabbitmq-server-3.7.13-1.el7.noarch.rpm
4.rabbitmq启动和停止命令
# 设置服务,开机自动启动
chkconfig rabbitmq-server on
# 启动服务
service rabbitmq-server start
# 停止服务
service rabbitmq-server stop
5.rabbitmq管理界面
启用管理界面
# 开启管理界面插件
rabbitmq-plugins enable rabbitmq_management
# 防火墙打开 15672 管理端口
firewall-cmd --zone=public --add-port=15672/tcp --permanent
firewall-cmd --reload
6.访问
访问服务器的15672端口,例如:
http://192.168.64.140:15672
7.添加用户
# 添加用户
rabbitmqctl add_user admin admin
# 新用户设置用户为超级管理员
rabbitmqctl set_user_tags admin administrator
8.设置访问权限
用户管理参考
https://www.cnblogs.com/AloneSword/p/4200051.html
9.开放客户端连接端口
# 打开客户端连接端口
firewall-cmd --zone=public --add-port=5672/tcp --permanent
firewall-cmd --reload
主要端口介绍
4369 – erlang发现口
5672 – client端通信口
15672 – 管理界面ui端口
25672 – server间内部通信口