消息队列——RabbitMq

目录

    • 一、消息队列的使用场景
    • 二、消息中间件的工作流程
    • 三、RabbitMq基本概念以及组件解释:
    • 四、rabbitmq安装(9步)
    • 五、rabbitmq六种工作模式
    • 六、生产者弄丢了数据
    • Confirm确认消息
    • Return消息机制
    • 消费端限流
    • RabbitMQ中的消息确认ACK机制
    • 消费端的重回队列机制
    • TTL队列/消息
    • 死信队列
    • 七、rabbitmq弄丢了数据
    • 八、消费端弄丢了数据
    • 九、如何保证消息队列高可用性
    • 十、消费了重复数据怎么解决?(其实就是问如何保证幂等性)
    • 十一、如何保证消息按顺序执行?
    • 十二、.autodelete(这个属性在@queue注解和@Exchange注解上面都可以加)
    • RabbitMQ整合Spring AMQP实战
    • SpringBoot整合RabbitMq
  • SpringBoot整合RabbitMQ二
    • Spring Cloud Stream整合RabbitMq
    • 一个基于 RabbitMQ 的可复用的分布式事务消息架构方案!

RabbitMq官网:https://www.rabbitmq.com/

一、消息队列的使用场景

  • 异步处理
  • 应用解耦
  • 流量削锋
  • 日志处理
  • 消息通讯
    【1】异步处理:场景说明:用户注册后,需要发注册邮件和注册短信。
    消息队列——RabbitMq_第1张图片
    引入消息队列后架构如下:
    引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理。
    用户的响应时间=注册信息写入数据库的时间,例如50毫秒。发注册邮箱、发注册短信写入消息队列后,直接返回客户端,因写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。
    按照传统的做法:
    ①、串行方式,将注册信息写入数据库成功后,发注册邮件,再发送注册短信,以上三个成功后,返回客户端。可能需要150毫秒,这样使用消息队列提高了3倍。
    消息队列——RabbitMq_第2张图片
    ②、并行方式,将注册信息写入数据库成功后,发送注册邮件,同时发送注册短信。也可能需要100毫秒,这样使用消息队列提高了2倍。
    消息队列——RabbitMq_第3张图片

【2】应用解耦:场景说明:用户下单后,订单系统需要通知库存系统。如下图:
消息队列——RabbitMq_第4张图片
传统模式的缺点:①、库存系统无法访问时,则订单减库存业务将会失败,从而导致订单失败;②、订单系统与库存系统耦合;
引入消息队列:①、用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。②、库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。
☛ 当库存系统不能正常使用时,也不会影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的解耦。
消息队列——RabbitMq_第5张图片
【3】流量削锋:场景说明:秒杀或团抢活动中使用广泛。秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。一般需要在应用前端加入消息队列。
消息队列——RabbitMq_第6张图片
用户请求:服务器接受后,首先写入消息队列。当消息队列长度超出最大数量,则直接抛弃用户请求或跳转至错误页面。
秒杀业务处理:根据消息队列中的请求信息,再做后续处理。
▁▂▃ 这样可以有效的控制活动人数和有效缓解短时间内的高流量冲击,防止压垮应用系统。

【4】日志处理:指将消息队列用在日志处理中,比如 Kafka 的应用,解决大量日志传输的问题。
消息队列——RabbitMq_第7张图片
▷ 日志采集客户端:负责日志数据采集,定时写入 Kafka队列。
▷ kafka消息队列:负责日志数据的接收,存储和转发。
▷ 日志处理应用:订阅并消费 kafka 队列中的日志数据。
【5】消息通信:消息队列一般都内置了高效的通信机制,因此也可以用纯消息通信。比如实现点对点消息队列,或者聊天室。
①、点对点通讯:客户端A和客户端B使用同一队列,进行消息通讯。
消息队列——RabbitMq_第8张图片
②、聊天室通讯(发布订阅模式):客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。
消息队列——RabbitMq_第9张图片

二、消息中间件的工作流程

消息队列——RabbitMq_第10张图片
【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
消息队列——RabbitMq_第11张图片

  1. 主动方应用先把消息发给消息中间件,消息状态标记为“待确认”;
  2. 消息中间件收到消息后,把消息持久化到消息存储中,但并不向被动方应用投递消息;
  3. 消息中间件返回消息持久化结果(成功/失败),主动方应用根据返回结果进行判断如何进行业务操作处理:
    a) 失败:放弃业务操作处理,结束(必要时向上层返回失败结果);
    b) 成功:执行业务操作处理;
  4. 业务操作完成后,把业务操作结果(成功/失败)发送给消息中间件;
  5. 消息中间件收到业务操作结果后,根据业务结果进行处理;
    a) 失败:删除消息存储中的消息,结束;
    b) 成功:更新消息存储中的消息状态为“待发送(可发送)”;
  6. 被动方应用监听并接收“待发送”状态的消息,执行业务处理;
  7. 业务处理完成后,向消息中间件发送ACK,确认消息已经收到(消息中间件将从队列中删除该消息)。

三、RabbitMq基本概念以及组件解释:

RabbitMQ是一个流行的开源消息队列系统,是AMQP(高级消息队列协议)标准的实现,由以高性能、健壮、可伸缩性出名的Erlang语言开发,并继承了这些优点。
RabbitMQ是一种消息中间件,用于处理来自客户端的异步消息。服务端将要发送的消息放入到队列池中。接收端可以根据RabbitMQ配置的转发机制接收服务端发来的消息。RabbitMQ依据指定的转发规则进行消息的转发、缓冲和持久化操作,主要用在多服务器间或单服务器的子系统间进行通信,是分布式系统标准的配置。
RabbitMq的原理图:
消息队列——RabbitMq_第12张图片
具体组件的详细说明参考:
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安装(9步)

在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
消息队列——RabbitMq_第13张图片
https://centos.pkgs.org/7/centos-x86_64/socat-1.7.3.2-2.el7.x86_64.rpm.html
消息队列——RabbitMq_第14张图片
下载和安装

# 下载 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
消息队列——RabbitMq_第15张图片
下载和安装

# 下载 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.设置访问权限
消息队列——RabbitMq_第16张图片
用户管理参考
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间内部通信口

五、rabbitmq六种工作模式

  1. 简单模式
  2. 工作模式
  3. 发布订阅模式
  4. 路由模式
  5. 主题模式
  6. RPC模式

六、生产者弄丢了数据

生产者将数据发送到rabbitmq的时候,可能数据就在半路给搞丢了,因为网络啥的问题,都有可能。

此时可以选择用rabbitmq提供的事务功能,就是生产者发送数据之前开启rabbitmq事务(channel.txSelect),然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback),然后重试发送消息;如果收到了消息,那么可以提交事务(channel.txCommit)。但是问题是,rabbitmq事务机制一搞,基本上吞吐量会下来,因为太耗性能。

所以一般来说,如果你要确保说写rabbitmq的消息别丢,可以开启confirm模式,在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉你说这个消息ok了。如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。

事务机制和cnofirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息rabbitmq接收了之后会异步回调你一个接口通知你这个消息接收到了。

所以一般在生产者这块避免数据丢失,都是生成者channel(通道)调成confirm模式。

Confirm确认消息

1、消息的确认,是指生产者投递消息后,如果Broker收到消息,则会给我们生产者一个应答。
2、生产者进行接收应答,用来确定这条消息是否正常的发送到Broker,这种方式也是消息的可靠性投递的核心保障。
消息队列——RabbitMq_第17张图片
生产者发送消息给MQ,MQ会给生产者一个应答,在生产者的内部有一个Confirm Listener(监听者),会监听这个应答
这个监听应答是异步的,也就是生产者自己只是发送了消息就不用管了,它内部的监听者会异步监听。

如何实现confirm确认消息?(在生产者上面添加)
1、第一步:在channel上开启确认模式:channel.confirmSelect()
2、第二步:在channel上添加监听:addConfirmListener,监听成功和失败的返回结果,根据具体的结果对消息进行重新发送,或者记录日志等后续处理!

Return消息机制

1、Return Listener用于处理一些不可路由的消息
2、我们的消息生产者,通过指定一个Exchange和RoutingKey,把消息送达到某一个队列中去,然后我们的消息监听队列,进行消费处理操作。
3、但是在某些情况下,如果我们在发送消息的时候,当前的exchange不存在或者指定的路由key路由不到,这个时候如果我们需要监听这种不可达的消息,就要使用Return Listener
消息队列——RabbitMq_第18张图片
基础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、声明一个绑定、一个队列
消息队列——RabbitMq_第19张图片
使用SpringAMQP去声明,就需要使用SpringAMQP的如下模式,即声明@Bean方式
消息队列——RabbitMq_第20张图片
案例:

/**  
     * 针对消费者配置  
     * 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直接通过这个组合注解一次性搞定消费端交换机、队列、绑定、路由、并且配置监听功能等
消息队列——RabbitMq_第21张图片

SpringBoot整合RabbitMQ二

本章节复制地址:https://mp.weixin.qq.com/s/1Xxy_mzaiW3i1lghL4DUFw
或者https://blog.csdn.net/qq_35387940/article/details/100514134

参考:https://blog.csdn.net/u013871100/article/details/82982235
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)