共计 4389 个字符,预计需要花费 11 分钟才能阅读完成。
继之前的 mysql 夺命连环之后,我发现我这个题目被好多套用的,什么夺命 zookeeper,夺命多线程一大堆,这一次,开始面试题系列 MQ 专题,音讯队列作为日常常见的应用中间件,面试也是必问的点之一,一起来看看 MQ 的面试题。
你们为什么应用 mq?具体的应用场景是什么?
mq 的作用很简略,削峰填谷。以电商交易下单的场景来说,正向交易的过程可能波及到创立订单、扣减库存、扣减流动估算、扣减积分等等。每个接口的耗时如果是 100ms,那么实践上整个下单的链路就须要消耗 400ms,这个工夫显然是太长了。
如果这些操作全副同步解决的话,首先调用链路太长影响接口性能,其次分布式事务的问题很难解决,这时候像扣减估算和积分这种对实时一致性要求没有那么高的申请,齐全就能够通过 mq 异步的形式去解决了。同时,思考到异步带来的不统一的问题,咱们能够通过 job 去重试保障接口调用胜利,而且个别公司都会有核查的平台,比方下单胜利然而未扣减积分的这种问题能够通过核查作为兜底的解决计划。
应用 mq 之后咱们的链路变简略了,同时异步发送音讯咱们的整个零碎的抗压能力也回升了。
那你们应用什么 mq?基于什么做的选型?
咱们次要调研了几个支流的 mq,kafka、rabbitmq、rocketmq、activemq,选型咱们次要基于以下几个点去思考:
- 因为咱们零碎的 qps 压力比拟大,所以性能是首要思考的因素。
- 开发语言,因为咱们的开发语言是 java,次要是为了不便二次开发。
- 对于高并发的业务场景是必须的,所以须要反对分布式架构的设计。
- 性能全面,因为不同的业务场景,可能会用到程序音讯、事务音讯等。
基于以上几个思考,咱们最终抉择了 RocketMQ。
你下面提到异步发送,那音讯可靠性怎么保障?
音讯失落可能产生在生产者发送音讯、MQ 自身失落音讯、消费者失落音讯 3 个方面。
生产者失落
生产者失落音讯的可能点在于程序发送失败抛异样了没有重试解决,或者发送的过程胜利然而过程中网络闪断 MQ 没收到,音讯就失落了。
因为同步发送的个别不会呈现这样应用形式,所以咱们就不思考同步发送的问题,咱们基于异步发送的场景来说。
异步发送分为两个形式: 异步有回调和异步无回调 ,无回调的形式,生产者发送完后不论后果可能就会造成音讯失落,而通过异步发送 + 回调告诉 + 本地音讯表的模式咱们就能够做出一个解决方案。以下单的场景举例。
- 下单后先保留本地数据和 MQ 音讯表,这时候音讯的状态是发送中,如果本地事务失败,那么下单失败,事务回滚。
- 下单胜利,间接返回客户端胜利,异步发送 MQ 音讯
- MQ 回调告诉音讯发送后果,对应更新数据库 MQ 发送状态
- JOB 轮询超过肯定工夫(工夫依据业务配置)还未发送胜利的音讯去重试
- 在监控平台配置或者 JOB 程序处理超过肯定次数始终发送不胜利的音讯,告警,人工染指。
一般而言,对于大部分场景来说异步回调的模式就能够了,只有那种须要齐全保障不能失落音讯的场景咱们做一套残缺的解决方案。
MQ 失落
如果生产者保障音讯发送到 MQ,而 MQ 收到音讯后还在内存中,这时候宕机了又没来得及同步给从节点,就有可能导致音讯失落。
比方 RocketMQ:
RocketMQ 分为同步刷盘和异步刷盘两种形式,默认的是异步刷盘,就有可能导致音讯还未刷到硬盘上就失落了,能够通过设置为同步刷盘的形式来保障音讯可靠性,这样即便 MQ 挂了,复原的时候也能够从磁盘中去复原音讯。
比方 Kafka 也能够通过配置做到:
acks=all 只有参加复制的所有节点全副收到音讯,才返回生产者胜利。这样的话除非所有的节点都挂了,音讯才会失落。replication.factor=N, 设置大于 1 的数,这会要求每个 partion 至多有 2 个正本
min.insync.replicas=N,设置大于 1 的数,这会要求 leader 至多感知到一个 follower 还放弃着连贯
retries=N,设置一个十分大的值,让生产者发送失败始终重试
尽管咱们能够通过配置的形式来达到 MQ 自身高可用的目标,然而都对性能有损耗,怎么配置须要依据业务做出衡量。
消费者失落
消费者失落音讯的场景:消费者刚收到音讯,此时服务器宕机,MQ 认为消费者曾经生产,不会反复发送音讯,音讯失落。
RocketMQ 默认是须要消费者回复 ack 确认,而 kafka 须要手动开启配置敞开主动 offset。
生产方不返回 ack 确认,重发的机制依据 MQ 类型的不同发送工夫距离、次数都不尽相同,如果重试超过次数之后会进入死信队列,须要手工来解决了。(Kafka 没有这些)
你说到消费者生产失败的问题,那么如果始终生产失败导致音讯积压怎么解决?
因为思考到时消费者生产始终出错的问题,那么咱们能够从以下几个角度来思考:
- 消费者出错,必定是程序或者其余问题导致的,如果容易修复,先把问题修复,让 consumer 恢复正常生产
- 如果工夫来不及解决很麻烦,做转发解决,写一个长期的 consumer 生产计划,先把音讯生产,而后再转发到一个新的 topic 和 MQ 资源,这个新的 topic 的机器资源独自申请,要能承载住以后积压的音讯
- 解决完积压数据后,修复 consumer,去生产新的 MQ 和现有的 MQ 数据,新 MQ 生产实现后恢复原状
那如果音讯积压达到磁盘下限,音讯被删除了怎么办?
这。。。他妈都删除了我有啥方法啊。。。沉着,再想想。。有了。
最后,咱们发送的音讯记录是落库保留了的,而转发发送的数据也保留了,那么咱们就能够通过这部分数据来找到失落的那局部数据,再独自跑个脚本重发就能够了。如果转发的程序没有落库,那就和生产方的记录去做比照,只是过程会更艰巨一点。
说了这么多,那你说说 RocketMQ 实现原理吧?
RocketMQ 由 NameServer 注册核心集群、Producer 生产者集群、Consumer 消费者集群和若干 Broker(RocketMQ 过程)组成,它的架构原理是这样的:
- Broker 在启动的时候去向所有的 NameServer 注册,并放弃长连贯,每 30s 发送一次心跳
- Producer 在发送音讯的时候从 NameServer 获取 Broker 服务器地址,依据负载平衡算法抉择一台服务器来发送音讯
- Conusmer 生产音讯的时候同样从 NameServer 获取 Broker 地址,而后被动拉取音讯来生产
为什么 RocketMQ 不应用 Zookeeper 作为注册核心呢?
我认为有以下几个点是不应用 zookeeper 的起因:
- 依据 CAP 实践,同时最多只能满足两个点,而 zookeeper 满足的是 CP,也就是说 zookeeper 并不能保障服务的可用性,zookeeper 在进行选举的时候,整个选举的工夫太长,期间整个集群都处于不可用的状态,而这对于一个注册核心来说必定是不能承受的,作为服务发现来说就应该是为可用性而设计。
- 基于性能的思考,NameServer 自身的实现十分轻量,而且能够通过减少机器的形式程度扩大,减少集群的抗压能力,而 zookeeper 的写是不可扩大的,而 zookeeper 要解决这个问题只能通过划分畛域,划分多个 zookeeper 集群来解决,首先操作起来太简单,其次这样还是又违反了 CAP 中的 A 的设计,导致服务之间是不连通的。
- 长久化的机制来带的问题,ZooKeeper 的 ZAB 协定对每一个写申请,会在每个 ZooKeeper 节点上放弃写一个事务日志,同时再加上定期的将内存数据镜像(Snapshot)到磁盘来保证数据的一致性和持久性,而对于一个简略的服务发现的场景来说,这其实没有太大的必要,这个实现计划太重了。而且自身存储的数据应该是高度定制化的。
- 音讯发送应该弱依赖注册核心,而 RocketMQ 的设计理念也正是基于此,生产者在第一次发送音讯的时候从 NameServer 获取到 Broker 地址后缓存到本地,如果 NameServer 整个集群不可用,短时间内对于生产者和消费者并不会产生太大影响。
那 Broker 是怎么保留数据的呢?
RocketMQ 次要的存储文件包含 commitlog 文件、consumequeue 文件、indexfile 文件。
Broker 在收到音讯之后,会把音讯保留到 commitlog 的文件当中,而同时在分布式的存储当中,每个 broker 都会保留一部分 topic 的数据,同时,每个 topic 对应的 messagequeue 下都会生成 consumequeue 文件用于保留 commitlog 的物理地位偏移量 offset,indexfile 中会保留 key 和 offset 的对应关系。
CommitLog 文件保留于 ${Rocket_Home}/store/commitlog 目录中,从图中咱们能够显著看进去文件名的偏移量,每个文件默认 1G,写满后主动生成一个新的文件。
因为同一个 topic 的音讯并不是间断的存储在 commitlog 中,消费者如果间接从 commitlog 获取音讯效率非常低,所以通过 consumequeue 保留 commitlog 中音讯的偏移量的物理地址,这样消费者在生产的时候先从 consumequeue 中依据偏移量定位到具体的 commitlog 物理文件,而后依据肯定的规定(offset 和文件大小取模)在 commitlog 中疾速定位。
Master 和 Slave 之间是怎么同步数据的呢?
而音讯在 master 和 slave 之间的同步是依据 raft 协定来进行的:
- 在 broker 收到音讯后,会被标记为 uncommitted 状态
- 而后会把音讯发送给所有的 slave
- slave 在收到音讯之后返回 ack 响应给 master
- master 在收到超过半数的 ack 之后,把音讯标记为 committed
- 发送 committed 音讯给所有 slave,slave 也批改状态为 committed
你晓得 RocketMQ 为什么速度快吗?
是因为应用了顺序存储、Page Cache 和异步刷盘。
- 咱们在写入 commitlog 的时候是程序写入的,这样比随机写入的性能就会进步很多
- 写入 commitlog 的时候并不是间接写入磁盘,而是先写入操作系统的 PageCache
- 最初由操作系统异步将缓存中的数据刷到磁盘
什么是事务、半事务音讯?怎么实现的?
事务音讯就是 MQ 提供的相似 XA 的分布式事务能力,通过事务音讯能够达到分布式事务的最终一致性。
半事务音讯就是 MQ 收到了生产者的音讯,然而没有收到二次确认,不能投递的音讯。
实现原理如下:
- 生产者先发送一条半事务音讯到 MQ
- MQ 收到音讯后返回 ack 确认
- 生产者开始执行本地事务
- 如果事务执行胜利发送 commit 到 MQ,失败发送 rollback
- 如果 MQ 长时间未收到生产者的二次确认 commit 或者 rollback,MQ 对生产者发动音讯回查
- 生产者查问事务执行最终状态
- 依据查问事务状态再次提交二次确认
最终,如果 MQ 收到二次确认 commit,就能够把音讯投递给消费者,反之如果是 rollback,音讯会保留下来并且在 3 天后被删除。