消息队列面试题

更多面试题:https://www.yuque.com/greedy-9i38g/tzpwui?# 《面试题》

目录

1、为啥使用消息队列哇?

2、消息队列有哪些优缺点呐?

3、Kafka、Active/Rabbit/Rocket MQ都有哪些区别哇?

4、如何确保消息队列的高可用哇?

5、咋保证消息被消费时的幂等性?

6、如何确保消息的可靠性传输?

7、如何保证消息的顺序性?

8、咋解决消息队列的延时以及过期失效问题?有几百万消息积压咋办?

9、如果让你写个消息队列,你咋设计?


1、为啥使用消息队列哇?

消息队列使用场景有很多,但是其核心是三点:削峰、异步、解耦

解耦:想象一下,A系统发送数据到B、C、D系统,万一突然来业务E系统也需要数据,但是C系统又不需要数据了。B系统还因为负载挂掉了...

总不能因为每个系统自己的问题,频繁的改动太多的A系统吧?记住没有什么是加一层解决不了的,如果不行,那就再加一层

异步:想象一下,A系统接收一个请求,需要在自己本地写库,还需要在BCD三个系统写库,自己本地写库要3ms,BCD三个系统分别写库要300ms、450ms、200ms。最终请求总延时是3 + 300 + 450 + 200 = 953ms,接近1s,用户感觉搞个什么东西,慢死了慢死了。

削峰:每天0点~11点,A系统风平浪静,每秒请求数量100条,但是到了11点之后,请求量暴增到1万条,但是系统最大的处理能力就只能是每秒钟处理1000个请求啊...系统会死的...

我这里只讲了什么场景适合用消息队列,至于削峰、异步、解耦具体怎么用,大家自行百度吧,百度一大堆呐😁

2、消息队列有哪些优缺点呐?

优点上面说了:削峰、异步、解耦

缺点也不少

  • 系统可用性降低:系统外部依赖越多越不稳定,因为要考虑各个方面的极限性(会不会突然挂掉哇、突然不好使了啥啥的)。本来就是点对点的A系统调用BCD三接口,好好的突然加进来一个MQ,MQ挂掉啦咋办?BCD系统直接傻了

  • 系统复杂性提高:加个MQ进来,消息重复性消费咋办?用户花一份钱,系统给人家发两份货,用户愿意,老板不愿意。消息丢失咋办?用户花钱了,没收着货,那还不如给人家多发货了。消息传递的顺序性?都是问题
  • 一致性问题:A系统处理完了,直接给用户返回成功,消息发送至BCD系统,BC系统写库成功。但是D写库失败了,咋整?数据不一致了....

还有个,是我自己认为的,代码里加入MQ后之后,再找代码之间调用的时候,明显没有接口之间调用找的快了 ̄へ ̄

3、Kafka、Active/Rabbit/Rocket MQ都有哪些区别哇?

特性

ActiveMQ

RabbitMQ

RocketMQ

Kafka

单机吞吐量

万级,吞吐量比RocketMQ和Kafka要低了一个数量级

万级,吞吐量比RocketMQ和Kafka要低了一个数量级

10万级,RocketMQ也是可以支撑高吞吐的一种MQ

10万级别,这是kafka最大的优点,就是吞吐量高。

一般配合大数据类的系统来进行实时数据计算、日志采集等场景

可用性

高,基于主从架构实现高可用性

高,基于主从架构实现高可用性

非常高,分布式架构

非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用

优劣势总结

我没用过,大家自行百度吧...

erlang语言开发,性能极其好,延时很低;社区活跃度高,好处是,你有问题大部分都可以找到答案。

缺点吧,想改源码不好改

背靠阿里,Java开发。而且有实际实战数据摆在那里,一个字:强

kafka唯一的一点劣势是有可能消息重复消费,对数据准确性会造成极其轻微的影响

4、如何确保消息队列的高可用哇?

4.1、RabbitMQ高可用

Rabbit MQ是最具有代表性的,它是基于主从做的高可用的。它有三种模式:单机模式、普通集群、镜像集群

  • 单机模式
    • demo级别的,本地玩玩的,没人生产用
  • 普通集群
    • 就是搁多台机器上,启动好多MQ实例,每个机器启动一个。但是创建的queue,只会放到一个MQ实例上,每个MQ实例之间会同步拉取queue的元数据,消费的时候,如果发现当前的MQ上没有想要的数据,MQ会从其它示例上拉取到你需要的数据
    • 这种方式很麻烦,就是个普通集群。这样导致你要么消费者每次随机连接一个实例,然后拉取数据,要么固定连接那个queue所在的实例,进行消费。前者有数据拉取的开销,后者有单实例性能瓶颈
    • 反正还有好多好多性能问题,就不一一列举了
  • 镜像集群
    • 这种模式,才是Rabbit MQ的高可用模式,跟普通集群不同的是,你创建的queue,无论是元数据还是queue里面的消息都会存在于多个实例上,每次写消息到queue时,会自动把消息同步到多个Rabbit MQ实例上去
    • 好处:任何一个机子宕机了,不影响整个系统的使用
    • 缺点:
      • 性能开销大,每条消息都会同步到所有机器上,网络带宽与消耗大
      • 没有扩展性,如果某个queue的负载很重,即时你给它添加机子,新机子也包含了这个queue的所有数据

4.2、Kafka高可用

Kafka最基本的架构:多个Broker组成,每个Broker是一个节点。创建一个Topic,这个Topic可以划分为多个partition,每个partition可以存在于不同的Broker上,每个partition就放一部分数据。这跟Redis-cluster很像...

跟MQ相比的不同,这就是天然的分布式消息队列,一个Topic的数据,打散放在不同的机器(Broker)上,每个上面放一部分数据。MQ只能称为高可用

kafka 0.8以后,提供了HA机制,就是replica副本机制。每个partition的数据都会同步到其他机器上,形成自己的多个replica副本。然后所有replica会选举一个leader出来,那么生产和消费都跟这个leader打交道,然后其他replica就是follower。写的时候,leader会负责把数据同步到所有follower上去,读的时候就直接读leader上数据即可。只能读写leader?很简单,要是你可以随意读写每个follower,那么就要注意数据一致性的问题,系统复杂度太高,很容易出问题。Kafka会均匀的将一个partition的所有replica分布在不同的机器上,这样才可以提高容错性

这么搞,就有所谓的高可用性了,因为如果某个Broker宕机了,没事儿,那个Broker上面的partition在其他机器上都有副本的,如果这上面有某个partition的leader,那么此时会重新选举一个新的leader出来,大家继续读写那个新的leader即可。这就有所谓的高可用性了。

写数据的时候,生产者就写leader,然后leader将数据落地写本地磁盘,接着其他follower自己主动从leader来pull数据。一旦所有follower同步好数据了,就会发送ack给leader,leader收到所有follower的ack之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)

消费的时候,只会从leader去读,但是只有一个消息已经被所有follower都同步成功返回ack的时候,这个消息才会被消费者读到

5、咋保证消息被消费时的幂等性?

以Kafka为例,Kafka有个偏移量(offset)的概念,就是每个消息写进去,都会有个offset代表它的序号。消费者消费之后,会把当前消费成功的消息告诉Kafka,代表当前序号的消息我消费过了,下次消费者再拉取数据的时候,Kafka你要从我上次消费到的地方继续消费。这样确保了,即时Kafka重启了,消费者也会从宕机之前的位置继续拉取数据

但是还是会有少量的数据会被重复消费,因为保不齐消费者还没通知Kafka我已经消费过当前消息的时候呢,Kafka的机子重启了。这样还是会有一点点的数据会被重复消费,有人认为就那一点点的重复数据,无所谓,还有数据库主键撑着呢。但是你能确保所有数据都是跟数据库操作的吗?万一插入的数据表没有主键呢?那不G了....

啥时幂等性?就是一个数据或者请求,不论你重复多少次,数据都是不会变得,就像电商的下单按钮,你要是网不好,咔咔咔点好几下,下好几单....

具体业务可以有不同的处理方式:

  • 拿数据写库,可以先根据主键查询当前数据是否存在,如果存在update一下。
  • 拿数据写Redis,这不用怕,Redis每次set,天然幂等性
  • 比如都不是上面两个场景,那就可以稍微复杂一点,以实际举例。我做的一个生产库数据搬迁的业务,因为是生产数据,不能有一点的重复性消费,订单主表虽然有主键,但是它下面的许多从表没有。我的做法是,在消费者消费的时候,拿ORDER_ID去Redis里面查询是否当前订单消费过了,如果消费过了,就直接拉取下一条消息。
  • 或者你的业务是基于有数据库的唯一索引来操作的数据,那样不用担心重复消费的问题,插入会报错

6、如何确保消息的可靠性传输?

丢数据,就两种。队列把消息整丢了,或者代码里整丢了

6.1、Rabbit MQ

1、生产者弄丢数据

生产者把消费发送到MQ的时候,可能因为网络原因给整丢了

  • 事务功能,就是生产者发送消息之前开启MQ事务,然后发送消息,如果MQ没有接受到,生产者会报错,进行回滚,重试发送消息,如果成功接收,提交事务。这样做存在的问题不用多说,吞吐量就下来了,浪费性能
  • confirm模式,就是生产者设置开启confirm模式之后,每次写消息都会被分配一个唯一ID,消息成功到达MQ后,MQ会回传一个ack消息给生产者,以此来表示这条消息我成功拿到了。如果MQ没能处理这条消息,它会调用生产者nack接口,来通知你这条消息失败了,你可以重试发送消息或者抛异常。而且也可以结合这个机制来维护每个消息ID的状态,当超过一定的时间还没接受到这条消息的回调,我重发消息

这俩模式的区别,显而易见一个是同步阻塞,一个是异步

2、MQ弄丢数据

这个必须开启MQ的持久化功能,就是消息写入MQ之后会持久化到硬盘上,哪怕是MQ挂了,重启后也能找回数据。但是也有极限,那就是还没持久化呢,MQ挂了...

设置持久化有两个步骤,第一创建queue的时候将其设为持久化的,这样就可以保证MQ持久化queue的元数据,但是不会持久化queue里面的数据。第二是发送消息的时候,将消息持久化。想要将消息持久化必须同时开启这俩功能

而且持久化可以与生产者那边联合起来,只有消息被持久化到磁盘后,才回调ack给生产者

3、消费者弄丢数据

MQ如果丢失数据,可能是正在消费的时候,进程挂了,比如重启了...

这时候还可以用MQ的ack机制,简单说就是关闭MQ自动ack,通过代码里面调用API来完成。当你消费者把这条消息的业务处理完了,在代码里面手动ack。这样的话你如果迟迟没有ack,MQ就把这个消息分配给其他消费者进行处理

6.2、Kafka

1、消费者弄丢数据

关闭Kafka自动提交offset,在代码中处理完消息后手动提交offset,这样就可以确保数据不会丢

2、Kafka弄丢数据

比较常见的场景,Kafka某个Broker宕机,在重新选举partition的leader的时候,恰逢此时此刻leader正在给follower同步数据的时候。有一部分数据就丢了

所以要给Kafka设置至少以下4个参数

  • 给Topic设置replication.factor参数,这个值必须大于1,要求每个partition至少有2个副本
  • 在Kafka服务端设置min.insync.replicas参数,这个值必须大于1,这是要求leader感知到至少有一个follower在跟自己保持联络,这样能确保leader挂了,有人接盘
  • 在生产端设置acks=all;这个是要求每条数据,必须等待我Kafka写入所有replication之后,才能认为写入数据成功了。
  • 在生产端设置retries=MAX;无限重试的意思,一旦写入Kafka失败就无限重试,卡在那里

3、生产者会不会弄丢数据

如果按照上述的思路设置了ack=all,一定不会丢,要求是,你的leader接收到消息,所有的follower都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次

7、如何保证消息的顺序性?

你搁MySQL里增删改一条数据,对应出来3条binlog,接着3条binlog发送到MQ里面。本来消费端的消费顺序是:增加、修改、删除。结果给换了消费顺序成:删除、增加、修改...

会导致消费顺序错乱的场景:

  • Rabbit MQ:一个queue,多个消费者
  • Kafka:一个Topic,一个partition,一个消费者,内部多线程...

如何保证消息的顺序性?

  • Rabbit MQ:拆分多个queue,一个queue对应一个消费者,就是麻烦queue变多了。或者一个queue对应一个消费者,消费者内部用内存队列做排队
  • Kafka:一个Topic,一个partition,一个消费者,让其内部实现单线程消费。写N个内存queue,然后对应N个线程,分别消费对应内存queue即可

8、咋解决消息队列的延时以及过期失效问题?有几百万消息积压咋办?

8.1、Kafka大量消息堆积在MQ里,好几个小时还没解决

这事儿我还真遇见过,当时就在发布生产之前就考虑到这种场景了,结果生产上一看,按目前的逻辑得处理18天...紧急放开开关,让代码走另一套逻辑

出现这种情况,如果没有提前想好对应策略,只能做紧急扩容

    1. 先修复消费者问题,确保其恢复消费速度,然后停掉所有消费者
    2. 新建一个Topic,partition是原来的10或者多少倍。临时建立好原来的10倍的queue
    3. 写一个临时分发数据的消费者,将这个程序部署上去库库消费积压的数据,消费之后不做业务处理,直接轮询写入刚刚临时建立好的10倍数量的queue
    4. 临时借调10倍的机器来部署消费者,每一个消费者对应上面的一个queue

这种做法就是人为的把队列资源和消费者资源扩大10倍,等消息积压处理的差不多了,再恢复原架构

8.2、Rabbit MQ

如果是Rabbit MQ,MQ是可以设置过期时间的,如果消息在MQ里积压超过一定时间,就会被MQ给清理掉。这可就不是积压的问题了,是数据没了啊!!!

这种情况下,就不是说增加消费者可以解决的了,等你写完程序发上去,消息都没了。咋解决呢?只能人为的半夜,写程序把丢失的数据找回来,重新塞MQ里

9、如果让你写个消息队列,你咋设计?

如果面试官真问了,就按Kafka的设计理念回答

  • 首先这个mq得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下kafka的设计理念,broker -> topic -> partition,每个partition放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给topic增加partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
  • 其次你得考虑一下这个mq的数据要不要落地磁盘吧?那肯定要了,落磁盘,才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是kafka的思路
  • 其次你考虑一下你的mq的可用性啊?这个事儿,具体参考kafka的高可用保障机制。多副本 -> leader & follower -> broker挂了重新选举leader即可对外服务
  • 能不能支持数据0丢失啊?可以的,参考我们之前说的那个kafka数据零丢失方案

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值