消息队列相关面试总结

210 阅读6分钟

1:为什么要引入mq

三个好处,解耦,异步,削峰。

  • 解耦:假如有个A系统,需要把数据写到BCD系统里,现在如果E系统也要接入,或者B系统不需要A的数据了,A系统就要频繁改造,如果用mq,A系统只需要发出去,谁爱接谁自己接,不接就停掉,A系统也不用管他们是不是调用成功。
  • 异步:假如用户下单完需要发短信,假设发一个短信需要一秒,就可以把这个步骤异步执行,对这次的结果能很快返回。
  • 削峰:单台mysql一般也就能接受2000左右并发,如果此时有5000并发,就可以用mq先把消息放进来,再启动消费者每秒消费2000个,等高峰过去,慢慢消化这批数据,可以抗住高并发

2:引入mq有什么问题

  • 可用性降低:本来只需要考虑系统挂了,现在引入mq后,还得保证你mq不能挂
  • 复杂度提升:引入新技术栈,必然导致程序复杂度提高,要考虑各种问题,如mq信息丢失,重复等问题
  • 一致性问题: 假设A系统被BCD系统消费完算成功,那BC成功了,D失败了,怎么处理?会导致数据一致性问题。

3:怎么进行消息队列选型

常见的mq有activeMq,rabbitMq,rocketMq和kafka。

  • activeMq优点是较为成熟,缺点是版本更新慢,很久不更新,文档比较少。现在一般不用他
  • rabbitMq缺点是elang语言开发,源码改造难度大,而且有瓶颈,不支持分布式,优点则是管理界面好,对中小互联网公司来说较为友善,文档丰富,更新快。如果流量不是特别高,一般是中小互联网公司的首选。
  • rocketMq是阿里系的,经过高流量的验证,具有分布式,高可用的特点,java语言开发,便于阅读源码,缺点则是社区活跃度一般,而且要做好阿里不再维护的打算,如果有专门的团队,则最好用rocketMq,哪怕人家不更新了,你也能hold住
  • kafka 没的说,实时计算、日志采集的首选。

4:如何保证其高可用

  • rabbitMq可以用集群镜像模式,每个实例都拥有全部数据,只要有一台活着,就不影响可用
  • kafka在0.8后才有高可用性,一个top分为多个partition,每个partition有多个副本,每次写数据和消费数据都跟leader玩,副本只管同步数据就行。避免大家机器上数据不同步,只要保证partition和副本不在一台机器上,即便这个机器挂了,还有副本转正。

5:消费到重复数据怎么办

  • 消息重复是很正常的,mq可以保证消息不丢失,其代价就是不一定不重复。重复并不可怕,只要做好幂等性,保证同一个请求不被处理两次就行。可以在数据库加唯一索引,可以每次把唯一请求id都写到redis上,每次消费前看一下redis是否有这条数据等

6:消息丢失怎么办

  • 如果是rabbitMq,先考虑什么情况可能丢。
  • 第一,生产者没把消息写到mq上,我们可以开事务,只有写上了才会返回成功,不过一般没人这么干,事务是同步阻塞的,吞吐量大降,一般都是开confirm机制,声明两个接口,成功的ack接口和失败的nack接口,每次发送消息,把唯一的消息id带到mq,一旦mq写入,就会回调ack接口,如果出错就调用nack。
  • 第二,mq挂了,消息还在内存里,有可能丢,只需要开消息持久化就行了,队列的持久化和消息的持久化都要开启。
  • 第三,消费者没处理完就返回成功,消息可能丢。只需要关闭自动ack机制,等逻辑处理完再手动调用ack就行。
  • 如果是kafka呢?
  • 第一,消费者没处理完,同理,关闭自动确认机制,手动调用。
  • 第二,kafka挂了,可能刚把数据写到主节点,副本还没同步完,这时候挂了主节点的数据可能丢失一部分。可以这么处理:先开启两个参数,保证最低有两个副本(包括自己),还有要保证最低有一个副本稳定的和自己保持联系。在生产端也要开启只有数据全部发送到所有副本后才返回成功。顺便设置个重试次数很大,失败了就重试。这样设置后,生产端也不会丢数据了。

7:怎么保证消费的顺序性

  • 什么时候会乱序?如果是rabbitMq,一个队列有多个消费者,确实保证不了有序,kafka的话,内部如果多线程处理,也会乱。
  • rabbitMq的解决方案,既然多个消费者消费同一个queue可能乱序,那就不要让他消费同一个,可以把需要保证顺序的消息,扔到同一个队列,然后让一个消费者监听这个队列,其他消费者不要监听这个队列就行了。
  • kafka的解决方案,其实原理也差不多,一个partition只能由一个消费者消费,只要在发送的时候制定一个key就能让消息进同一个partition,当然,如果消费者开了多线程处理消息,还是有可能有问题,那就继续按照刚才的思路处理,再加内存队列,保证这些消息进同一个内存队列,就不会有乱序问题了

8:如果堆积大量数据在mq里怎么办

  • 一般来说大量堆积要么每秒请求太多,要么消费太慢,如果是消费者有问题,先解决消费者bug,如果是消费慢,加机器消费就行了。假设你已经处理好了bug,速度也正常了,但是堆积数据太多,短时间处理不完,但是又必须短时间处理怎么办?没别的,扩容。如果是kafka,因为一个partition只能被一个消费者消费,所以要临时写个程序,先创建一个topic,partition是原来十倍,消费者不再进行耗时操作处理,直接把消息重新丢到新的topic上,再扩大十倍的消费者去消费新的topic,等把消息消费完了,再恢复原先架构。
  • 那如果mq快满了,但是消费者还没修理好怎么办?不怎么办,先把消费者代码改了,拿到消息就丢,清空积压的消息再说,再到夜深人静的时候,把数据重新想办法再导一次。

9:rabbitMq发送信息流程

  • rabbitMq有5种模式,有connection,channel,exchange,routing key,queue等概念
  • 1:默认,直接发到queue里
  • 2:fanout,不用指定路由key,凡是绑定此交换器的队列都会收到消息
  • 3:direct,需要指定routing key,完全匹配的queue的才会接收到消息
  • 4:top,支持模糊匹配的routing key
  • 5:header,header匹配才会收到消息,一般不怎么用