rabbit-mq面试知识点(持续更新)

1.rabbitmq 作用 优缺点

作用:

解耦,异步,削峰

优点:

延迟低,可用性高,并发能力强,开源管理界面,社区活跃

缺点:

吞吐量相对较差,阅读源码门槛高,集群动态拓展麻烦,增加 mq 之后增加系统复杂性,可能降低系统可用性,

2.rabbitmq 中的角色

  • 消息代理 broker
  • 生产者 producer
  • 消费者 consumer
  • 消息队列 queue
  • 交换机 exchange
  • 绑定 binding
  • 路由键 routingKey

3.如何保证消息的可靠性传递(如何处理消息丢失的问题)

  • 生产者丢失数据的情况

生产者在发送数据到 mq 时,可能消息还没到达服务端,因为网络通信等原因消息就丢失了,

transaction 机制

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

confirm 机制

所以一般来说,如果要确保写入 rabbitmq 的消息不丢失,可以开启 confirm 模式,在生产者那里开启 confirm 之后,每次写的消息都会被分配一个唯一 id,如果消息成功写入 rabbitmq 中,rabbitmq 会回调 ack 接口,通知生产者这个消息写入成功。如果 rabbitmq 没能成功处理这条消息,会回调 nack 接口,通知生产者消息处理失败,此时可以进行重试。而且用户可以结合这个机制在内存中维护每个消息 id 的状态,如果超过一定时间没有收到消息的回调通知,可以操作重发

事务机制和 confirm 机制最大的不同在于,事务机制是同步的,提交事务之后会阻塞,但是 confirm 是异步的,发送消息不需要等待返回结果,而是通过 rabbitmq 异步回调通知接口

所以一般为了避免生产者数据丢失,都是用 confirm 机制

  • rabbitmq 丢失数据

rabbitmq 自己弄丢数据,比如内存爆满、服务器宕机等情况,会导致已经存储到 rabbitmq 上的数据丢失,此时必须开启 rabbitmq 的持久化功能,将数据存储到磁盘,开启之后即使 rabbitmq 服务挂了,在重启之后会自动读取之前存储的数据进行恢复,极为罕见的是 rabbitmq 还没有进行持久化的操作,服务就已经挂了,可能会导致少量数据丢失

设置持久化有两个步骤,第一个是创建 queue 的时候将其设置为持久化的,这样可以保证 rabbitmq 持久化 queue 的元数据,但是不会持久化 queue 里存的数据;第二步是发送消息的时候将消息的 deliverMode 设置为 2,也就是将消息设置为持久化的,此时 rabbitmq 就会将消息持久化到磁盘上去。必须要同时设置这两个持久化才行,才能保证 rabbitmq 重启之后,可以恢复 queue 然后恢复 queue 中的数据

哪怕是给 rabbitmq 开启了持久化,也可能在消息写到 rabbitmq 之后,持久化到磁盘之前,服务宕机,就会导致这部分数据丢失

  • 消费者丢失数据

消费者丢失数据一般出现在刚刚接收到消息还没有进行处理的时候,这个时候出现了消费者服务宕机或者异常中断导致消息丢失,rabbitmq 认为消费者已经消费完毕,数据就会丢失了

这时候需要用到 rabbitmq 提供的 ack 机制,关闭 rabbitmq 的自动 ack,在程序处理完成之后进行手动的 ack,没有收到 ack 消息 rabbitmq 是不会丢弃消息的

4.如何保证消息队列的高可用

rabbitmq 有三种模式:单机模式,普通集群模式,镜像集群模式

单机模式:

单台服务器

普通集群模式:

多台服务器上启动多个 rabbitmq 实例,但是创建的 queue 只会放到一个实例上,但是每个实例都会同步 queue 的元数据,消费的时候如果链接到了另外一台 rabbitmq 实例,这个实例会从 queue 所在实例上拉取数据

这种模式需要消费者每次随机链接一个实例之后拉取数据,或者固定链接到 queue 所在实例进行消费,前者有拉取数据的开销,后者导致单实例性能瓶颈化,而且如果 queue 所在的实例宕机,会导致其他实例无法进行 queue 数据的拉取,如果开启了消息持久化,数据不一定会丢失,但是要等实例恢复了才能从此 queue 消费

这个方案主要是为了提高吞吐量,就是说让集群中多个节点来服务某个 queue 的读写操作

镜像集群模式:

这种模式才是所谓的 rabbitmq 的高可用模式,跟普通集群模式不同的是,创建的 queue 无论是元数据还是 queue 中的消息都会存在于多个实例上,然后每次生产者将消息写入到 queue 中时,都会自动把消息同步到多个实例中的 queue 中

这样的好处在于,任何一台实例宕机了都不会对消息的生产和消费造成影响。坏处在于,第一性能开销太大,消息同步所有实例,导致网络带宽的压力和消耗很重,第二没有拓展性,每台实例的机器压力都很容易达到性能瓶颈

如何开启镜像集群模式,在 rabbitmq 控制台新增一个策略,这个策略是镜像集群模式的策略,指定的时候可以要求数据同步到所有节点,也可以要求数据只同步到指定数量的节点或者指定名称的节点,然后再次创建 queue 的时候,应用此策略,就会自动将数据同步到其他节点上

5.如何保证消息不被重复消费(如何保证消息消费时的幂等性)

数据库记录消息保证唯一

消息带唯一标识,通过 redis 避免重复消费

6.如何保证消息的顺序性

  • rabbitmq 为什么要保证消费的顺序性

queue 中的数据只能被一个消费者所消费,多个消费者在消费过程中是无序的,多个消费者同时消费一个 queue,顺序错乱就会导致数据的不一致,这和我们预期的结果不符

  • 如何保证顺序性
  1. 拆分多个 queue,每个 queue 对应一个 consumer

  2. 一个 queue 对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后底层分发给不同的 worker 去处理

7.如何解决消息队列的延时及过期失效问题?消息队列满了应该怎么办?有几百万消息持续积压几个小时,如何解决?

原因:消费端出问题了,消息无法被消费或者消费缓慢,消息队列集群的磁盘可能都快写满了,积压时间过长,rabbitmq 设置了消息过期时间消息可能没被消费就过期了

举例:消费端每次消费之后都要写入 mysql,结果 mysql 挂了,消费端 hang 住无法继续消费消息,或者其他原因导致消费者消费缓慢

分析:

1.大量消息在 mq 积压几个小时

消费者出现问题,即使恢复也需要等待消息被消费,等待时间可能会很长,需要操作临时紧急扩容

  • 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉

  • 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍或者 20 倍的 queue 数量

  • 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接轮询写入临时建立好的 10 倍数量的 queue

  • 接着用临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据

  • 这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常 10 倍的速度来消费数据

  • 等快速消费完积压数据之后,得恢复原先架构部署,重新用原来的 consumer 机器来消费消息

2.假设设置了消息过期时间,也就是 TTL,如果消息在 queue 中积压超过一定时间就会被 rabbitmq 给清理掉,这个数据就会丢失

  • 写临时程序根据数据逻辑重新生成丢失的消息,重新写入 mq 中

8.如何设计一个消息队列

1.支持可伸缩性,支持快速扩容,便于增加吞吐量和容量

分布式系统,broker->topic->partition,每个 partition 加一台机器,就存一部分数据,如果当前资源不够了,就给 topic 增加 partition,做数据迁移,增加机器

2.持久化,保证数据不丢失

顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序写的性能是很高的

3.可用性

多副本->leader&follower->broker 挂了重新选举 leader 提供对外服务

9.rabbitmq 通信全过程

  • 生产者生产消息后,将消息发布给交换机 exchange

  • 交换机根据路由规则将消息路由到队列 queue

  • broker 再将 queue 中的消息投递给订阅该队列的消费者,或者是消费者从队列中获取消息

  • 给 exchange 绑定一个备份交换机,当到达原 exchange 的消息不能被正确路由到任何队列时,消息被发送给备份交换机

  • 备份交换机将消息根据路由规则路由到备份队列,存储

  • 由于可以给消息设置过期时间,所以当原 queue 中的消息达到过期时间仍未被消费时,会被发送到死信交换机;或者发送给消费者的消息被拒绝后,也会被发送到死信交换机

  • 死信交换机根据路由规则将消息路由到死信队列,存储

10.rabbitmq 实现订单超时自动关闭

  • 死信队列:下单投放消息到 A 交换机,设置过期时间,消息到 AA 队列,为 AA 队列绑定死信交换机,不设置 AA 队列的消费者,让消息不会被消费,消息过期后会投递到死信交换机,死信队列,由死信消费者消费,判断订单是否已经完成支付,已支付则无需处理,未支付执行关闭订单、返还库存等逻辑

  • 延时队列:下单发送延时消息到延时队列交换机,消息会保持在 broker 中,并不是立即投递给消费者,只有在到达指定延时时间之后才会投递给消费者,在延时队列消费者中判断订单是否已经完成支付,已支付则无需处理,未支付执行关闭订单、返还库存等逻辑

  • 两种方案对比:延时队列需要插件支持,需要实例化更多的 Bean

  • 其他方案:定时任务(不准),线程休眠(重启影响),监听 redis 键值过期时间,监听 zookeeper 节点过期时间

11.rabbitmq 工作模式

简单模式

一个生产者,一个消费者

工作队列模式

一个生产者,多个消费者,每个消费者获取到的消息唯一,默认轮询获取

12.Exchange 模式

  • 发布订阅模式(fanout):一个生产者发送的消息会被多个消费者获取,发送到 fanout exchange 的消息都会被转发到与 exchange 绑定的所有 queue 上,这种模式不需要 routingkey,需要提前将 exchange 与 queue 进行绑定,一个 exchange 可以绑定多个 queue,一个 queue 可以和多个 exchange 绑定,如果接收到消息的 exchange 没有与任何 queue 绑定,则消息会丢失

  • 路由模式(direct):任何发送到 direct exchange 的消息都会被转发到 routingKey 指定的 queue,这种模式不需要 exchange 进行任何绑定,消息传递时需要一个 routingkey,可以简单理解为要发送到的队列名称,如果 vhost 中不存在该队列名,消息会丢失

  • 匹配订阅模式(topic):任何发送到 topic exchange 的消息都会被转发到所有关心 routingkey 指定 Topic 的 queue 中,就是每个队列都有其关心的 Topic,所有的 Topic 都带有一个 routingkey,exchange 会将消息转发到所有关注 Topic 能与 routingkey 模糊匹配的队列。这种模式需要 routingkey 并且提前绑定 exchange 与 queue,在进行绑定时要提供一个该队列对应的 Topic,’#’表示 0 或者若干个关键字,’*‘表示一个关键字,如果 exchange 没有发现能够与 routingkey 匹配的 queue,消息会丢失

  • headers 模式:header exchange 主要通过发送的 request message 中的 header 进行匹配,其中匹配规则(x-match)又分为 all 和 any,all 表示必须所有的键值对匹配,any 表示只要有一个键值对匹配即可,headers exchange 的默认匹配规则(x-match)是 any