MQ常见⾯试题
⾯试官:你好
候选⼈:你好
⼤家寒暄⼀下。。。
(⾯试官在你的简历上⾯看到了,呦,有个亮点,就是你在项⽬⾥⽤过MQ,⽐如说你⽤过ActiveMQ)
⾯试官:你在系统⾥⽤过消息队列吗?(⾯试官在随和的语⽓中展开了⾯试)
候选⼈:⽤过的
⾯试官:那你说⼀下你们在项⽬⾥是怎么⽤消息队列的?c程序设计是c++吗
候选⼈:我们项⽬上有个开标⼤厅的系统,⼤厅系统每次获悉到某个标段要开标的时候,就会发送⼀条信息到RabbitMQ中,后台有个评标系统负责获取消息然后做更新及导⼊动作。
⾯试官:那你们为什么使⽤消息队列啊?
候选⼈:额。。。(楞了⼀下,为什么?我没怎么仔细想过啊,⽼⼤让⽤就⽤了),就我理解项⽬上引⽤了MQ是为了解耦,异步和削峰填⾕。
解耦是啥呢,⽐如说A系统调⽤BCD系统,如果此时插⼊了⼀个E系统,意味着我们得修改A系统调⽤,如果说此时B不⽤了,还得改A系统断开B的调⽤,⽽且得考虑调⽤失败情况下的重新设计⼀套重新调⽤的机制。所以采取mq的家,我们A系统只需要把消息集中打到mq
上,BCD来调⽤即可。
异步:还是以刚才为例,假如BCD调⽤均是300ms,那么⼀个⽅法调⽤完毕需要900ms,对⽤户⽽⾔是很不可接受的。我们此时可以采取三个mq分别代表BCD,让BCD来消费
削峰:由于开标会密集的集中于上午9点到10点之间,所以在这个时间客户的⼤规模的操作,请求可能达到5000条/s,由于会⾛
mysql,mysql处理2000以上会压⼒很⼤,所以我们使⽤mq,以系统最⼤的接受值来处理,到⾮⾼峰期的期间还是可以以最⼤来消化库存的记录。
⾯试官:那你说说⽤消息队列都有什么优点和缺点?
(⾯试官此时⼼⾥想的是,你的MQ在项⽬⾥为啥要⽤?你没考虑过,那我稍微简单点⼉,我问问你消息队列你之前有没有考虑过如果⽤的话,优点和缺点分别是啥?)
候选⼈:这个。。。(确实平时没怎么考虑过这个问题啊。。。僵硬啊)
优点就是之前说的解耦,异步和削峰填⾕。
缺点就是增加了架构的复杂性,降低了可⽤性。设想⼀下原本没有mq的情况,我们只需要关注ABCD各个系统是否正常,现在多加了⼀个MQ,由于彼此都是采取MQ进⾏消息的消费,所以⼀旦MQ宕机了,整个系统就炸了。还有就是系统的复杂度,硬⽣⽣加了⼀个MQ,带来了如下的⼀些问题,⽐如如何保证消息的重复消费,怎么处理消息丢失,还有就是消息传递的顺序性等等
另外⼀个就是带来了⼀致性问题:A系统处理完了返回success,⽤户就认为这个请求已经成功了,但是BCD三个系统BC写库成功D写库失败,数据就不⼀致了。所以MQ是个很复杂的架构,引⼊它固然有好处,但是也得针对它带来的坏处做额外的技术⽅案和架构来规避。
⾯试官:kafka、activemq、rabbitmq、rocketmq都有什么区别?
(⾯试官问你这个问题,就是说,绕过⽐较虚的话题,直接看看你对各种MQ中间件是否了解,是否做过功课,是否做过调研)
候选⼈:我们就⽤过activemq,所以别的没⽤过。。。区别,也不太清楚
特性 ActiveMQ RabbitMQ RocketMQ Kafka
单机吞吐 万级 万级 ⼗万级 ⼗万级
topic数⽬对吞吐影响 ⼏百⼏千个topic,吞吐⼩幅下降 ⼏⼗到⼏百个,吞吐⼤量下降
可⽤性 主从 主从 分布式 分布式
eclipse的jdk
伸缩型钢z消息可靠 较低概率丢失消息 经过参数配置可以做到0丢失 经过参数优化配置可以做到0丢失
优劣势 功能成熟,较低概率丢失消息,官⽅社区维护越来越少,主要解耦和异步,在⼤吞吐场景下较⼩应⽤ erlang语⾔开发,性能极好,开源社区活跃,吞吐量低,⽽且由于是erlang语⾔,所以国内做源码级研究和定制困难。 接⼝简单易⽤,阿⾥品牌背书,⽽且社区维护也较活跃。⽀持⼤规模topic数量,⽀持复杂MQ场景,由于是java系的,所以可以对它进⾏源码级的定制和研究 核⼼功能少,但是提供了超级⾼的吞吐量,极⾼的可⽤性和可靠性,分布式可以任意扩展。唯⼀的劣势是可能出现消息重复消费,但是对于⼤数据领域及⽇志采集,这点⼏乎可以忽略不计
⾯试官:那你们是如何保证消息队列的⾼可⽤啊?
候选⼈:(这个。。。我平时就是简单⾛api调⽤⼀下,不太清楚消息队列怎么部署的。。。)
1)RabbitMQ的⾼可⽤
RabbitMQ是⽐较典型的,基于主从的架构
RabbitMQ有三种模式:单机模式,普通集模式,镜像集模式
单机模式:demo级别,⽣产基本没⼈⽤
普通集模式:多台机器启动多个RabbitMQ实例,每个机器启动⼀个。创建的Queue,只会放到⼀个RabbitMQ实例,每个实例都同步了Queue的元数据。完了消费的时候,实际上如果连接到另外⼀个实例,那么这个实例就会从queue所在的实例拉取数据过来。⽐较⿇烦,不是分布式,就是集。
每次⽤户要么随机连接⼀个实例然后从queue所在的MQ拉取Queue数据,要么就固定连接那个存放Queue实例的的MQ服务器。前者导致数据拉取的开销,后者导致单机性能瓶颈。
如果存放Queue的MQ宕机了,那么其他实例就⽆法从那个实例拉取。如果开启了消息持久化,让RabbitMQ落地存储消息的话,消息不⼀定会丢,得等实例恢复了,才能继续从这个Queue所在的实例拉取消息。其实这种模式没有什么⾼可⽤⽽⾔,纯粹是为了提⾼吞吐量。
镜像集模式:这种模式就是RabbitmQ的⾼可⽤模式,创建的queue⽆论是元数据还是queue中的消息的都会存在多个实例,每次写消息到queue的时候,都会⾃动把消息到多个实例的queue⾥进⾏消息同步。
好处就是任何⼀机器宕机,其他机器依然可⽤,坏处就是性能开销⼤,消息同步所有机器,⽹络带宽压⼒和消耗很⼤这种模式,没有任何扩展性可⾔,如果⼀个queue负载很重,加机器,新增的机器也包含了这个queue的所有数据,没办法线性扩展。
怎么开启这个镜像模式呢?rabbitMQ有很好的管理控制台,就是在后台新增⼀个策略,这个策略就是镜像集模式的策略,指定的时候可以要求数据同步所有节点,也可以要求同步到指定数量的节点,再次创建queue的时候,应⽤这个策略就会⾃动把数据同步到其他节点上去了。
2)Kafka的⾼可⽤
另外⼀种典型,纯分布式的MQ架构
kafka的基本架构认识:多个broker组成,每个broker是⼀个节点;每次创建⼀个topic,这个topic可以划分成多个partition,每个partition可以存在于不同的broker上,每个partition存储⼀部分数据。
这是天然的分布式消息队列,也就是说⼀个topic的数据,是以多个partition的形式分散于多个机器上的,
每个机器都放⼀部分数据。
kafka0.8以后提供了HA机制,也就是replica副本机制。0.8之前如果任何⼀个broker宕机了,那么这个broker的partition就废了,⽆法读也⽆法写。
0.8之后,每个partition数据都会同步到其他机器,形成⾃⼰的多个replica副本,然后所有的replica会选举⼀个leader出来,那么⽣产和消费都和这个leader打交道,其他replica都是follower。写的时候,leader负责把消息同步给其他的follower,读的时候直接读leader上的数据。为啥要读leader,因为读其他的需要注意数据⼀致性的问题,系统复杂度就⾼了。
如果leader宕机了,那么就选举⼀个新的leader出来,⽤户继续读写这个新的leader就⾏了。
写数据的时候,leader将数据落地到本地磁盘,接着其他的follower主动从leader来pull数据。⼀旦所有的follower同步好数据,就会发ack给leader。leader收到所有follower的ack之后就会返回成功的消息给⽣产者。只有收到所有的ack之后的消息才能被消费者读到。
⾯试官:如何保证消息不被重复消费啊?如何保证消费的时候是幂等的啊?
候选⼈:啥?(mq不就是写⼊和消费就可以了,哪来这么多问题)
⾸先⽐如rabbitmq,rocketMQ,kafka,都有可能会出现消息重复消费的问题。
就以kafka为例,kafka有⼀个offset的概念,就是每个消息,都有⼀个offset,代表序号。消费者消费完之后,每个⼀段时间会把⾃⼰消费过的offset提交下,代表这个已经消费过了。如果重启系统,kill进程之类的,还没来得及提交offset,这边就会出现少量消息再次消费。
重复消费没什么,致命的是重复消息没有保证幂等。
所以我们需要考虑的是保证消费的幂等性。
1)如果是写库,先根据主键查下,如果有,那么别插⼊,采取update
2)如果是redis,反正每次都是set,天然幂等性
3)⽣产者发送每条数据的时候都带上全局唯⼀ID,消费到之后现根据id到redis中查下,之前是否消费过,如果没有消费过就处理,如果消费过,就不处理,这样就实现了避免重复消费。
⾯试官:如何保证消息的可靠性传输啊?要是消息丢失了怎么办啊?
候选⼈:(我们没怎么丢过消息啊。。。)
⼀般来说MQ的丢失是俩种,⼀种是mq⾃⼰弄丢了,⼀种是消费的时候弄丢了。
以rabbbitMQ为例⼦
1)⽣产者弄丢了信息
⽣产者将数据发送到rabbitmq,可能数据因为⽹络的关系,弄丢了数据。
可以使⽤rabbitmq的事务功能,⽣产者在发送数据之前开启rabbitmq事务(select),然后发送消息,如果消息没有没rabbitmq接收到,那么⽣产者就会收到异常报错,此时会回滚事务(llback),然后尝试重新发送消息,如果收到消息,那么就可以提交事务(Commit)。这套的问题在于,事务机制开启,吞吐量会下降,因为这套⽐较耗性能。
⼀般来说我们确保写rabbitmq不丢失,可以开启confirm模式,在⽣产者那边设置开启confirm模式,这样每次写消息的时候都会分配⼀个唯⼀的ID,然后如果写⼊rabbitmq中,rabbitmq会回传⼀个ack,告诉你这个消息已经ok了,如果rabbitmq没能处理这个消息,还会回调⼀个nack的接⼝,通知⽣产者这个消息接受失败,⽣产者开始重试。我们⼀般可以结合这个confirm的机制在⾃⼰的内存中维护每⼀个id的状态,超过⼀定时间没有接收到消息的回调,则重发。
事务机制和confirm机制最⼤的不同就是事务是同步的,提交⼀个事务之后会阻塞,但是confirm是异步的。所以⼀般我们采取confirm机制来实现⽣产者阶段的消息丢失。
2)rabbitmq丢失消息
rabbitmq丢失消息,必须开启rabbitmq的持久化,消息写⼊持久化⼊磁盘,即使挂了,再重启也会⾃动读取之前存储的数据,⼀般不会丢。极其罕见就是还没持久化就挂了,这样会导致数据丢失。
设置持久化有俩个步骤:第⼀是创建queue的时候设置持久化,这样保证了rabbitmq持久化queue的元数据,但是不会持久化queue⾥的数据,第⼆是发送消息的时候把消息的dliveryModel设置为2,就是把消息设置为持久化,这样rabbitmq就会把消息持久化到磁盘上去,必须把俩个都设置成持久化才⾏。这样即使rabbitmq挂了,重启也能从磁盘中恢复queue,恢复queue中的数据
⼀般我们是采取持久化和confirm机制相结合,消息持久化磁盘之后,就会通知ack,哪怕持久化到磁盘之前挂了,数据丢失,⽣产者收不到ack也能重发。
这边要注意⼀下,就算开了持久化,也存在消息丢失,就是还没来得及持久化到磁盘,rabbitmq就挂了。
3)消费端丢失数据,这个就⽐较尴尬了,刚消费到,还没来得及处理,消费端就宕机了。但是此时rabbitmq认为你已经消费过了。
为了避免这种问题,这边要使⽤rabbitmq提供的ack机制,简单的来说就是关闭rabbitmq的⾃动ack,
通过api来调⽤,每次代码保证已经处理完了,再通过程序ack⼀把,这样没有处理完就没有ack,即使宕机了,也会有其他消费者来消费这个。
kafka
1)消费端弄丢了数据
出现这种情况唯⼀的就是,消费端消费了这个消息,然后⾃动提交了offset,让kafka认为你已经消费了这条消息,在刚准备处理这个消息的时候,挂了,那么此时就丢失消息。
解决⽅式:关闭⾃动提交offset,改为⼿动提交,在处理完毕之后再提交offset。这边要注意⼀个幂等的问题,就是处理完毕之后还没来得及提交offset就挂了。
2)kafka弄丢了数据
这个场景之前遇到过,kafka某个broker宕掉了,重新选举partition的leader时候,如果此时上⼀个的follower还有⼀些数据没通不掉,那么某个follower选举成了leader,就少了⼀些数据。
设置四个参数来解决
给这个topic设置replication.factor参数:这个值必须⼤于1.要求每个partition必须⾄少有俩个副本
在kafka服务器中设置plicas参数必须⼤于1,这是要求⼀个leader⾄少感知到⼀个follower跟⾃⼰保持联系未掉队
⽣产者端设置acks=all,要求每条数据,必须写⼊所有replica,才认为写成功
在⽣产者端设置retries=MAX ⼀旦写⼊失败,⽆限重连
3)⽣产者会不会丢失数据
如果按照上设置了acks=all 就不会丢,如果失败会⽆限重连。雄鹿vs爵士常规赛
⾯试官:那如何保证消息的顺序性?
候选⼈:(顺序性?什么意思?我为什么要保证消息的顺序性?)
⾸先顺序性是肯定要保证的,如⼀个信息是增删改,这个顺序如果乱了就会有问题
乱的场景:rabbitmq。⼀个queue,多个consummer,会乱
kafka:⼀个topic,⼀个partition,⼀个consummer,内部多线程,会乱。
所以解决⽅案:
以rabbitmq为例:我们可以拆分多个queue,每个queue⼀个消费者。或者⼀个queue,但是对应⼀个消费者,这个消费者内部⽤内存队列进⾏排队,然后分发给不同的worker去处理。
kafka的话:⼀个topic,⼀个partition,⼀个consummer,内部单线程消费,写n个内存queue,然后n个线程分别消费⼀个内存queue 即可。
⾯试官:如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有⼏百万消息持续积压⼏⼩时,说说怎么解决?fifo硬件
候选⼈:(不是,我这平时没遇到过这些问题啊,就是简单⽤⽤,知道mq的⼀些功能)
问题1:
rabbitmq可以设置过期时间,就是TTL,如果消息在queue中积压超过⼀定时间就会被rabbitmq清理掉,这样数据就没了。我们通常采取的⽅案是批量重导,因为这个消息是直接丢弃了,所以我们采取过⾼峰期,采取临时程序查出那些丢失的数据然后再重新发给mq。
问题2:
临时程序来接⼊数据来消费,消费⼀个丢⼀个,快速消费所有的信息。或者就是都不要了,快速消费掉所有的信息,晚上再批量重导,补数据。
问题3:
紧急扩容
1先修复consummer,确保它恢复消费速度,然后停掉所有的consummer
2新建⼀个topic。partition是原来的⼗倍,临时监理原有10倍或20倍的queue数量
3写⼀个临时分发数据的consummer,消费积压数据,直接均匀地轮询写⼊建⽴好的10倍数量的queue
4临时征⽤10倍的机器来部署consummer,每⼀批consummer消费⼀个临时的queue数据
5 将queue和cosummer扩⼤10倍,以正常速度⼗倍来消费
6 快速消费积压数据,然后恢复原先的架构
⾯试官:如果让你写⼀个消息队列,该如何进⾏架构设计啊?说⼀下你的思路
候选⼈:(问题⼀个⽐⼀个僵硬,不给活路啊。。。)
根据MQ的功能来设计架构
1)MQ得⽀持可伸缩性吧,需要的时候可以快速扩容,增加吞吐量。
这边可以抄下kafka,broker-topic-partition的理念
每个partition放⼀个机器,然后存放⼀部分数据,如果资源不够了,那就给topic增加partition啊,这样可以存放更多的数据,提供更⾼的吞吐量。
2)其次要考虑信息丢失那么就是持久化了
要落地磁盘,这样才能保证重启之后,消息能恢复。落磁盘⽤顺序写,这样性能很⾼【kafka思路】
mysql面试题常问3)考虑mq的可⽤性
抄下kafka的leader-follower的模式,挂了重新选举
4)然后看能不能⽀持0丢失
这也简单啊,参考kafka的零丢失⽅案巴拉巴拉

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。