RabbitMQ是一个由erlang开发的AMQP
(Advanced Message Queue 高级消息队列协议 )的开源实现,能够实现异步消息处理
RabbitMQ是一个消息代理:它接受和转发消息
。
你可以把它想象成一个邮局:当你把你想要发布的邮件放在邮箱中时,你可以确定邮差先生最终将邮件发送给你的收件人。在这个比喻中,RabbitMQ是邮政信箱,邮局和邮递员。
RabbitMQ和邮局的主要区别在于它不处理纸张,而是接受,存储和转发二进制数据块
总的来说的话,rabbitmq具有以下的几点优点:
应用解耦
异步提速
削峰填古
connectionFactory
(连接管理器):应用程序与RabbitMQ之间建立连接的管理器
Channel
(信道):消息推送使用的通道
Exchange
(交换器):用于接受、分配消息
Queue
(队列):用于存储生产者的消息
RoutingKey
(路由键):生产者将消息发送给交换器的时候,会指定一个RoutingKey,用来指定这个消息的路由规则,这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
BindKey
(绑定键):用于把交换器的消息绑定到队列上
生产者
:消息的创建者,负责创建和推送数据到消息服务器
消费者
:消息的接收方,用于处理数据和确认消息
代理
:就是RabbitMQ本身,用于扮演快递的角色,本身并不生产消息
①广播式交换器类型(Fanout)
该类交换器不分析所接收到消息中的 Routing Key,默认将消息转发到所有与该交换器绑定的队列
中去。
②直接式交换器类型(Direct)
该类交换器需要精确匹配 Routing Key 与 Binding Key,如消息的 Routing Key = Cloud,那么该条消息只能被转发至 Binding Key = Cloud 的消息队列中去。
③主题式交换器(Topic Exchange)
该类交换器通过消息的 Routing Key 与 Binding Key 的模式匹配,将消息转发至所有符合绑定规则的队列中。
Binding Key 支持通配符,其中“*”匹配一个词组
,“#”匹配多个词组
(包括零个)。
RabbitMQ是基于信道Channel的方式
来传输数据,排除了使用TCP链接来进行数据的传输,因为TCP链接创建和销毁对于系统性能的开销比较大,且并发能力受系统资源的限制,这样很容易造成rabbitMQ的性能瓶颈。
消费者链接RabbitMQ其实就是一个TCP链接,一旦链接创建成功之后,就会基于链接创建Channel,每个线程把持一个Channel,Channel复用TCP链接,减少了系统创建和销毁链接的消耗
,提高了性能
1. publisher-confirms(发送方确认模式)
2. 消息持久化,当然前提是队列和交换机必须持久化
持久化
exchange要持久化
queue要持久化
message要持久化
3. 消息确认机制(ACK)
ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有三种确认方式:
自动确认:acknowledge="none"
手动确认:acknowledge="manual"
根据异常情况确认:acknowledge=“auto”
当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收。
消息持久化
;ACK确认机制
;设置集群镜像模式
;消息补偿机制
支持
简单模式
工作队列模式
发布订阅模式:
路由模式
Topics 通配符模式
如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);
方法确认签收消息
如果出现异常,则在catch中调用 basicNack或 basicReject
,拒绝消息,让MQ重新发送消息。
对于交换机(exchange)与队列(queue)的持久化只需要将durable属性设置为true即可
,
当重启RabbitMQ服务后,交换机和队列都会恢复,但是当只有队列的durable属性设置为true时,重启后会造成消息丢失。
DLX,全称为 Dead-Letter-Exchange,死信交换器,死信邮箱。当消息在一个队列中变成死信 (dead message) 之后,
它能被重新被发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。
可以认为是无限制
,因为限制取决于机器的内存
,但是消息过多会导致处理效率的下降。
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
TTL+死信队列
组合实现延迟队列
的效果。
产生原因有可能是
消费端宕机
消费端消费能力不足
生产端发送流量过大
二、解决方案
方案一:通常的解决方案就是增加消费端实例。说白了就是增加机器。如果出现线上事故,
能申请多少机器就申请多少机器,争取在最短的时间内消费掉积压在MQ中的消息。
方案二:如果申请机器行不通,毕竟公司的机器是有限的,此时可以增加消费端的消费能力。
在MQ的配置中配置"最大消费者数量"与"每次从队列中获取的消息数量"
方案三:如果还是不能解决问题的话,还有另外一种解决方案。紧急上线专门用于记录消息的队列,
不多BB,先把MQ中的消息记录到数据库中,然后再慢慢的消化处理
。
状态判断法
:消费者消费数据后把消费数据记录在 redis
中,下次消费时先到 redis 中查看是否存在该消息,存在则表示消息已经消费过,直接丢弃消息。
业务判断法
:通常数据消费后都需要插入到数据库中,使用数据库的唯一性约束防止重复消费
。每次消费直接尝试插入数据,如果提示唯一性字段重复,则直接丢失消息。一般都是通过这个业务判断的方法就可以简单高效地避免消息的重复处理了。
可靠性
: RabbitMQ使用一些机制来保证可靠性, 如持久化、传输确认及发布确认等。
灵活的路由
: 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能, RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个 交换器绑定在一起, 也可以通过插件机制来实现自己的交换器。
扩展性
: 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展 集群中节点。
高可用性
: 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队 列仍然可用。
多种协议
: RabbitMQ除了原生支持AMQP协议,还支持STOMP, MQTT等多种消息 中间件协议。
多语言客户端
:RabbitMQ 几乎支持所有常用语言,比如 Java、 Python、 Ruby、 PHP、 C#、 JavaScript 等。
管理界面
: RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集 群中的节点等。
插件机制
: RabbitMQ 提供了许多插件 , 以实现从多方面进行扩展,当然也可以编写自 己的插件。
RabbitMQ就是 AMQP 协议的 Erlang 的实现
(当然 RabbitMQ 还支持 STOMP2、 MQTT3 等协议 ) AMQP 的模型架构 和 RabbitMQ 的模型架构是一样的,生产者将消息发送给交换器,交换器和队列绑定 。
RabbitMQ 中的交换器、交换器类型、队列、绑定、路由键等都是遵循的 AMQP 协议中相 应的概念。目前 RabbitMQ 最新版本默认支持的是 AMQP 0-9-1。
Module Layer:协议最高层,主要定义了一些客户端调用的命令,客户端可以用这些命令实现自己的业务逻辑。
Session Layer:中间层,主要负责客户端命令发送给服务器,再将服务端应答返回客户端,提供可靠性同步机制和错误处理。
TransportLayer:最底层,主要传输二进制数据流,提供帧的处理、信道服用、错误检测和数据表示等。
交换器 (Exchange):消息代理服务器中用于把消息路由到队列的组件。
队列 (Queue):用来存储消息的数据结构,位于硬盘或内存中。
绑定 (Binding):一套规则,告知交换器消息应该将消息投递给哪个队列。
生产者
消息生产者,就是投递消息的一方。
消息一般包含两个部分:消息体(payload)和标签(Label)。
消费者
消费消息,也就是接收消息的一方。
消费者连接到RabbitMQ服务器,并订阅到队列上。消费消息时只消费消息体,丢弃标签。
从本质上来说是因为互联网的快速发展,业务不断扩张,促使技术架构需要不断的演进。
从以前的单体架构到现在的微服务架构,成百上千的服务之间相互调用和依赖。从互联网初期一个服务器上有 100 个在线用户已经很了不得,到现在坐拥10亿日活的微信。此时,我们需要有一个「工具」来解耦服务之间的关系、控制资源合理合时的使用以及缓冲流量洪峰等等。因此,消息队列就应运而生了。
它常用来实现:异步处理、服务解耦、流量控制
(削峰)。
Broker可以看做RabbitMQ的服务节点
。一般情况下一个Broker可以看做一个RabbitMQ服务器
。
Queue
: RabbitMQ的内部对象,用于存储消息。多个消费者可以订阅同一队列,这时队列中的消息会被平摊(轮询)给多个消费者进行处理。
Exchange
:生产者将消息发送到交换器,由交换器将消息路由到一个或者多个队列中。当路由不到时,或返回给生产者或直接丢弃。
优点上面已经说了,就是在特殊场景下有其对应的好处,解耦、异步、削峰
。缺点有以下几个:
系统可用性降低 系统引入的外部依赖越多,越容易挂掉。万一 MQ 挂了,MQ 一挂,整套系统崩 溃,你不就完了?
系统复杂度提高 硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?
怎么保证消息传递的顺序性?问题一大堆。
一致性问题 A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致 了。
消息到MQ的过程中搞丢,MQ自己搞丢,MQ到消费过程中搞丢。
生产者到RabbitMQ:事务机制和Confirm机制
,注意:事务机制和 Confirm 机制是互斥的,两者不能共存,会导致 RabbitMQ 报错。
RabbitMQ自身:持久化、集群、普通模式、镜像模式。
RabbitMQ到消费者:basicAck机制、死信队列、消息补偿机制。
生产者将消息发送给交换器的时候,会指定一个RoutingKey,用来指定这个消息的路由规则,
这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
通过绑定将交换器和队列关联起来,一般会指定一个BindingKey,这样RabbitMq就知道如何正确路由消息到队列了。
1.Producer先连接到Broker,建立连接Connection,开启一个信道(Channel)。
2.Producer声明一个交换器并设置好相关属性。
3.Producer声明一个队列并设置好相关属性。
4.Producer通过路由键将交换器和队列绑定起来。
5.Producer发送消息到Broker,其中包含路由键、交换器等信息。
6.相应的交换器根据接收到的路由键查找匹配的队列。
7.如果找到,将消息存入对应的队列,如果没有找到,会根据生产者的配置丢弃或者退回给生产者。
8.关闭信道。
9.管理连接。
1.Producer先连接到Broker,建立连接Connection,开启一个信道(Channel)。
2.向Broker请求消费响应的队列中消息,可能会设置响应的回调函数。
3.等待Broker回应并投递相应队列中的消息,接收消息。
4.消费者确认收到的消息,ack。
5.RabbitMq从队列中删除已经确定的消息。
6.关闭信道。
mandatory :true 返回消息给生产者。
mandatory: false 直接丢弃。
消息被拒
(Basic.Reject /Basic.Nack) 且 requeue = false。消息TTL过期
。队列满了,无法再添加
。优先级高的队列会先被消费。
可以通过x-max-priority
参数来实现。
当消费速度大于生产速度且Broker没有堆积的情况下,优先级显得没有意义。
RabbitMQ 客户端中与事务机制相关的方法有三个:
channel.txSelect
用于将当前的信道设置成事务模式。
channel.txCommit
用于提交事务 。
channel.txRollback
用于事务回滚,如果在事务提交执行之前由于 RabbitMQ 异常崩溃或者其他原因抛出异常,通过txRollback来回滚。
生产者把信道设置为confirm确认模式
,设置后,所有在该信道发布的消息都会被指定一个唯一的ID
,一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者
(包含消息的唯一ID),这样生产者就知道消息到达对应的目的地了。
推
拉
channel.basicNack channel.basicReject
At most once:最多一次。消息可能会丢失,但不会重复传输。
At least once:最少一次。消息绝不会丢失,但可能会重复传输。
Exactly once: 恰好一次,每条消息肯定仅传输一次。
每一个RabbitMQ服务器都能创建虚拟的消息服务器,也叫虚拟主机(virtual host)
,简称vhost
。
默认为“/”。
内存节点:ram,将变更写入内存。
磁盘节点:disc,磁盘写入操作。
RabbitMQ要求最少有一个磁盘节点。
通常由以下两部分组成?
rabbit_amqqueue_process
:负责协议相关的消息处理
,即接收生产者发布的消息
、向消费者交付消息、处理消息的确认(包括生产端的 confirm 和消费端的 ack) 等。
backing_queue
:是消息存储的具体形式和引擎,并向 rabbit amqqueue process提供相关的接口以供调用。
alpha: 消息内容(包括消息体、属性和 headers) 和消息索引都存储在内存中 。
beta: 消息内容保存在磁盘中,消息索引保存在内存中。
gamma: 消息内容保存在磁盘中,消息索引在磁盘和内存中都有 。
delta: 消息内容和索引都在磁盘中 。
接口之间耦合比较严重
面对大流量并发时,容易被冲垮
存在性能问题
1.Client发送消息给MQ
2.MQ将消息持久化后,发送Ack消息给Client,此处有可能因为网络问题导致Ack消息无法发送到Client,
那么Client在等待超时后,会重传消息;
3.Client收到Ack消息后,认为消息已经投递成功。
1.MQ将消息push给Client(或Client来pull消息)
2.Client得到消息并做完业务逻辑
3.Client发送Ack消息给MQ,通知MQ删除该消息,此处有可能因为网络问题导致Ack失败,
那么Client会重复消息,这里就引出消费幂等的问题;
4.MQ将已消费的消息删除
RabbitMQ 有三种模式:单机模式,普通集群模式,镜像集群模式。
单机模式
:就是demo级别的
,一般就是你本地启动了玩玩儿的,没人生产用单机模式
普通集群模式
:意思就是在多台机器上启动多个RabbitMQ实例,每个机器启动一个
。
镜像集群模式
:这种模式,才是所谓的RabbitMQ的高可用模式
,跟普通集群模式不一样的是,
你创建的queue,无论元数据(元数据指RabbitMQ的配置数据
)还是queue里的消息都会存在于多个实例上,
然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步
。