继之前的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天后被删除。