Java ZooKeeper-RocketMQ 面试题
前言
最新的 Java 面试题,技术栈涉及 Java 基础、集合、多线程、Mysql、分布式、Spring全家桶、MyBatis、Dubbo、缓存、消息队列、Linux…等等,会持续更新。
如果对老铁有帮助,帮忙免费点个赞,谢谢你的发财手!
1、谈谈你对ZooKeeper的理解 ?
ZooKeeper 是一个开源的分布式协调服务,为分布式系统提高了一系列的服务,提供的服务包括了:服务注册与订阅、统一命名服务、集群管理、分布式锁和分布式通知等。
Zookeeper提供了三个核心功能,分别是:文件系统、监听机制和集群管理
- 1、文件系统 Zookeeper存储数据的结构,类似于一个文件系统,每个节点(znode)都是类似于K-V的结构,每个节点的名字相当于key,每个节点保存的数据,相当于value。
- 2、监听机制 客户端先向ZooKeeper服务端的某个节点注册一个 Watcher 监听,当监听的数据状态发生变化时,服务端会向指定客户端发送一个事件通知,客户端收到事件以后,调用对应的回调方法,完成事件变更的通知。
- 3、集群管理 Zookeeper提供了CP的模型,来保证集群中的每个节点的数据一致性。 zookeeper本身是一个集群结构,它有3种角色: leader领导者:处理所有的事务请求(写请求),也可以处理读请求,集群中只有一个leader; follower追随者:只能处理读请求,参与leader选举 observer观察者:只能处理读请求,不参与leader选举,作用是为了提升集群的读性能
2、Zookeeper的工作原理(Zab协议)
- Zookeeper的工作原理核心是原子广播机制,这个机制保证了各个节点之间的数据一致性,实现这个机制的协议叫做Zab协议。 Zab协议有两种模式,分别是恢复模式(选主)和广播模式(同步),当服务启动或者 Leader 服务器宕机,Zab就进入了恢复模式,此时不对外提供服务。
- 首先选举出新的 Leader 服务器,然后与其他的Follower 服务器进行数据同步,当集群中超过半数机器完成数据同步之后,退出恢复模式进入广播模式,然后就可以接收客户端的事务请求了。
3、谈谈你对分布式锁的理解,以及分布式锁的实现?
- 分布式锁,是一种跨进程的跨机器节点的互斥锁,它可以保证同一时刻只能有一个线程去访问共享资源。 目前实现分布式锁最常用的中间件是 Redis 和 Zookeeper:
- Redis:利用它提供的 SETNX 命令,如果设置key返回1,说明获取锁成功,返回0获取锁失败。然后还可以通过 EX参数来设置key的过期时间,从而避免死锁问题。 但也可能存在一些极端情况,比如锁过期了,但是业务逻辑还没处理完。这种极端情况可以使用Redisson 客户端来实现,Redisson 中有一个 watchdog 的机制,翻译过来就是看门狗,它是基于Netty下面的一个时间轮的实现类来实现。(getLock、lock、unlock)
- Zookeeper:利用它提供的有序节点,当线程创建节点后,如果该节点是当前目录下所有节点序号最小的节点,则认为获取锁成功。如果不是最小的节点,则对该节点序号的前一个节点添加一个监听事件,当监听的节点释放锁之后,触发回调通知,从而再次去尝试抢占锁。 总的来说,这两种方案都有各自的优缺点: Redis它获取锁的方式简单粗暴,如果获取不到锁,会不断尝试获取锁,消耗性能比较大,但是实现难度比较低。 Zookeeper如果获取不到锁,只需要添加一个监听器就可以了,消耗性能比较小,但是实现难度比较高。
4、 zookeeper 是如何保证事务的顺序一致性的?
- zookeeper采用了全局递增的事务Id 来标识,所有的 proposal(提议)在被提出的时候都会加上 zxid,zxid实际上是一个64位的数字,高32位是epoch(时期; 纪元; 世; 新时代)用来标识leader是否发生改变,如果有新的leader产生出来,epoch会自增,然后低32位用来递增计数。 当产生新的提议时,zookeeper会采用数据库的二阶段提交,首先会向其他的节点发出事务执行请求,如果超过半数的机器都能执行,那么就会提交事务。
5、 zookeeper主从同步机制:
- 1、当leader 接受到消息请求后,会给消息加上一个全局的事务Id (zxid);
- 2、leader将带有zxid的消息作为一个提案(proposal)分发给所有的follower;
- 3、当follower接受到提案后,先把提案写到磁盘,写入成功以后再向leader回复一个ack;
- 4、当leader 接受到半数以上ack,就会向follower发送提交commit命令,同时自己也执行;
- 5、当follower接受到commit命令以后,就会提交该消息,从而实现数据同步。
6、分布式集群中为什么会有 Master?
在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,其他的机器可以共享这个结果,这样可以大大减少重复计算,提高性能。
7、 zk 节点宕机如何处理?
- 1、如果是一个 Follower 宕机,只要超过半数的节点正常,集群就能正常提供服务,否则集群就会失效;
- 2、如果是一个 Leader 宕机,Zookeeper会进入恢复模式,重新选举出新的 Leader。 Zookeeper本身也是集群,推荐不少于 3 个服务器,而且最好奇数个。
8、说几个 zookeeper 常用的命令?
常用命令:ls get set create delete 等。
9、ZK 如何投票实现Leader选举?
如果 Leader 节点宕机了,为了保证集群继续提供服务,Zookeeper 需要从剩下的 Follower 节点里面去选举一个新的节点作为 Leader,也就是所谓的 Leader 选举。具体的实现是:
- 1、每一个节点都会向集群里面的其他节点发送一个票据 Vote,这个票据包括三个属性:epoch逻辑时钟,zxid事务id,myid服务器id;然后第一轮投票都会投给自己。
- 2、每个节点把收到的票据和自己节点的票据做比较,根据 epoch、zxid、myid 的顺序逐一比较,以值最大的一方获胜,比较结束以后就把票投给获胜的节点;
- 3、通过多轮投票以后,以少数服从多数的方式,最终获得票数最多的节点成为Leader。 到这里,leader选举就结束啦。 其中epoch,逻辑时钟,用来标识当前票据是否过期;zxid,事务id,用来表示当前节点最新存储的数据的事务编号; myid,服务器id,在 myid 文件里面填写的;
MQ中间件
10、什么是 RocketMq?
- RocketMq是一个基于 AMQP 高级消息队列协议的中间件,接受并转发消息。它有4个核心组件,分别是:
- producer生产者:负责生产和发送消息到 Broker;
- Exchange交换机:按照一定的规则将消息路由转发到某个队列,对消息进行过虑;
- Queue队列:用来存储消息,并把消息转发给指定的消费者;
- consumer消费者:负责从 Broker 中获取消息,并进行相应处理; 它的工作原理是生产者把消息发送到Exchange交换机上。Exchange交换机把收到的消息根据路由规则,转发给绑定的queue队列,队列再把消息投递给订阅了这个队列的消费者,从而完成消息的异步通讯。
11、什么是消息队列?
消息队列 Message Queue,简称 MQ。
消息队列有很多使用场景,比较常见的有3个:解耦、异步、削峰。
- 1、应用解耦:把相关联的系统进行职责解耦,比如:生成订单会调用仓库管理系统或积分系统。
- 2、异步处理:不需要立即处理消息,提高系统的性能,比如:用户注册发送验证码、下单短信通知、发送优惠券等。
- 3、流量削峰:能够有效的顶住瞬间高并发,防止服务器承受不住导致崩溃,比如秒杀、限时抢购优惠券等。 比如吞吐量低的中小型公司,一般用 ActiveMQ、RabbitMQ 较为合适, 大数据、吞吐量高的大型公司一般选用 Kafka 和 RocketMQ。
12、RocketMq的路由类型和发送消息的方式?
它的工作原理是生产者把消息发送到Exchange交换机上,Exchange交换机把收到的消息根据路由规则,转发给绑定的queue队列,最后再把消息投递给订阅了这个队列的消费者,从而完成消息的异步通讯。
在RabbitMQ中,交换机常见的有3种类型,分别是Fanout、Direct 、Topic:
- Fanout(扇出交换机):类似于广播机制,将消息转发给到所有绑定的队列上,与routingKey(路由键)无关;
- Direct(直连交换机):完整匹配方式,也就是Routing key和Binding Key完全一致,才把消息发给该队列;
- Topic(主题交换机):就是Routing Key加了通配符,符合匹配规则的Queue队列都会收到这个消息,#:代表0个或多个单词,*:代表一个单词。
13、死信消息的生命周期?
- 1、消费者消费业务消息时发生异常,就会返回nck或者reject操作;
- 2、那么这些消息就会被投递到死信交换机中;
- 3、死信交换机将消息发送到相应的死信队列;
- 4、死信队列可以定时重新投递到Broker中,也可以由死信消费者消费。
14、如何保证消息的顺序性?
这个问题是由于不同的消息都发送到了同一个 queue队列中,而这个queue队列又被多个消费者消费。
解决这个问题,我们可以给 RabbitMQ 创建多个 queue队列,每个队列对应一个消费者。比如生产者发送消息的时候,同一个订单号的消息发送到同一个 queue队列中,由于同一个 queue队列的消息是有序的,那么同一个订单号的消息就只会被一个消费者顺序消费,从而保证了消息的顺序性。
15、如何防止消息丢失?
- 1)、使用事务消息
- 2)、使用消息确认机制 消息丢失的场景有下面几种:
- 1)、生产者发送消息到MQ的过程中丢失 解决方法: 在生产者端开启comfirm 确认模式,每个消息会分配一个唯一的 id,如果MQ写入了内存中, 就会返回一个ack,告诉你说这个消息ok了,如果MQ没有写入这个消息,会回调一个nack接口,告诉你这个消息失败了,你可以进行重试或抛弃此条消息。
- 2)、MQ收到消息,写入到内存中,还没消费,服务挂掉了,数据都会丢失 解决方法:将消息持久化到磁盘,有两个步骤: 第1步:把该消息设置为持久化,即deliveryMode设置为2,第3个参数设置如下: channel.basicPublish(“exchange”, “routingKey”, MessageProperties.PERSISTENT_TEXT_PLAIN, " message".getBytes()); 第2步:把该queue队列设置为持久化,即durable=true,第2个参数设置为true, channel.queueDeclare(QUEUE_NAME, true, false, false, null);
- 3)、消费者刚拿到消息,还没处理,服务挂掉了,MQ又以为消费者处理完了 解决方式:首先关闭 RabbitMQ 的自动 ack,然后消费者在处理完消息之后,再手动返回ack。这样可以保证,如果你还没处理完,就不会返回ack,那队列就不会删除该消息。 (关闭 RabbitMQ 的自动 ack,即autoAck=false,第2个参数设置为false channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel){});)
16、如何保证消息不被重复消费?
我们为了消息的可靠性,采用了手动ack机制;如果消费者在处理完一个消息之后,还没有手动调用ack,服务就挂了,MQ以为你还还没处理,就把这个消息投递给其他的消费者,就导致了消息被重复消费。
我觉得这个问题可以分为2种场景来对待:
- 第1种幂等性场景:就是说消息重复消费和消费一次产生的影响是一样的,可以不用处理,比如Redis的set操作,它是天然幂等性的;
- 第2种非幂等性场景:把消息唯一id保存到 mysql 或者 redis 里面,在处理消息之前先去 mysql 或者 redis 里面,判断一下是否已经消费过了。
17、MQ处理消息失败了怎么办?
一般生产环境中,都会在使用MQ的时候设计两个队列:一个是核心业务队列,一个是死信队列。核心业务队列,就是比如专门用来给订单系统发送订单消息的,然后另外一个死信队列就是用来处理异常情况的。
18、消息基于什么传输?
RabbitMQ 使用channel通道的方式来传输数据,通道是建立在真实的 TCP 连接内的虚拟连接,且通道数量没有限制,大大提升了MQ的处理性能。
19、RocketMQ 底层原理?
RocketMQ 架构主要包含以下四个部分:
- 1、NameServer:是一个简单的Topic路由注册中心,支持Broker的注册与发现,主要有两个功能:Broker管理(提供心跳检测机制)和路由信息管理(保存Broker的路由信息和用于客户端查询的队列信息);
- 2、BrokerServer:RocketMQ 的核心组件,主要负责消息的 1、存储:CommitLog(存储消息的文件); 2、投递:ConsumeQueue(消息消费队列);
- 3.查询:IndexFile(索引文件);
- 4.以及维护消费者的Topic订阅信息和保证服务高可用; 1、Producer:消息生产者,往Broker发送指定Topic的消息。 2、Consumer:消息消费者,主动拉取消息来消费,支持集群方式(同一Topic下的一条消息只会发送到同一消费组中的一个消费者)和广播方式(所有的消息会广播发送到所有的消费者)。
20、RocketMQ为什么速度快, 吞吐量?
因为使⽤了顺序存储、页缓存(Page Cache)和异步刷盘;我们在写⼊commitlog的时候是顺序写⼊的,这样⽐随机写⼊的性能就会⾼很多,而且它不是直接写⼊磁盘,⽽是先写⼊页缓存,开启一个异步线程通过零拷贝机制,定时的将缓存中的数据刷到磁盘中,从而保证消息的快速读写。
21、什么是零拷贝?
- 传统⽂件复制方式:需要对⽂件在内存中进⾏四次复制(内核态到用户态的来回复制)。
- 零拷⻉:就是减少内核态到用户态的来回复制,它是通过内存映射的机制,直接把内核态里的数据映射到用户态,对文件的操作转化为对内存地址的操作; 通常有两种⽅式,mmap(RocketMq)和sendfile(kafka)。
22、死信队列?
当一条消息消费失败时,经过多次重试依然失败,为了保证消息数据不丢失,需要将消息投入到死信队列中。
死信的来源:
- 1、消息 TTL(存活时间)过期
- 2、队列满了,无法再添加数据到队列中
- 3、消息被拒绝(消费方拒绝应答:basic.reject 或 basic.nack)并且不放回队列中( requeue=false)
总结
都已经看到这里啦,赶紧收藏起来,祝您工作顺心,生活愉快!
版权归原作者 旺仔爱Java 所有, 如有侵权,请联系我们删除。