更新时间:2021-09-15 10:44:56 来源:极悦 浏览1825次
大家对RabbitMQ原理都应该有所了解,下面我们就来看一下关于RabbitMQ原理的面试题。
其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么?
先说一下消息队列常见的使用场景吧,其实场景有很多,但是比较核心的有 3 个:解耦、异步、削峰。
解耦:多系统多进程的数据交换,用pub/sub
异步:把大数据量的同步处理改为异步
削峰:一般的A 系统使用 MySQL,扛到每秒 2k 个请求就差不多了,如果每秒请求到 5k 的话,可能就直接把 MySQL 给打死了,导致系统崩溃,用户也就没法再使用系统了。如果使用 MQ, 每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最 大请求数量就 ok,这样下来,哪怕是高峰期的时候,A 系统也绝对不会挂掉,这又设计请求排队的问题。
优点:解耦、异步、削峰
缺点:
系统可用性降低
系统引入的外部依赖越多,越容易挂掉。
系统复杂度提高
一致性问题
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,这数据就不一致了。
RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式
单机模式不存在高可用。
普通集群模式也不存在高可用性,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。但是你创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上 拉取数据过来。这种方式确实很麻烦,也不怎么好,没做到所谓的分布式,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实 例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈。而且如果那个放 queue 的实例宕机了,会导致接下来其他实例就无法从那个实例拉取,如果你开启了消息持久化,让 RabbitMQ 落地存储消息的话,消息不一定会丢,得等这个实例恢复了,然后才可以继续从这个 queue 拉取数据。
镜像集群模式的策略是高可用策略,指定的时候可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的 节点上去了。
其实本质针对的场景,都是说,可能你的消费端出了问题,不消费了;或者消费的速度极其慢,造成消息堆积了,MQ存储快要爆了,甚至开始过期失效删除数据了。
针对这个问题可以有事前、事中、事后三种处理
事前:开发预警程序,监控最大的可堆积消息数,超过就发预警消息(比如短信),不要等出生产事故了再处理。
事中:看看消费端是不是故障停止了,紧急重启。
事后:需要对消费端紧急扩容 ,增加处理消费者进程,如扩充10倍处理,但其实这也有个问题,即数据库的吞吐是有限制的,如果是消费到数据库也是没办法巨量扩容的,所以还是要在吞吐能力支持下老老实实的泄洪消 费。所以事前预防还是最重要的。否则出发删除过期数据,那就需要再重写生产消息的程序,重新产生消息。
需要考虑3个可能丢数据的地方:生产端、队列本身、消费端
(1)生产端:开启事务(不推荐,太耗性能降低吞吐),推荐开启 confirm 模式,在生产者那里设置开启 confirm 模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了RabbitMQ 中,RabbitMQ 会给你回传一个 ack 消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 nack 接口,告诉你这个消息接收失败,你可以重试。而 且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
(2)队列本身:就是 RabbitMQ 自己弄丢了数据,这个你必须开启 RabbitMQ 的持久化,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。
设置持久化有两个步骤:
创建 queue 的时候将其设置为持久化,这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。
第二个是发送消息的时候将消息的 deliveryMode 设置为 2。就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。
(3)消费端:其实和kafka的原理很类似,kafka即手动提交offsize。用RabbitMQ 提供的 ack 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 ack,通过自己的一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 ack。这样的话,如果你还没处理完,不就没有 ack 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别 的 consumer 去处理,消息是不会丢的。
这个需要灵活作答,考察的是思考力,因为消费的场景有很多,有数据库、有缓存、有第三方接口
(1)比如针对数据库,你拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键(或者UUID),那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
(2)再比如redis缓存,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。
(3)再比如第三方接口,需要确定两点,第三方接口程序是有去重能力的,那么脏一点直接丢数据过去,如果没有去重能力,还是需要我们来写程序去重,就是第2点的办法。
节点的存储类型分为两种:
磁盘节点
内存节点
磁盘节点就是配置信息和元信息存储在磁盘上,内存节点把这些信息存储在内存中,当然内次节点的性能是大大超越磁盘节点的。
单节点系统必须是磁盘节点,否则每次你重启RabbitMQ之后所有的系统配置信息都会丢失。
RabbitMQ要求集群中至少有一个磁盘节点,当节点加入和离开集群时,必须通知磁盘节点。
以上就是关于“RabbitMQ原理面试题”的介绍,你都答对了吗?如果还想了解更多相关知识,可以关注一下极悦RabbitMQ教程。教程内容由浅到深,细致全面,当然也有配套的视频教程供可以免费下载学习,希望对大家能够有所帮助。
0基础 0学费 15天面授
Java就业班有基础 直达就业
业余时间 高薪转行
Java在职加薪班工作1~3年,加薪神器
工作3~5年,晋升架构
提交申请后,顾问老师会电话与您沟通安排学习