消息队列经典十连问,你能扛到第几问?
Posted 涛歌依旧
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了消息队列经典十连问,你能扛到第几问?相关的知识,希望对你有一定的参考价值。
大家好呀。金三银四即将来临,整理了十道十分经典的消息队列面试题,看完肯定对面试有帮助的,大家一起加油哈~
什么是消息队列 消息队列的应用场景 消息队列如何解决消息丢失问题 消息队列如何保证消息的顺序性。 消息有可能发生重复消费吗?如何幂等处理? 如何处理消息队列的消息积压问题 消息队列技术选型,Kafka还是RocketMQ,还是RabbitMQ 消息中间件如何做到高可用? 如何保证数据一致性,事务消息如何实现 如果让你写一个消息队列,该如何进行架构设计?
你可以把消息队列理解为一个使用队列来通信的组件。它的本质,就是个转发器,包含发消息、存消息、消费消息的过程。最简单的消息队列模型如下:
我们通常说的消息队列,简称MQ(Message Queue),它其实就指消息中间件,当前业界比较流行的开源消息中间件包括:RabbitMQ、RocketMQ、Kafka
。
有时候面试官会换个角度问你,为什么使用消息队列。你可以回答以下这几点:
应用解耦 流量削峰 异步处理 消息通讯 远程调用
一个消息从生产者产生,到被消费者消费,主要经过这3个过程:
因此如何保证MQ不丢失消息,可以从这三个阶段阐述:
消息的有序性,就是指可以按照消息的发送顺序来消费。有些业务对消息的顺序是有要求的,比如先下单再付款,最后再完成订单,这样等。假设生产者先后产生了两条消息,分别是下单消息(M1),付款消息(M2),M1比M2先产生,如何保证M1比M2先被消费呢。
为了保证消息的顺序性,可以将M1、M2发送到同一个Server上,当M1发送完收到ack后,M2再发送。如图:
这样还是可能会有问题,因为从MQ服务器到消费端,可能存在网络延迟,虽然M1先发送,但是它比M2晚到。
那还能怎么办才能保证消息的顺序性呢?将M1和M2发往同一个消费者,且发送M1后,等到消费端ACK成功后,才发送M2就得了。
消息队列保证顺序性整体思路就是这样啦。比如Kafka的全局有序消息,就是这种思想的体现: 就是生产者发消息时,1个Topic
只能对应1个Partition
,一个 Consumer
,内部单线程消费。
但是这样吞吐量太低,一般保证消息局部有序即可。在发消息的时候指定Partition Key
,Kafka对其进行Hash计算,根据计算结果决定放入哪个Partition
。这样Partition Key相同的消息会放在同一个Partition。然后多消费者单线程消费指定的Partition。
消息队列是可能发生重复消费的。
如何幂等处理重复消息呢?
我之前写过一篇幂等设计的文章,大家有兴趣可以看下哈:聊聊幂等设计
幂等处理重复消息,简单来说,就是搞个本地表,带唯一业务标记的,利用主键或者唯一性索引,每次处理业务,先校验一下就好啦。又或者用redis缓存下业务标记,每次看下是否处理过了。
消息积压是因为生产者的生产速度,大于消费者的消费速度。遇到消息积压问题时,我们需要先排查,是不是有bug产生了。
如果不是bug,我们可以优化一下消费的逻辑,比如之前是一条一条消息消费处理的话,我们可以确认是不是可以优为批量处理消息。如果还是慢,我们可以考虑水平扩容,增加Topic的队列数,和消费组机器的数量,提升整体消费能力。
如果是bug导致几百万消息持续积压几小时。有如何处理呢?需要解决bug,临时紧急扩容,大概思路如下:
先修复consumer消费者的问题,以确保其恢复消费速度,然后将现有consumer 都停掉。 新建一个 topic,partition 是原来的 10 倍,临时建立好原先10倍的queue 数量。 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。
先可以对比下它们优缺点:
Kafka | RocketMQ | RabbitMQ | |
---|---|---|---|
单机吞吐量 | 17.3w/s | 11.6w/s | 2.6w/s(消息做持久化) |
开发语言 | Scala/Java | Java | Erlang |
主要维护者 | Apache | Alibaba | Mozilla/Spring |
订阅形式 | 基于topic,按照topic进行正则匹配的发布订阅模式 | 基于topic/messageTag,按照消息类型、属性进行正则匹配的发布订阅模式 | 提供了4种:direct, topic ,Headers和fanout。fanout就是广播模式 |
持久化 | 支持大量堆积 | 支持大量堆积 | 支持少量堆积 |
顺序消息 | 支持 | 支持 | 不支持 |
集群方式 | 天然的Leader-Slave,无状态集群,每台服务器既是Master也是Slave | 常用 多对’Master-Slave’ 模式,开源版本需手动切换Slave变成Master | 支持简单集群,\'复制’模式,对高级集群模式支持不好。 |
性能稳定性 | 较差 | 一般 | 好 |
消息中间件如何保证高可用呢?单机是没有高可用可言的,高可用都是对集群来说的,一起看下kafka的高可用吧。
Kafka 的基础集群架构,由多个broker
组成,每个broker
都是一个节点。当你创建一个topic
时,它可以划分为多个partition
,而每个partition
放一部分数据,分别存在于不同的 broker 上。也就是说,一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。
有些伙伴可能有疑问,每个partition
放一部分数据,如果对应的broker挂了,那这部分数据是不是就丢失了?那还谈什么高可用呢?
Kafka 0.8 之后,提供了复制品副本机制来保证高可用,即每个 partition 的数据都会同步到其它机器上,形成多个副本。然后所有的副本会选举一个 leader 出来,让leader去跟生产和消费者打交道,其他副本都是follower。写数据时,leader 负责把数据同步给所有的follower,读消息时, 直接读 leader 上的数据即可。如何保证高可用的?就是假设某个 broker 宕机,这个broker上的partition 在其他机器上都有副本的。如果挂的是leader的broker呢?其他follower会重新选一个leader出来。
一条普通的MQ消息,从产生到被消费,大概流程如下:
生产者产生消息,发送带MQ服务器 MQ收到消息后,将消息持久化到存储系统。 MQ服务器返回ACk到生产者。 MQ服务器把消息push给消费者 消费者消费完消息,响应ACK MQ服务器收到ACK,认为消息消费成功,即在存储中删除消息。
我们举个下订单的例子吧。订单系统创建完订单后,再发送消息给下游系统。如果订单创建成功,然后消息没有成功发送出去,下游系统就无法感知这个事情,出导致数据不一致。
如何保证数据一致性呢?可以使用事务消息。一起来看下事务消息是如何实现的吧。
生产者产生消息,发送一条半事务消息到MQ服务器 MQ收到消息后,将消息持久化到存储系统,这条消息的状态是待发送状态。 MQ服务器返回ACK确认到生产者,此时MQ不会触发消息推送事件 生产者执行本地事务 如果本地事务执行成功,即commit执行结果到MQ服务器;如果执行失败,发送rollback。 如果是正常的commit,MQ服务器更新消息状态为可发送;如果是rollback,即删除消息。 如果消息状态更新为可发送,则MQ服务器会push消息给消费者。消费者消费完就回ACK。 如果MQ服务器长时间没有收到生产者的commit或者rollback,它会反查生产者,然后根据查询到的结果执行最终状态。
这个问题面试官主要考察三个方面的知识点:
遇到这种设计题,大部分人会很蒙圈,因为平时没有思考过类似的问题。大多数人平时埋头增删改啥,不去思考框架背后的一些原理。有很多类似的问题,比如让你来设计一个 Dubbo 框架,或者让你来设计一个MyBatis 框架,你会怎么思考呢?
回答这类问题,并不要求你研究过那技术的源码,你知道那个技术框架的基本结构、工作原理即可。设计一个消息队列,我们可以从这几个角度去思考:
首先是消息队列的整体流程,producer发送消息给broker,broker存储好,broker再发送给consumer消费,consumer回复消费确认等。 producer发送消息给broker,broker发消息给consumer消费,那就需要两次RPC了,RPC如何设计呢?可以参考开源框架Dubbo,你可以说说服务发现、序列化协议等等 broker考虑如何持久化呢,是放文件系统还是数据库呢,会不会消息堆积呢,消息堆积如何处理呢。 消费关系如何保存呢?点对点还是广播方式呢?广播关系又是如何维护呢?zk还是config server 消息可靠性如何保证呢?如果消息重复了,如何幂等处理呢? 消息队列的高可用如何设计呢?可以参考Kafka的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。 消息事务特性,与本地业务同个事务,本地消息落库;消息投递到服务端,本地才删除;定时任务扫描本地消息库,补偿发送。 MQ得伸缩性和可扩展性,如果消息积压或者资源不够时,如何支持快速扩容,提高吞吐?可以参照一下 Kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
阿里RocketMQ如何解决消息的顺序&重复两大硬伤?: https://dbaplus.cn/news-21-1123-1.html
[2]消息中间件面试题:如何解决消息队列的延时以及过期失效问题?: https://jsbintask.cn/2019/01/28/interview/interview-middleware-manymessage/
[3]消息队列设计精要: https://zhuanlan.zhihu.com/p/21649950
[4]MQ消息最终一致性解决方案: https://juejin.cn/post/6844903951448408071
求个点赞、在看、转发,感谢
Redis夺命十二问,你能扛到第几问?
大家好,我是飘渺
Redis是面试中绕不过的槛,只要在简历中写了用过Redis,肯定逃不过。今天我们就来模拟一下面试官在Redis这个话题上是如何一步一步深入,全面考察候选人对于Redis的掌握情况。
小张:
面试官,你好。我是来参加面试的。
面试官:
你好,小张。我看了你的简历,熟练掌握Redis,那么我就随便问你几个Redis相关的问题吧。首先我的问题是,Redis是单线程还是多线程呢?
小张:
Redis不同版本之间采用的线程模型是不一样的,在Redis4.0版本之前使用的是单线程模型,在4.0版本之后增加了多线程的支持。
在4.0之前虽然我们说Redis是单线程,也只是说它的网络I/O线程以及Set 和 Get操作是由一个线程完成的。但是Redis的持久化、集群同步还是使用其他线程来完成。
4.0之后添加了多线程的支持,主要是体现在大数据的异步删除功能上,例如 unlink key
、flushdb async
、flushall async
等
面试官:
回答的很好,那为什么Redis在4.0之前会选择使用单线程?而且使用单线程还那么快?
小张:
选择单线程个人觉得主要是使用简单,不存在锁竞争,可以在无锁的情况下完成所有操作,不存在死锁和线程切换带来的性能和时间上的开销,但同时单线程也不能完全发挥出多核CPU的性能。
至于为什么单线程那么快我觉得主要有以下几个原因:
-
Redis 的大部分操作都在内存中完成,内存中的执行效率本身就很快,并且采用了高效的数据结构,比如哈希表和跳表。
-
使用单线程避免了多线程的竞争,省去了多线程切换带来的时间和性能开销,并且不会出现死锁。
-
采用 I/O 多路复用机制处理大量客户端的Socket请求,因为这是基于非阻塞的 I/O 模型,这就让Redis可以高效地进行网络通信,I/O的读写流程也不再阻塞。
面试官:
不错,那Redis是如何实现数据不丢失的呢?
小张:
Redis数据是存储在内存中的,为了保证Redis数据不丢失,那就要把数据从内存存储到磁盘上,以便在服务器重启后还能够从磁盘中恢复原有数据,这就是Redis的数据持久化。Redis数据持久化有三种方式。
-
AOF 日志(Append Only File,文件追加方式):记录所有的操作命令,并以文本的形式追加到文件中。
-
RDB 快照(Redis DataBase):将某一个时刻的内存数据,以二进制的方式写入磁盘。
-
混合持久化方式:Redis 4.0 新增了混合持久化的方式,集成了 RDB 和 AOF 的优点。
面试官:
那你分别说说 AOF和 RDB的实现原理吧。
小张:
AOF采用的是写后日志的方式,Redis先执行命令把数据写入内存,然后再记录日志到文件中。AOF日志记录的是操作命令,不是实际的数据,如果采用AOF方法做故障恢复时需要将全量日志都执行一遍。
RDB采用的是内存快照的方式,它记录的是某一时刻的数据,而不是操作,所以采用RDB方法做故障恢复时只需要直接把RDB文件读入内存即可,实现快速恢复。
面试官:
你刚提到了AOF采用的是 “写后日志” 的方式,我们平时用的MySQL则采用的是 “写前日志”,那 Redis为什么要先执行命令,再把数据写入日志呢?
小张:额头开始冒汗,问的是些啥问题呀。。。
额,这个主要是由于Redis在写入日志之前,不对命令进行语法检查,所以只记录执行成功的命令,避免出现记录错误命令的情况,而且在命令执行后再写日志不会阻塞当前的写操作。
面试官:
那 后写日志又有什么风险呢?
小张:
我... 这个我不会。
面试官:
好吧,后写日志主要有两个风险可能会发生:
-
数据可能会丢失:如果 Redis 刚执行完命令,此时发生故障宕机,会导致这条命令存在丢失的风险。
-
可能阻塞其他操作:AOF 日志其实也是在主线程中执行,所以当 Redis 把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行。
我还有个问题是 RDB做快照时会阻塞线程吗?
小张:
Redis 提供了两个命令来生成 RDB 快照文件,分别是 save
和 bgsave
。save
命令在主线程中执行,会导致阻塞。而 bgsave
命令则会创建一个子进程,用于写入 RDB 文件的操作,避免了对主线程的阻塞,这也是 Redis RDB 的默认配置。
面试官:
RDB 做快照的时候数据能修改吗?
小张:
save是同步的会阻塞客户端命令,bgsave的时候是可以修改的。
面试官:
那Redis是怎么解决在bgsave做快照的时候允许数据修改呢?
小张:(你咋还问。。。我™不会啊!)
额,这个我不太清楚...
面试官:
这里主要是利用bgsave
的子线程实现的,具体操作如下:
-
如果主线程执行读操作,则主线程和
bgsave
子进程互相不影响; -
如果主线程执行写操作,则被修改的数据会复制一份副本,然后
bgsave
子进程会把该副本数据写入 RDB 文件,在这个过程中,主线程仍然可以直接修改原来的数据。
要注意,Redis 对 RDB 的执行频率非常重要,因为这会影响快照数据的完整性以及 Redis 的稳定性,所以在 Redis 4.0 后,增加了 AOF 和 RDB 混合的数据持久化机制: 把数据以 RDB 的方式写入文件,再将后续的操作命令以 AOF 的格式存入文件,既保证了 Redis 重启速度,又降低数据丢失风险。
小张:
学到了学到了。
面试官:
那你再跟我说说Redis如何实现高可用吧?
小张:
Redis实现高可用主要有三种方式:主从复制、哨兵模式,以及 Redis 集群。
主从复制
将从前的一台 Redis 服务器,同步数据到多台从 Redis 服务器上,即一主多从的模式,这个跟MySQL主从复制的原理一样。
哨兵模式
使用 Redis 主从服务的时候,会有一个问题,就是当 Redis 的主从服务器出现故障宕机时,需要手动进行恢复,为了解决这个问题,Redis 增加了哨兵模式(因为哨兵模式做到了可以监控主从服务器,并且提供自动容灾恢复的功能)。
Redis Cluster(集群)
Redis Cluster 是一种分布式去中心化的运行模式,是在 Redis 3.0 版本中推出的 Redis 集群方案,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,从而提高 Redis 服务的读写性能。
面试官:
使用哨兵模式在数据上有副本数据做保证,在可用性上又有哨兵监控,一旦master宕机会选举salve节点为master节点,这种已经满足了我们的生产环境需要,那为什么还需要使用集群模式呢?
小张:
额,哨兵模式归根节点还是主从模式,在主从模式下我们可以通过增加salve节点来扩展读并发能力,但是没办法扩展写能力和存储能力,存储能力只能是master节点能够承载的上限。所以为了扩展写能力和存储能力,我们就需要引入集群模式。
面试官:
集群中那么多Master节点,redis cluster在存储的时候如何确定选择哪个节点呢?
小张:
这应该是使用了某种hash算法,但是我不太清楚。。。
面试官:
那好,今天的面试就到这里吧,你先回去等我们的面试通知。
小张:
好的,谢谢面试官,你能告诉我redis cluster怎么实现节点选择的吗?
面试官:
Redis Cluster采用的是类一致性哈希算法实现节点选择的,至于什么是一致性哈希算法你自己回去看看。
Redis Cluster将自己分成了16384个Slot(槽位),哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中,具体执行过程分为两大步。
-
根据键值对的 key,按照 CRC16 算法计算一个 16 bit 的值。
-
再用 16bit 值对 16384 取模,得到
0~16383
范围内的模数,每个模数代表一个相应编号的哈希槽。
每个Redis节点负责处理一部分槽位,加入你有三个master节点 ABC,每个节点负责的槽位如下:
节点 | 处理槽位 |
---|---|
A | 0-5000 |
B | 5001 - 10000 |
C | 10001 - 16383 |
这样就实现了cluster节点的选择。
好了,今天关于Redis的面试就到这里了,你们觉得如何,都能答对吗?
新开了一个纯技术交流群,群里氛围还不错,无广告,无套路,单纯的吹牛逼,侃人生,想进的可以通过下方二维码加我微信,备注进群。
以上是关于消息队列经典十连问,你能扛到第几问?的主要内容,如果未能解决你的问题,请参考以下文章