Java ZooKeeper-RocketMQ 面试题

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)

总结

都已经看到这里啦,赶紧收藏起来,祝您工作顺心,生活愉快!

相关推荐
Lee川21 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i1 天前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
孟陬1 天前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端
想用offer打牌1 天前
一站式了解四种限流算法
java·后端·go
绝无仅有1 天前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有1 天前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
华仔啊1 天前
Java 开发千万别给布尔变量加 is 前缀!很容易背锅
java
AAA梅狸猫1 天前
Looper.loop() 循环机制
面试
AAA梅狸猫1 天前
Handler基本概念
面试