ZooKeeper原理、应用与面经总结
一、原理篇
为什么需要ZooKeeper?
现在分布式集群已经成为后台系统的主流部署方式,而集群部署和分布式的环境提供了强大服务的同事也增加了问题的复杂性,比如集群节点在相互通信时,A节点向B节点发送消息。A节点如果想知道消息是否发送成功,只能由B节点告诉A节点。那么如果B节点关机或者由于其他的原因脱离集群网络,问题就出现了。A节点不断的向B发送消息,并且无法获得B的响应。B也没有办法通知A节点已经离线或者关机。集群中其他的节点完全不知道B发生了什么情况,还在不断的向B发送消息。这时,你的整个集群就发生了部分失败的故障。Zookeeper不能让部分失败的问题彻底消失,但是它提供了一些工具能够让你的分布式应用安全合理的处理部分失败的问题。
引自——【Zookeeper 入门】
1.1分布式系统理论
Zookeeper是Hadoop分布式调度服务,用来构建分布式应用系统。构建一个分布式应用是一个很复杂的事情,一个分布式系统会有以下几大特性和需要解决的问题:
- 分布性,项目会根据不同的功能拆分成不同的模块,部署成不同的服务放在不同的机器上。
- 对等性,服务备份成对等的几个副本,相互之间都是对等的。
- 并发性,多进程、多JVM
- 缺乏全局时钟,节点有各自独立的时钟,如果有并发事务则需要有一个居中协调的调度器
- 随机故障,可能来自于节点异常(单点故障)、通信故障、网络延时、网络分区
分布式系统中绕不开最重要CAP原则,又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼,最多只能同时满足其中的两项。
之前在博客《分布式系统的事务探讨》中介绍过,简而言之就是:一致性是多个副本的结果一致性(广义上则可以延伸到分布式事务的转账问题),可用性则是请求在有限的时间内得到响应,分区容错性就是在网络分区故障的时候、仍可对外提供满足Consistency和Availability的服务。
在CAP理论的基础上,又进化出了BASE理论,P是一定需要的,剩下要做的就是在C和A之间进行平衡。BASE理论:即使无法做到强一致性,但分布式系统可以根据自己的业务特点,采用适当的方式来使系统达到最终的一致性
- Basically Avaliable 基本可用。既然故障不可避免但要保证基本可用,则允许服务容错、降级、熔断等措施
- Soft state 软状态。允许系统中的数据存在中间状态,允许延时,比如请求排队。
- Eventually consistent 最终一致性。所有的数据在经过一段时间的数据同步后,最终能够达到一个一致的状态。
1.2 ZooKeeper理论
ZooKeeper是一种分布式协调服务,用于管理大型主机。ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集。在分布式环境中协调和管理服务是一个复杂的过程,而ZooKeeper通过其简单的架构和API解决了这个问题,例如ZK提供了数据发布订阅、负载均衡、命名服务、Master选举、集群管理、配置管理、分布式队列和分布式锁等功能。
Zookeeper的数据保持在内存中,这就意味着它可以实现高吞吐量和低延迟的数据。另外,Zookeeper自己在处理协调的时候要复制多个主机,Zookeeper服务的组成部分必须彼此都知道彼此,它们维持了一个内存状态影像,连同事务日志和快照在一个持久化的存储中。只要大多数的服务器是可用的,Zookeeper服务就是可用的。
ZK服务1.2.1 ZK的重要概念总结
特点总结:1、集群部署,容错性高。2、内存设计,高吞吐低延迟。3、特别适合读场景,性能好。4、临时节点设计,提供监听服务。
- ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。
为了保证高可用,最好是以**集群形态来部署,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。 - ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟(但是内存限制了能够存储的容量不太大,此限制也是保持znode中存储的数据量较小的进一步原因)。
- ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
- ZooKeeper有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。
- ZooKeeper 底层其实只提供了两个功能:①管理(存储、读取)用户程序提交的数据;②为用户程序提交数据节点监听服务。
(1).会话
在 ZooKeeper 中,一个客户端连接是指客户端和服务器之间的一个 TCP 长连接。客户端启动的时候,首先会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也开始了。通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向Zookeeper服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的Watch事件通知。ZK会话在整个运行期间的生命周期中,会在不同的会话状态之间切换,这些状态包括:CONNECTING、CONNECTED、RECONNECTING、RECONNECTED、CLOSE
(2).ZK数据模型
ZooKeeper包含一个树形的数据模型——Znode。一个znode中包含了存储的数据和 ACL(Access Control List)。ZooKeeper的设计适合存储少量的数据(因为保存在内存中),并不适合存储大量数据,所以znode的存储限制最大不超过1M。
ZK数据的访问被定义成原子性的。即一个客户端访问一个znode时,不会只得到一部分数据;客户端访问数据要么获得全部数据,要么读取失败,什么也得不到。相似的,写操作时,要么写入全部数据,要么写入失败,什么也写不进去。ZooKeeper能够保证写操作只有两个结果,成功和失败。绝对不会出现只写入了一部分数据的情况。(区别于数据流式读取的HDFS)
ZooKeeper通过path来定位znode,使用斜杠'/'
来表示路径,这个和Unix系统定位文件一样。但是,znode的路径只能使用绝对路径,而不能像Unix系统一样使用相对路径,即Zookeeper不能识别../和./这样的路径。
(3).节点Znode
-
1、Znode有两种类型:
短暂(ephemeral)(create -e /app1/test1 “test1” 客户端断开连接zk删除ephemeral类型节点) 生命周期和客户端会话绑定,旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。
持久(persistent) (create -s /app1/test2 “test2” 客户端断开连接zk不删除persistent类型节点) -
2、Znode有四种形式的目录节点(默认是persistent )
PERSISTENT
PERSISTENT_SEQUENTIAL
EPHEMERAL
EPHEMERAL_SEQUENTIAL -
3、创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护;同时,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序
(4).Watcher
Watcher(事件监听器),是Zookeeper中的一个很重要的特性。Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是Zookeeper实现分布式协调服务的重要特性。
但是请注意watch机制官方说明:一个Watch事件是一个一次性的触发器,当被设置了Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端,以便通知它们。不支持用持久Watcher的原因很简单,如果Watcher的注册是持久的,那么必然导致服务端的每次数据更新都会通知到客户端——这在数据变更非常频繁且监听客户端特别多的场景下,ZooKeeper无法保证性能。
(5).ACL权限控制
ACL机制,表示为scheme:id:permissions,第一个字段表示授权策略,第二个id表示用户,permissions表示相关权限(如只读,读写,管理等)。这个在实际种可能用到较少,了解一下即可。
1.2.3 来自ZK的保证
- 顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
- 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,即要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
- 单一系统映像 : 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
- 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
1.3 ZooKeeper集群和Zab协议
生产中ZK是以集群的形式进行部署的,分为三类角色,Leader是集群工作机制中的核心,事务请求的唯一调度和处理者,保证集群事务处理的顺序性,集群内部个服务器的调度者(管理follower,数据同步)。Follower是集群工作机制中的跟随者处理非事务请求,转发事务请求给Leader参与事务请求proposal投票,参与leader选举投票。还有Observer 观察者,和follower功能相同,但不参与任何形式投票。
ZK集群集群工作流程:Zookeeper 客户端会随机连接到 Zookeeper 集群的一个节点,如果是读请求,就直接从当前节点中读取数据;如果是写请求,那么节点就会向 leader 提交事务,leader 会广播事务,只要有超过半数节点写入成功,该写请求就会被提交(类 2PC 协议)。但是考虑以下特殊情况,ZK集群该如何工作呢?
- 主从架构下,leader 崩溃,数据一致性怎么保证?
- 选举 leader 的时候,整个集群无法处理写请求的,如何快速进行 leader 选举?
这个时候需要Zab协议出场了:
ZAB 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
Zab协议核心是在整个zookeeper集群中只有一个节点——leader将所有客户端的写操作转化为事务( 提议proposal )。leader节点在数据写完之后,将向所有的follower节点发送数据广播请求(数据复制),等所有的follower节点的反馈,在zab协议中,只要超过半数follower节点反馈ok,leader节点会向所有follower服务器发送commit消息,将leader节点上的数据同步到follower节点之上。
Zab周期:正常情况下当客户端对zk有写的数据请求时,leader节点会把数据同步到follower节点,这个过程其实就是消息的广播模式;在新启动的时候,或者leader节点奔溃的时候会要选举新的leader,选好新的leader之后会进行一次数据同步操作,整个过程就是奔溃恢复。整个过程其实可以分成两部分。第一的消息广播模式,第二是崩溃恢复模式。
Zab周期-
消息广播:Zab协议中的leader等待follower的ack反馈,只要半数以上的follower成功反馈就好,不需要收到全部的follower反馈。zookeeper中数据副本的同步方式与2PC提交类似,但二阶段提交的要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要么全部成功要么全部失败。二阶段提交会产生严重阻塞问题,但Paxos和Zab只需要半数以上就可以确定。为了进一步防止阻塞,leader服务器与每个follower之间都有一个单独的队列进行收发消息,使用队列消息可以做到异步解耦,leader和follower之间只要往队列中发送了消息即可,如果使用同步方式容易引起阻塞,性能上要下降很多。
-
奔溃恢复(也是快速选举算法和流程实现):1、每个Server会发出一个投票,第一次都是投自己。投票信息:(myid,ZXID)。2、收集来自各个服务器的投票。3、处理投票并重新投票,处理逻辑:优先比较ZXID,然后比较myid。4、统计投票,只要超过半数的机器接收到同样的投票信息,就可以确定leader。5、改变服务器状态
因此上面两个问题中,Leader挂掉以后的数据一致性的问题,答案可以这么来总结:
- 主从架构下,leader 崩溃,数据一致性怎么保证?leader 崩溃之后,集群会选出新的 leader,然后就会进入恢复阶段,新的 leader 具有所有已经提交的提议,因此它会保证让 followers 同步已提交的提议,丢弃未提交的提议(以 leader 的记录为准),这就保证了整个集群的数据一致性。
- 选举 leader 的时候,整个集群无法处理写请求的,如何快速进行 leader 选举?这是通过 Fast Leader Election 实现的,leader 的选举只需要超过半数的节点投票即可,这样不需要等待所有节点的选票,能够尽早选出 leader。
二、ZooKeeper应用
Zookeeper客户端提供了基本的操作,比如,创建会话、创建节点、读取节点、更新数据、删除节点和检查节点是否存在等。但对于开发人员来说,Zookeeper提供的基本操纵还是有一些不足之处,比如Session超时之后没有实现重连机制、异常处理繁琐、Watcher是一次性等等。但是好在社区提供了两款十分强大的ZK客户端:ZKClient
和Curator
。
- ZkClient是一个开源客户端,在Zookeeper原生API接口的基础上进行了包装,更便于开发人员使用。内部实现了Session超时重连,Watcher反复注册等功能。像dubbo等框架对其也进行了集成使用。
- Curator是Netflix公司开源的一套Zookeeper客户端框架,和ZkClient一样,解决了非常底层的细节开发工作,包括连接重连、反复注册Watcher和NodeExistsException异常等。目前已经成为Apache的顶级项目。另外还提供了一套易用性和可读性更强的Fluent风格的客户端API框架。除此之外,Curator中还提供了Zookeeper各种应用场景(Recipe,如共享锁服务、Master选举机制和分布式计算器等)的抽象封装。
这两个客户端,熟练掌握其中的一种即可!下面以ZKClient为例,创建会话、创建节点、读取数据、更新数据、删除节点、检测节点等方面介绍ZkClient的使用。
会话创建、节点创建、获取子节点、删除节点
public class ZkClientTest {
public static void main(String[] args) throws IOException,InterruptedException {
//创建会话
ZkClient zkClient = new ZkClient("127.0.0.1:2181", 5000);
String path = "/zk-book";
//监测子节点变化
zkClient.subscribeChildChanges(path, new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChild)
throws Exception {
System.out.println(parentPath + " 's child changed ,currentChilds: " + currentChild);
}
});
//创建节点
zkClient.createPersistent(path);
Thread.sleep(1000);
System.out.println(zkClient.getChildren(path));
Thread.sleep(1000);
zkClient.createPersistent(path + "/c1");
Thread.sleep(1000);
System.out.println(zkClient.getChildren(path));
Thread.sleep(1000);
zkClient.createPersistent(path + "/c2","123");
Thread.sleep(1000);
System.out.println(zkClient.getChildren(path));
//删除节点
Thread.sleep(1000);
zkClient.delete(path + "/c1");
Thread.sleep(1000);
System.out.println(zkClient.getChildren(path));
zkClient.delete(path + "/c2");
System.out.println(zkClient.getChildren(path));
Thread.sleep(1000);
}
}
改变节点数据、检测节点是否存在
public class ZkClientTest {
public static void main(String[] args) throws InterruptedException {
//创建会话
ZkClient zkClient = new ZkClient("127.0.0.1:2181", 5000);
//创建节点
String path = "/zk-book";
zkClient.createPersistent(path,"123");
//监测节点数据变化
zkClient.subscribeDataChanges(path, new IZkDataListener() {
@Override
public void handleDataDeleted(String dataPath) throws Exception {
System.out.println("Node " + dataPath + " deleted.");
}
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.println("Node " + dataPath + " changed, new data: " +data);
}
});
//读取节点数据
System.out.println(zkClient.readData(path));
zkClient.writeData(path, "456");
Thread.sleep(1000);
System.out.println("Node exists :" + zkClient.exists(path));
zkClient.delete(path);
System.out.println("Node exists :" + zkClient.exists(path));
}
}
Demo引自——【ZooKeeper学习笔记-6---ZkClient使用】,该博客中有系统总结ZK应用,可以学习。
2.1集群部署
zk集群搭建可参考该文章:https://blog.csdn.net/liu_zhaoming/article/details/79332232
注意!!!在zookeeper的选举过程中,为了保证选举过程最后能选出leader,就一定不能出现两台机器得票相同的僵局,所以一般的,要求zk集群的server数量一定要是奇数,也就是2n+1
台,并且,如果集群出现问题,其中存活的机器必须大于n+1
台,否则leader无法获得多数server的支持,系统就自动挂掉。所以一般是3个或者3个以上节点。因此有些面试考察的时候会问各种挂掉的情况,只要记住:过半存活就能保证工作,否则系统就挂掉了。另外ZK集群不支持动态扩容(其实就是水平扩容),Zookeeper在这方面不太好,扩容只能采用以下两种方式:
- 全部重启:关闭所有Zookeeper服务,修改配置之后启动。不影响之前客户端的会话。
- 逐个重启:顾名思义。这是比较常用的方式。
三、面经
Zookeeper是什么框架?
应用场景
Paxos算法& Zookeeper使用协议
选举算法和流程
Zookeeper有哪几种节点类型
Zookeeper对节点的watch监听通知是永久的吗?
部署方式?集群中的机器角色都有哪些?集群最少要几台机器?
集群如果有3台机器,挂掉一台集群还能工作吗?挂掉两台呢?
集群支持动态添加机器吗?
参考引用
Zookeeper 入门
Zookeeper ZAB 协议分析
ZK系列专栏,尤其推荐 Zookeeper 3、Zookeeper工作原理(详细)
可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章