RabbitMQ

简介

RabbitMQ

RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(称面向消息的中间件)。RabbitMQ服务器是使用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端。

RabbitMQ是一个消息中间件:它接受并转发消息。你可以把它当做一个快递站点,当你要发送一个包裹时,你把你的包裹放到快递站,快递员最终会把你的快递送到收件人那里,按照这种逻辑RabbitMQ是一个快递站,一个快递员帮你传递快件。RabbitMQ与快递站的主要区别在于,它不处理快件而是接收,存储和转发消息数据

什么是AMQP协议?

复制代码
    AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。

RabbitMQ的架构

名词介绍

复制代码
    **Broker(中间件-**消息队列服务器实体**)**:接收和分发消息的应用,RabbitMQ Server就是Message Broker
    **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 中去。

Exchange是接受生产者消息并将消息路由到消息队列的关键组件

常用的类型有:

direct (point-to-point), topic(publish-subscribe) and fanout (multicast)

**Queue(消息队列载体)每个消息都会被投入到一个或多个队列
Routing Key((Exchange)交换机的路由规则) :生产者将消息发送到交换机时会携带一个key,来指定路由规则
** binding Key(绑定
:在绑定Exchange和Queue时,会指定一个BindingKey,生产者发送消息携带的RoutingKey会和bindingKey对比,若一致就将消息分发至这个队列
vHost 虚拟主机:每一个RabbitMQ服务器可以开设多个虚拟主机每一个vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的 "交换机exchange、绑定Binding、队列Queue",更重要的是每一个vhost拥有独立的权限机制,这样就能安全地使用一个RabbitMQ服务器来服务多个应用程序,其中每个vhost服务一个应用程序。

交换机类型

1.direct Exchange(直接交换机)

匹配路由键,只有完全匹配消息才会被转发

2.Fanout Excange(扇形交换机)

将消息发送至所有的队列

3.Topic Exchange(主题交换机)

将路由按模式匹配,此时队列需要绑定要一个模式上。符号"#"匹配一个或多个词,"* "符号"匹配不多不少一个词。因此"abc.#"能够匹配到"abc.def.ghi",但是"abc." 只会匹配到"abc.def"。

4.Header Exchange

在绑定Exchange和Queue的时候指定一组键值对,header为键,根据请求消息中携带的header进行路由

工作模式

1.simple (简单模式)
复制代码
一个消费者消费一个生产者生产的信息
2.Work queues(工作模式)

一个生产者生产信息,多个消费者进行消费,但是一条消息只能消费一次

3.Publish/Subscribe(发布订阅模式)

生产者首先投递消息到交换机,订阅了这个交换机的队列就会收到生产者投递的消息

4.Routing(路由模式)

生产者生产消息投递到direct交换机中,扇出交换机会根据消息携带的routing Key匹配相应的队列

5.Topics(主题模式)

生产者生产消息投递到topic交换机中,上面是完全匹配路由键,而主题模式是模糊匹配,只要有合适规则的路由就会投递给消费者

保证消息的稳定性

1.消息持久化

RabbitMQ的消息默认存在内存中的,一旦服务器意外挂掉,消息就会丢失

消息持久化需做到三点

  1. Exchange设置持久化
  2. Queue设置持久化
  3. Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息
2.ACK确认机制

多个消费者同时收取消息,收取消息到一半,突然某个消费者挂掉,要保证此条消息不丢失,就需要acknowledgement机制,就是消费者消费完要通知服务端,服务端才将数据删除

这样就解决了,及时一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case。

3.设置集群镜像模式

RabbitMQ三种部署模式:

  1. 单节点模式:最简单的情况,非集群模式,节点挂了,消息就不能用了。业务可能瘫痪,只能等待。
  2. 普通模式:默认的集群模式,某个节点挂了,该节点上的消息不能用,有影响的业务瘫痪,只能等待节点恢复重启可用(必须持久化消息情况下)。
  3. 镜像模式:把需要的队列做成镜像队列,存在于多个节点,属于RabbitMQ的HA方案

为什么设置镜像模式集群,因为队列的内容仅仅存在某一个节点上面,不会存在所有节点上面,所有节点仅仅存放消息结构和元数据。下面自己画了一张图介绍普通集群丢失消息情况

4.消息补偿机制

持久化的消息,保存到硬盘过程中,当前队列节点挂了,存储节点硬盘又坏了,消息丢了,怎么办?

产线网络环境太复杂,所以不知数太多,消息补偿机制需要建立在消息要写入DB日志,发送日志,接受日志,两者的状态必须记录。

然后根据DB日志记录check 消息发送消费是否成功,不成功,进行消息补偿措施,重新发送消息处理。

如何实现延迟队列

RabbitMQ本身没有延迟队列,需要靠TTL和DLX模拟出延迟的效果

TTL(Time To Live)

RabbitMQ可以针对Queue和Message设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter

RabbitMQ针对队列中的消息过期时间有两种方法可以设置。

A: 通过队列属性设置,队列中所有消息都有相同的过期时间。

B: 对消息进行单独设置,每条消息TTL可以不同。

如果同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead letter

DLX (Dead-Letter-Exchange)

RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由。
x-dead-letter-exchange :出现dead letter之后将dead letter重新发送到指定exchange
x-dead-letter-routing-key :指定routing-key发送

队列出现dead letter的情况有:

  1. 消息或者队列的TTL过期
  2. 队列达到最大长度
  3. 消息被消费端拒绝(basic.reject or basic.nack)并且requeue=false

利用DLX,当消息在一个队列中变成死信后,它能被重新publish到另一个Exchange。这时候消息就可以重新被消费。

消息队列特点-优缺点

优点:

解耦、异步、削峰

缺点:

系统可用性降低

系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,人 ABCD 四个系统好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整,MQ 一挂,整套系统崩溃的,你不就完了?如何保证消息队列的高可用,可以点击这里查看。

系统复杂度提高

硬生生加个 MQ 进来,你怎么[保证消息没有重复消费]?怎么[处理消息丢失的情况]?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。

一致性问题

A系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。

高可用集群架构

非常经典的 mirror镜像模式,保证 100%数据不丢失。在实际工作中也是用得最多的,并且实现非常的简单,一般互联网大厂都会构建这种镜像集群模式。

mirror镜像队列,目的是为了保证 rabbitMQ数据的高可靠性解决方案,主要就是实现数据的同步,一般来讲是2 - 3个节点实现数据同步。对于100%数据可靠性解决方案,一般是采用 3个节点。

问答:

如何避免消息堆积

RabbitMQ丢消息怎么办?

按照"生产者 -> 交换器 -> 队列 -> 消费者"的模式,一旦发生丢消息的情况,无非有三种情况:生产者丢消息、消息队列丢消息、消费者丢消息。下面我们逐个进行分析:

1、对于生产者丢消息,RabbitMQ提供的transaction和confirm机制可以保证生产者不丢消息,transaction机制类似数据库的事务,只有当消息发送成功,事物才会被提交,否则事务被被回滚。因为每次发消息都必须开启事物,所以transaction机制会导致RabbitMQ吞吐量降低,一般建议使用confirm机制,即消息被正确投递则发送ACK给生产者,否则发送NACK给生产者。

2、对于消息队列丢消息,解决方案我们在前面有提到过,主要有两点,第一,声明队列的时候设置durable为true,这表示这是一个支持持久化的队列。第二,发送消息的时候,设置DeliveryMode为2,这表示消息支持持久化的磁盘,如果有一天RabbitMQ遭遇不幸,消息会被持久化到磁盘上,所以说,习惯性保存是个好习惯啊......

3、对于消费者丢消息,解决方案是手动ACK,因为只有队列收到ACK时,它才会从队列中删除这条消息,否则,这条消息会重新回到队列中,只要它能重新回到队列、重新处理,它怎么会丢呢


防止消息丢失:

当队列中的消息一直失败,如何删除队列中的这条消息

页面操作:点击这个队列名称->点击消息->删除消息

消息队列配置项已经修改exchange和队列的绑定规则,但是依然会匹配之前的规则

页面查看队列的绑定规则,存在持久化问题

将交换器、队列和消息都设置持久化之后就能保证数据不会被丢失吗?当然不是,多个方面

  • 消费者端: 消费者订阅队列将autoAck设置为true,虽然消费者接收到了消息,但是没有来得及处理就宕机了,那数据也会丢失,解决方案就是以为手动确认接收消息,待处理完消息之后,手动删除消息
  • 在rabbitmq服务端,如果消息正确被发送,但是rabbitmq未来得及持久化,没有将数据写入磁盘,服务异常而导致数据丢失,解决方案,可以通过rabbitmq集群的方式实现消息中间件的高可用性

RaabitMQ的死信机制

从实用性的角度来讲,它可以帮助我们实现"延时队列",虽然在更多的场景下,我们希望消息能被立即处理,因为这样看起来更像一个"实时"的行为。可在实际应用过程中,我们难免会遇到这样一种情况,一条消息经过手动ACK以后从队列中移除,结果消费者端问你能不能再消费一次这条消息,所以,Kafka里就提供了两种策略,即最多一次和至少一次,最多一次保证的是消息不会被重复消费,而至少一次保证的是消息100%被成功消费。所以,简单来说,在为RabbitMQ配置了死信的情况下,可以让部分消息有机会重新进入队列、重新被消费。那么,什么情况下会产生死信呢?主要有下面三种情况:

  1. 消息被否定确认,使用channel.basicNack或channel.basicReject,并且此时requeue属性被设置为false。
  2. 消息在队列的存活时间超过设置的TTL时间。
  3. 消息队列的消息数量已经超过最大队列长度。

接下来,为了配合死信机制,我们必须要声明死信队列,建议为每一个需要配置死信的事件单独定义一个死信队列,

RabbitMQ重试与超时

利用消息/队列的TTL实现超时,利用死信实现重试,消息TTL和队列TTL的不同在于,一个队列超时则队列内的消息会被全部清空,而一个消息超时则可以在清空前决定是否要清空。

重试与超时最大的问题其实在于幂等性,因为在以往的实践中,当我们的消费者变成一个第三方的API接口的时候,我们很难知道,一个消息到底需要处理多久,我一直不明白,为什么宝洁这样的公司,它一个API接口居然能等将近30分钟,而更加令人难以忍受的,是大量只能调用一次的接口,这类接口既无法保证能100%调用成功,同样无法保证,第二次调和第一次调效果完全一样,所以,关于重试与超时这部分,其实应该结合实际业务去设计,因为每个人的诉求可能都不一样。

参考

RabbitMQ参考地址
RabbitMQ基础了解参考-写的很好很全面(重点看
https://blog.csdn.net/qinyuanpei/article/details/108224978-示例

相关推荐
惊讶的猫1 小时前
AMQP 与 RabbitMQ 四大模型
分布式·rabbitmq
像少年啦飞驰点、3 小时前
从零开始学 RabbitMQ:小白也能懂的消息队列实战指南
java·spring boot·微服务·消息队列·rabbitmq·异步编程
lekami_兰3 小时前
RabbitMQ 延迟队列实现指南:两种方案手把手教你搞定
后端·rabbitmq·延迟队列
为什么不问问神奇的海螺呢丶18 小时前
n9e categraf rabbitmq监控配置
分布式·rabbitmq·ruby
m0_687399841 天前
telnet localhost 15672 RabbitMQ “Connection refused“ 错误表示目标主机拒绝了连接请求。
分布式·rabbitmq
Ronin3051 天前
日志打印和实用 Helper 工具
数据库·sqlite·rabbitmq·文件操作·uuid生成
坊钰3 天前
【Rabbit MQ】Rabbit MQ 的结构详解,传输机制!!!
java·rabbitmq
请叫我头头哥4 天前
SpringBoot进阶教程(八十九)rabbitmq长链接及域名TTL,多机房切换配置重连能力
rabbitmq·springboot
三水不滴4 天前
对比一下RabbitMQ和RocketMQ
经验分享·笔记·分布式·rabbitmq·rocketmq
JP-Destiny4 天前
后端-RabbitMQ
后端·消息队列·rabbitmq·java-rabbitmq