0%

RabbitMQ-面试宝典

MQ

MQ的应用场景

异步通信,系统解耦,流量削峰

  • 异步通信:MQ提供了异步处理机制。可以减少主流程的响应时间,列如:异步发送短信。
  • 系统解耦:降低系统之间的耦合度。列如:在数据中心中,让各个系统自己去监听需要的消息数据,达到系统解耦的目的。
  • 流量削峰:削弱瞬时的请求高峰,让系统吞吐量在高峰请求下保持可控。

MQ的优缺点

优点:在特殊场景下有对应的好处[异步通信,系统解耦,流量削峰]。

缺点:

  • 系统可用性降低;MQ挂了后,导致系统不可用;处理方案:MQ需要做高可用。
  • 系统复杂度提高;系统添加MQ后,需要保证[消息幂等性,消息丢失问题,消息传递的顺序性]。
  • 一致性问题:A系统处理完了直接返回成功了;但问题是,BCD 三个系统,BD 两个系统写库成功了,结果 C 系统写库失败了,这数据就不一致了。

主流MQ对比

主流的MQ有很多,列如:ActiveMQ,RabbitMQ,Kafka,RocketMQ等。

主流MQ对比

ActiveMQ

最早大家都用 ActiveMQ,比如阿里最开始就用这个,随着业务发展,ActiveMQ IO 模块出现瓶颈,阿里通过一系列优化还是不能很好的解决,就被放弃了。而且现在 ActiveMQ 社区也不活跃,不推荐用的。

RabbitMQ

后来大家开始用 RabbitMQ,社区比较活跃,但是 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,而且它只能集群部署,不支持分布式,无法扩容。在服务端处理同步发送的性能上也比不上 Kafka 和 RocketMQ,所以在这个时代来看,也不是最好的选择。

RocketMQ

现在越来越多的公司会去用 RocketMQ,RocketMQ 是站在 Kafka 的巨人的肩膀上,对其进行了优化让其更满足互联网公司的特点。它是纯 Java 开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。

2011年初,Linkin 开源了 Kafka 这个优秀的消息中间件,淘宝中间件团队在对 Kafka 做过充分 Review 之后,Kafka 无限消息堆积,高效的持久化速度吸引了我们,但是同时发现这个消息系统主要定位于日志传输,对于使用在淘宝交易、订单、充值等场景下还有诸多特性不满足,为此我们重新用Java语言编写了RocketMQ,定位于非日志的可靠消息传输(日志场景也 OK),目前 RocketMQ 在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog 分发等场景。

RabbitMQ

RabbitMQ是什么?

RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息中间件。

AMQP是什么?

AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个进程间传递异步消息的网络协议。
在异步通讯中,消息不会立刻到达接收方,而是被存放到一个容器中,当满足一定的条件之后,消息会被容器发送给接收方,这个容器即消息队列,而完成这个功能需要双方和容器以及其中的各个组件遵守统一的约定和规则,AMQP就是这样的一种协议,消息发送与接受的双方遵守这个协议可以实现异步通讯。这个协议约定了消息的格式和工作方式。

AMQP协议三层是?

  • Module Layer:协议最高层;主要定义了一些客户端调用的命令,客户端可以用这些命令实现自己的业务逻辑。
  • Session Layer:中间层;主要负责客户端命令发送给服务器,再将服务端应答返回客户端,提供可靠性同步机制和错误处理。
  • Transport Layer:最底层;主要传输二进制数据流,提供帧的处理、信道服用、错误检测和数据表示等。

AMQP模型

AMQP模型

  • Broker:表示消息队列服务器实体。
  • Conection:连接,TCP连接。
  • Channel:信道,在一个TCP连接上可以有多个信道,几乎所有操作都是在信道中完成的
  • Message:消息,由消息头+消息体组成。
  • Exchange:交换机,用来接收生产者发送的消息,并将消息路由到某个队列中
  • Queue:队列,存储消息
  • Binding:绑定,表示队列与交换机之间的关系
  • RoutingKey:消息路由关键字,一个消息头,交换机可以用这个消息头决定如何路由某条消息
  • Publisher:消息生产者,是一个向Broker发送消息的客户端应用程序。
  • Consumer:消息消费者,是一个从队列中取得消息的客户端应用程序。
  • Virtual Host:虚拟主机

Binding是什么?

Binding,绑定,指队列与交换机之间的关系。
一般来说,会指定一个BindingKey,这样RabbitMQ就知道如何路由消息到队列中。

RoutingKey是什么?

RoutingKey,消息路由关键字,一个消息头。
生产者发送消息时,会指定一个路由键,最终交换机根据类型和Binding关系来决定将消息路由到哪个队列中。

Virtual Host是什么?

Virtual Host,虚拟主机。
在MQ中可以实现多个完全隔离的环境,默认是”/“。

交换机有几种类型

默认交换机:默认交换机其实是一个没有名字的,空字符的直连交换机(direct exchange)
direct交换机:直连交换机,将消息路由到 BindingKey 和 Routingkey 全匹配的队列中
fanout交换机:扇形交换机,将消息路由到绑定当前交换机所有的队列上,忽略消息 Routingkey。
topic交换机:主题交换机,可以实现 direct 和 fanout 交换机的所有功能,特点是支持在 BindingKey 上使用通配符,更加灵活。

生产者工作流程

1)生产者连接Broker,建立TCP连接,建立信道(Channel)。
2)如果不存在交换机和队列,则声明交换机和队列。
3)生产者通过信道发送消息给Broker,由Exchange将消息路由到相关队列(根据交换机类型 和 BindingKey 和 Routingkey 等进行路由)。
4)关闭信道,关闭TCP连接

消费者工作流程

1)消费者连接Broker,建立TCP连接,建立信道(Channel)。
2)消费者监听指定的Queue(队列)
4)当有消息到达Queue时,Broker默认将消息推送给消费者。
5)消费者接收到消息,进行消费。
6)消费成功后,进行ack消息应答,Broker从队列中删除已经ack应答的消息
7)关闭信道,关闭TCP连接

RabbitMQ的工作模式

  • 简单模式;最简答的收发模式
  • 工作队列模式;一个队列多个消费者,资源竞争,正常情况下一个消息只会被一个消费者消费。
  • 发布订阅模式;对应扇形交换机(fanout)
  • 路由模式;对应直连交换机(direct)
  • 主题模式;对应主题交换机(topic)

多个消费者监听一个队列时,消息如何分发?

  • 轮询分发: 默认的策略,消费者轮流,平均地接收消息
  • 不公平分发:根据消费者的能力来分发消息,给空闲的消费者发送更多消息

RabbitMQ消息获取方式

  • 推,MQ推送消息到消费者,常用(监听方式)
  • 拉,消费者手动拉取消息消费,不常用

RabbitMQ的消息状态

什么是死信队列

DLX,全称为 Dead-Letter-Exchange,死信交换器,死信队列。
当消息在一个队列中变成死信 (dead message) 之后,它重新发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。

成为死信的原因?

  • 消息TTL过期,消息到达超时时间未被消费
  • 队列达到最大长度(队列满了,消息无法再添加到队列中)
  • 消费者拒绝消息(basic.reject 或 basic.nack)并且requeue=false

什么是延迟队列

普通队列:普通队列中的元素总是等着希望被早点取出处理
延时队列:延迟队列中的元素希望在指定时间被取出和处理,所以延时队列中的元素是都是带时间属性的,通常来说是需要被处理的消息或者任务。

如何实现延迟队列

  • TTL+死信队列实现延迟队列:通过队列TTL给消息设置统一过期时间,消息过期后转发到死信队列中。
  • 基于延迟插件实现延迟队列:使用延迟交换机,并且生产者发送消息时设置过期时间

什么是优先级队列

优先级高的消息会先被消费。
注意:优先级队列需要在消息堆积的时候才生效。列如:消息一进来就被消费了,就没有优先级的意义了。

RabbitMQ如何保证高可用(集群)

因为是基于主从(非分布式)做高可用性的;RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式。

单机模式

就是 Demo 级别的,一般就是你本地启动了玩玩儿的?,没人生产用单机模式。

普通集群模式

普通集群模式:意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个 queue 的读写操作。

镜像集群模式

镜像集群模式:这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。这样的话,好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。

RabbitMQ集群的节点类型?

  • 内存节点;ram,将变更写入内存。
  • 磁盘节点;disc,将变更写入磁盘。

磁盘节点就是配置信息和元信息存储在磁盘上,内存节点把这些信息存储在内存中,当然内存节点的性能是大大超越磁盘节点的。
单节点系统必须是磁盘节点,否则每次你重启RabbitMQ之后所有的系统配置信息都会丢失。
RabbitMQ要求集群中至少有一个磁盘节点,当节点加入和离开集群时,必须通知磁盘节点。

如何保证消息不丢失

产生消息丢失的原因有哪些?生产者消息丢失,消费者消息丢失,MQ本身消息丢失。

生产者消息丢失

消息没有成功发送到交换机,解决方案:使用事务或生产者消息确认机制。
消息没有成功路由到队列,解决方案:使用备份交换机或生产者消息回退机制。

消费者消息丢失

消费者关闭自动ack应答,手动ack应答。

MQ本身消息丢失

使用持久化(交换机,队列,消息),保证消息存储到硬盘中。
使用镜像集群模式,一份数据存储多个节点中,实现了高可用。

消息补偿机制(大公司使用的方式)

角色:生产者,消费者,回调检查服务,定时检查服务;
第一步骤:生产者消息入库,发送普通消息和延迟消息,消费者消费普通消息成功后也消息入库。(普通消息和延迟消息相同)
第二步骤:回调检查服务监听到延迟消息后,检查消费者是否消费过这条消息,消费过就忽略,否则,重复第一步骤。
第三步骤:定时检查服务,会定时检查生产者消息库,消费者消息库是否一致,如果不一致,让生产者重新发送缺失的消息。

如何保证消息幂等性

使用全局唯一id和去重表。(当消息被消费时,先判断是否去重表中是否存在,是否被消费过)
当集群环境下,可以使用分布式锁 + 全局唯一id + 去重表。

如何保证消息的顺序性

消息顺序性问题,主要分三个环节:发消息的顺序,队列中消息的顺序,消费消息的顺序。

发消息的顺序

消息发送端的顺序,大部分业务不做要求,谁先发消息无所谓,如果遇到业务一定要发送消息也确保顺序,那意味着,只能全局加锁一个个的操作,一个个的发消息,不能并发发送消息。

队列中消息的顺序

RabbitMQ中,消息最终会保存在队列中,在同一个队列中,消息是顺序的,先进先出原则,这个由Rabbitmq保证,通常也不需要开发关心。

注意:

  • 普通队列,是先进先出的原则。
  • 不同队列中的消息顺序,是没有保证的,例如:进地铁站的时候,排了三个队伍,不同队伍之间的,不能确保谁先进站。

消费消息的顺序

如何保证消息顺序性,通常说的就是消费者消费消息的顺序。在多个消费者消费同一个消息队列的场景,通常是无法保证消息顺序的。

解决方案:通常就是一个队列只有一个消费者,消费者不进行并发消费。
这样就可以一个个消息按顺序处理,缺点就是并发能力下降了,无法并发消费消息,这是个取舍问题。

提示:如果业务又要顺序消费,又要增加并发,通常思路就是开启多个队列,业务根据规则将消息分发到不同的队列,通过增加队列的数量来提高并发度,例如:电商订单场景,只需要保证同一个用户的订单消息的顺序性就行,不同用户之间没有关系,所以只要让同一个用户的订单消息进入同一个队列就行,其他用户的订单消息,可以进入不同的队列。

如何处理消息堆积问题

消息积压处理办法:临时紧急扩容。

  • 先修复Consumer的问题,确保其恢复消费速度,如何将现有的Consumer都停掉
  • 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。
  • 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
  • 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
  • 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 Consumer 机器来消费消息。