Java问答知识总结篇-基础知识
Java问答知识总结篇-JVM
Java问答知识总结篇-多线程&并发编程
Java问答知识总结篇-网络基础
Java问答知识总结篇-Spring
Java问答知识总结篇-Spring Boot
Java问答知识总结篇-Mybatis
Java问答知识总结篇-MySQL
Java问答知识总结篇-Redis
Java问答知识总结篇-MQ
Java问答知识总结篇-Nginx
Java问答知识总结篇-分布式
Java问答知识总结篇-Spring Cloud
Java问答知识总结篇-Dubbo
Java问答知识总结篇-Zookeeper
Java问答知识总结篇-ElasticSearch
Java问答知识总结篇-Netty
Java问答知识总结篇-场景分析题
ZooKeeper 有哪些应用场景
数据发布与订阅
发布与订阅即所谓的配置管理,顾名思义就是将数据发布到ZooKeeper节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,地址列表等就非常适合使用。
数据发布/订阅的一个常见的场景是配置中心,发布者把数据发布到 ZooKeeper 的一个或一系列的节点上,供订阅者进行数据订阅,达到动态获取数据的目的。
配置信息一般有几个特点:
- 数据量小的KV
- 数据内容在运行时会发生动态变化
- 集群机器共享,配置一致
ZooKeeper 采用的是推拉结合的方式。
- 推: 服务端会推给注册了监控节点的客户端 Wathcer 事件通知;
- 拉: 客户端获得通知后,然后主动到服务端拉取最新的数据。
命名服务
作为分布式命名服务,命名服务是指通过指定的名字来获取资源或者服务的地址,利用ZooKeeper创建一个全局的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。
统一命名服务的命名结构图如下所示:
- 在分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务
- 类似于域名与IP之间对应关系,IP不容易记住,而域名容易记住
- 通过名称来获取资源或服务的地址,提供者等信息
- 按照层次结构组织服务/应用名称
可将服务名称以及地址信息写到ZooKeeper上,客户端通过ZooKeeper获取可用服务列类。
配置管理
程序分布式的部署在不同的机器上,将程序的配置信息放在ZooKeeper的znode下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容,利用watch通知给各个客户端 从而更改配置。
ZooKeeper配置管理结构图如下所示:
- 分布式环境下,配置文件管理和同步是一个常见问题
- 一个集群中,所有节点的配置信息是一致的,比如 Hadoop 集群
- 对配置文件修改后,希望能够快速同步到各个节点上
- 配置管理可交由ZooKeeper实现
- 可将配置信息写入ZooKeeper上的一个Znode
- 各个节点监听这个Znode
- 一旦Znode中的数据被修改,ZooKeeper将通知各个节点
集群管理
所谓集群管理就是:是否有机器退出和加入、选举master。
集群管理主要指集群监控和集群控制两个方面。前者侧重于集群运行时的状态的收集,后者则是对集群进行操作与控制。开发和运维中,面对集群,经常有如下需求:
- 希望知道集群中究竟有多少机器在工作
- 对集群中的每台机器的运行时状态进行数据收集
- 对集群中机器进行上下线的操作
集群管理结构图如下所示:
- 分布式环境中,实时掌握每个节点的状态是必要的,可根据节点实时状态做出一些调整
- 可交由ZooKeeper实现
- 可将节点信息写入ZooKeeper上的一个Znode
- 监听这个Znode可获取它的实时状态变化
- 典型应用
Hbase中Master状态监控与选举:利用ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客户端请求能够创建成功。
分布式通知与协调
- 分布式环境中,经常存在一个服务需要知道它所管理的子服务的状态
- NameNode需知道各个Datanode的状态
- JobTracker需知道各个TaskTracker的状态
- 心跳检测机制可通过ZooKeeper来实现
- 信息推送可由ZooKeeper来实现,ZooKeeper相当于一个发布/订阅系统
分布式锁
处于不同节点上不同的服务,它们可能需要顺序的访问一些资源,这里需要一把分布式的锁。
分布式锁具有以下特性:写锁、读锁、时序锁。
写锁: 在zk上创建的一个临时的无编号的节点。由于是无序编号,在创建时不会自动编号,导致只能客户端有一个客户端得到锁,然后进行写入。
读锁: 在zk上创建一个临时的有编号的节点,这样即使下次有客户端加入是同时创建相同的节点时,他也会自动编号,也可以获得锁对象,然后对其进行读取。
时序锁: 在zk上创建的一个临时的有编号的节点根据编号的大小控制锁。
分布式队列
分布式队列分为两种:
- 当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
- 一个job由多个task组成,只有所有任务完成后,job才运行完成
- 可为job创建一个/job目录,然后在该目录下,为每个完成的task创建一个临时的Znode,一旦临时节点数目达到task总数,则表明job运行完成
- 队列按照FIFO方式进行入队和出队操作,例如实现生产者和消费者模型。
说说Zookeeper的工作原理
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。
Zab协议有两种模式,它们 分别是恢复模式(选主)和广播模式(同步)。
Zab协议 的全称是 Zookeeper Atomic Broadcast(Zookeeper原子广播)。Zookeeper 是通过Zab 协议来保证分布式事务的最终一致性。Zab协议要求每个 Leader 都要经历三个阶段:发现,同步,广播。
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加 上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一 个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
epoch:可以理解为皇帝的年号,当新的皇帝leader产生后,将有一个新的epoch年号。
每个Server在工作过程中有三种状态:
- LOOKING:当前Server不知道leader是谁,正在搜寻
- LEADING:当前Server即为选举出来的leader
- FOLLOWING:leader已经选举出来,当前Server与之同步
Zookeeper 集群中有哪些角色
在一个集群中,最少需要 3 台。或者保证 2N + 1 台,即奇数。为什么保证奇数?主要是为了选举算法。
Zookeeper 集群中是怎样选举leader的
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。leader选举是保证分布式数据一致性的关键。
出现选举主要是两种场景:初始化、leader不可用。
当zk集群中的一台服务器出现以下两种情况之一时,就会开始leader选举。
- 服务器初始化启动。
- 服务器运行期间无法和leader保持连接。
而当一台机器进入leader选举流程时,当前集群也可能处于以下两种状态。 - 集群中本来就已经存在一个leader。
- 集群中确实不存在leader。
首先第一种情况,通常是集群中某一台机器启动比较晚,在它启动之前,集群已经正常工作,即已经存在一台leader服务器。当该机器试图去选举leader时,会被告知当前服务器的leader信息,它仅仅需要和leader机器建立连接,并进行状态同步即可。
重点是leader不可用了,此时的选主制度。
投票信息中包含两个最基本的信息。
sid :即server id,用来标识该机器在集群中的机器序号。
zxid :即zookeeper事务id号。
ZooKeeper状态的每一次改变, 都对应着一个递增的Transaction id,,该id称为zxid.,由于zxid的递增性质, 如果zxid1小于zxid2,,那么zxid1肯定先于zxid2发生。创建任意节点,或者更新任意节点的数据, 或者删除任意节点,都会导致Zookeeper状态发生改变,从而导致zxid的值增加。
以(sid,zxid)的形式来标识一次投票信息。
例如:如果当前服务器要推举sid为1,zxid为8的服务器成为leader,那么投票信息可以表示为(1,8)。
集群中的每台机器发出自己的投票后,也会接受来自集群中其他机器的投票。每台机器都会根据一定的规则,来处理收到的其他机器的投票,以此来决定是否需要变更自己的投票。
规则如下 :
- 初始阶段,都会给自己投票。
- 当接收到来自其他服务器的投票时,都需要将别人的投票和自己的投票进行pk,规则如下:
优先检查zxid。zxid比较大的服务器优先作为leader。如果zxid相同的话,就比较sid,sid比较大的服务器作为leader。
所有服务启动时候的选举流程:三台服务器 server1、server2、server3。
- server1 启动,一台机器不会选举。
- server2 启动,server1 和 server2 的状态改为 looking,广播投票。
- server3 启动,状态改为 looking,加入广播投票。
- 初识状态,互不认识,大家都认为自己是王者,投票也投自己为 Leader。
- 投票信息说明,票信息本来为五元组,这里为了逻辑清晰,简化下表达。
初始 zxid = 0,sid 是每个节点的名字,这个 sid 在 zoo.cfg 中配置,不会重复。
节点 | sid |
---|---|
Server1 | 1 |
Server2 | 2 |
Server3 | 3 |
- 初始 zxid=0,server1 投票(1,0),server2 投票(2,0),server3 投票(3,0)
- server1 收到 投票(2,0)时,会先验证投票的合法性,然后自己的票进行 pk,pk 的逻辑是先比较 zxid,server1(zxid)=server2(zxid)=0,zxid 相等再比较 sid,server1(sid)<server2(sid),pk 结果为 server2 的投票获胜。server1 更新自己的投票为 (2,0),server1重新投票。
- TODO 这里最终是 2 还是 3,需要做实验确定。
- server2 收到 server1 投票,会先验证投票的合法性,然后 pk,自己的票获胜,server 不用更新自己的票,pk 后,重新在发送一次投票。
- 统计投票,pk 后会统计投票,如果半数以上的节点投出相同的票,确定选出了 Leader。
选举结束,被选中节点的状态由 LOOKING 变成 LEADING,其他参加选举的节点由 LOOKING变成 FOLLOWING。如果有 Observer 节点,如果 Observer 不参与选举,所以选举前后它的状态一直是 OBSERVING,没有变化。
简单地说
开始投票 -> 节点状态变成 LOOKING -> 每个节点选自己-> 收到票进行 PK -> sid 大的获胜 -> 更新选票 -> 再次投票 -> 统计选票,选票过半数选举结果 -> 节点状态更新为自己的角色状态。
ZooKeeper 分布式锁怎么实现的
如果有客户端1、客户端2等N个客户端争抢一个 Zookeeper 分布式锁。大致如下:
- 大家都是上来直接创建一个锁节点下的一个接一个的临时有序节点
- 如果自己不是第一个节点,就对自己上一个节点加监听器
- 只要上一个节点释放锁,自己就排到前面去了,相当于是一个排队机制
而且用临时顺序节点的另外一个用意就是,如果某个客户端创建临时顺序节点之后,不小心自己宕机了也没关系, Zookeeper 感知到那个客户端宕机,会自动删除对应的临时顺序节点,相当于自动释放锁,或者是自动取消自己的排队。
本地锁,可以用 JDK 实现,但是分布式锁就必须要用到分布式的组件。比如 ZooKeeper、Redis。网上代码一大段,面试一般也不要写,我这说一些关键点。
几个需要注意的地方如下:
- 死锁问题:锁不能因为意外就变成死锁,所以要用 ZK 的临时节点,客户端连接失效了,锁就自动释放了。
- 锁等待问题:锁有排队的需求,所以要 ZK 的顺序节点。
- 锁管理问题:一个使用使用释放了锁,需要通知其他使用者,所以需要用到监听。
监听的羊群效应:比如有 1000 个锁竞争者,锁释放了,1000 个竞争者就得到了通知,然后判断,最终序号最小的那个拿到了锁。其它 999 个竞争者重新注册监听。这就是羊群效应,出点事,就会惊动整个羊群。应该每个竞争者只监听自己前面的那个节点。比如 2 号释放了锁,那么只有 3 号得到了通知。
为什么Zookeeper集群的数目,一般为奇数个
首先需要明确zookeeper选举的规则:leader选举,要求 可用节点数量 > 总节点数量/2 。
比如:标记一个写是否成功是要在超过一半节点发送写请求成功时才认为有效。同样,Zookeeper选择领导者节点也是在超过一半节点同意时才有效。最后,Zookeeper是否正常是要根据是否超过一半的节点正常才算正常。这是基于CAP的一致性原理。
zookeeper有这样一个特性:集群中只要有过半的机器是正常工作的,那么整个集群对外就是可用的。
也就是说如果有2个zookeeper,那么只要有1个死了zookeeper就不能用了,因为1没有过半,所以2个zookeeper的死亡容忍度为0;
同理,要是有3个zookeeper,一个死了,还剩下2个正常的,过半了,所以3个zookeeper的容忍度为1;
同理:
- 2->0;两个zookeeper,最多0个zookeeper可以不可用
- 3->1;三个zookeeper,最多1个zookeeper可以不可用
- 4->1;四个zookeeper,最多1个zookeeper可以不可用
- 5->2;五个zookeeper,最多2个zookeeper可以不可用
- 6->2;两个zookeeper,最多0个zookeeper可以不可用
会发现一个规律,2n和2n-1的容忍度是一样的,都是n-1,所以为了更加高效,何必增加那一个不必要的zookeeper呢。
zookeeper的选举策略也是需要半数以上的节点同意才能当选leader,如果是偶数节点可能导致票数相同的情况。
讲解一下 ZooKeeper 的持久化机制
什么是持久化
- 数据,存到磁盘或者文件当中。
- 机器重启后,数据不会丢失。内存 -> 磁盘的映射,和序列化有些像。
ZooKeeper 的持久化
- SnapShot 快照,记录内存中的全量数据
- TxnLog 增量事务日志,记录每一条增删改记录(查不是事务日志,不会引起数据变化)
为什么持久化这么麻烦,一个不可用吗?
快照的缺点,文件太大,而且快照文件不会是最新的数据。 增量事务日志的缺点,运行时间长了,日志太多了,加载太慢。二者结合最好。
快照模式:
- 将 ZooKeeper 内存中以 DataTree 数据结构存储的数据定期存储到磁盘中。
- 由于快照文件是定期对数据的全量备份,所以快照文件中数据通常不是最新的。