ZooKeeper需要关注的点
0. Overview
1. 用途
- 集群管理
- 集群机器监控/负载均衡
- 集群选主
- 配置管理
- 命名服务
- 分布式锁
- 队列管理
2. 特点
3. 基本架构
- zk角色
- zk消息类型
- Znode类型
4. 数据读写
5. 工作原理
- Zab协议/数据更新
- Fast Paxos协议/Leader选举
- Server的三种状态
- Watcher机制
6. Reference
Overview
协调框架
,实现同步
服务,配置维护
和命名服务
等分布式应用,基于对Zab协议(ZooKeeper Atomic Broadcast,zk原子消息广播协议,分布式一致性算法)的实现,能够保证分布式环境中数据的一致性。简单来看,zookeeper=文件系统+通知机制。
用途
集群管理
集群机器监控/负载均衡
在分布式的集群中,由于各种原因,如硬件故障,软件故障,网络问题,有些节点会进进出出。有新的节点加入进来,也有老的节点退出集群。此时,集群中有些机器(比如Master节点)需要感知到这种变化,然后根据这种变化做出对应的决策,
/clusterServersStatus/{hostname}
集群选主
一旦当前master服务器宕机了,它创建的EPHEMERAL_SEQUENTIAL临时顺序节点会马上消失;紧接着集群中注册过Watcher的所有服务器会收到当前master服务器已宕机的通知,然后将重新进行master选举,有采用最小机器编号的,有采用最新事务编号的,有采用quorum多数投票的等等
在分布式环境中,相同的业务应用分布在不同的机器上,有些业务逻辑(例如一些耗时的计算,网络I/O处理),往往只需要让整个集群中的某一台机器进行执行,其余机器可以共享这个结果,这样可以大大减少重复劳动,提高性能。例如elasticsearch的index关闭replica,但es不是用zk来实现;又如数据库的读写分离(把写全部给leader/master,查询则使用follower的机器),主从复制。于是这个master选举便是这种场景下的碰到的主要问题,
配置管理
zk很容易实现这种集中式的配置管理,比如将app1的所有配置设置在/app1的znode下,app1所有机器一启动就对/app1这个节点进行监控
zk.exist("/app1",true)
,并且实现回调方法Watcher。那么在zk上/app1 znode节点下数据发生变化的时候,每个机器都会收到通知,Watcher方法将会被执行,那么应用再取下数据即可zk.getData("/app1",false,null)
将配置数据直接存储在zk上,不论叶子节点还是中间节点都可以作为配置项
客户端机器任何访问过的配置项都会从zk上拉取并缓存在本地共享内存,同时注册Watcher
之后的配置修改都会收到通知并有机会刷新缓存
命名服务
分布式锁
互斥资源
进行访问时需要加锁,那分布式程序分布在各个主机上的进程对互斥资源进行访问时也需要加锁。分布式系统可能会有多个可服务的窗口,但是在某个时刻只能让一个服务去工作,当这台服务出问题时锁要被释放,立即failover到另外的服务。这在很多分布式系统中都是这么做,这种设计有一个更好听的名字叫
Leader Election
/选主
。例如,到银行取钱,有多个服务窗口,但对你来说,只能有一个窗口对你服务,如果正在对你服务的窗口的柜员突然有急事走了?此时怎么办?找大堂经理(zookeeper)。大堂经理指定另外的一个窗口继续为你服务。
队列管理
特点
基本架构
zk角色
每个server在内存中存储了一份数据
zk启动时,从实例中选举一个leader(Paxos协议)
Leader负责处理数据更新等操作(Zab协议)
zk消息类型
Znode类型
数据读写
写数据,某一个客户端进行写数据请求时,如果是follower接收到
写请求
,就会把请求转发给leader,leader通过内部的Zab协议
进行原子广播,直到所有zk节点都成功写了数据并commit后,这次写请求算是完成,然后zk server就会给client发回响应。写流程
读数据,因为集群中所有的zk节点都呈现一个同样的命名空间视图,写请求已经保证了写一次数据必须是集群所有的zk节点都已同步了命名空间,所以读的时候可以在任意一台zk节点上
节点数据太大
/节点层次太深
/子节点太多
,都会影响到其稳定性。所以zk不是一个用来做高并发高性能的数据库,zk一般只用来存储配置信息。工作原理
Zab协议/数据更新
客户端请求
转化为事务提议(Proposal),并将该proposal分发给集群所有的follower服务器。之后Leader服务器需要等待所有的follower服务器的反馈,一旦超过了半数的follower服务器进行了正确反馈后,那么Leader服务器就会再次向所有的follower服务器分发commit消息,要求其将前一个proposal进行提交。当整个服务框架启动过程中或Leader服务器出现网络中断、崩溃退出与重启等异常情况时,Zab协议就会进入
恢复模式
并选举产生新的Leader服务器当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就可以进入
消息广播模式
Fast Paxos协议/Leader选举
Leader选举
是保证分布式数据一致性的关键所在。当zk集群中的一台服务器出现以下两种情况之一时,需要进入Leader选举集群中已存在Leader
对于集群中已经存在Leader这种情况,一般都是某台机器启动得较晚,在其启动之前,集群已经在正常工作,对这种情况,该机器试图去选举Leader时,会被告知当前集群的Leader信息,对于该机器而言,仅仅需要和Leader机器建立起连接,并进行状态同步即可
集群中不存在Leader
首次投票。无论哪种导致进行Leader选举,集群的所有机器都处于试图选举出一个Leader的状态,即LOOKING状态,LOOKING机器会向所有其他机器发送消息,该消息称为投票。投票中包含了服务器的唯一标识SID和事务标识ZXID,
(SID, ZXID)
用于标识一次投票信息。
假定zk由5台机器组成,SID分别为1、2、3、4、5,ZXID分别为9、9、9、8、8,并且此时SID为2的机器是Leader机器,某一时刻,1、2所在机器出现故障,因此集群开始进行Leader选举。在第一次投票时,每台机器都会将自己作为投票对象,于是SID为3、4、5的机器投票情况分别为(3, 9),(4, 8), (5, 8)变更投票。每台机器发出
投票
后,也会收到其他机器的投票,每台机器会根据一定规则
来处理收到的其他机器的投票,并以此来决定是否需要变更自己的投票,这个规则也是整个Leader选举算法的核心所在,
(vote_sid,vote_zxid):接收到的投票中所推举Leader服务器的(SID,ZXID)对
(self_sid,self_zxid):当前服务器自己的(SID,ZXID)对
每次对收到的投票的处理,都是对(vote_sid, vote_zxid)和(self_sid, self_zxid)对比的过程
规则一:如果vote_zxid > self_zxid,就
认可
当前收到的投票,并再次将该投票发送出去规则二:如果vote_zxid < self_zxid,那么
坚持
自己的投票,不做任何变更规则三:如果vote_zxid == self_zxid,那么就对比两者的SID,如果vote_sid > self_sid,那么就认可当前收到的投票,并再次将该投票发送出去;否则vote_sid < self_sid,那么坚持自己的投票,不做任何变更
Leader的zxid, sid最大,zxid越大意味着数据越新
再次
接收到其他机器的投票,然后开始统计投票,如果一台机器收到了超过半数的相同投票,那么这个投票对应的SID机器即为Leader。Server的三种状态
Watcher机制
客户端在自己需要关注的ZNode节点上注册getData/exists/getChildren一个Watcher监听后,一旦这个ZNode节点发生变化,则在该节点上注册过Watcher监听的所有客户端会收到ZNode节点变化通知NodeDataChanged/NodeChildrenChanged等。在收到通知时,客户端通过回调Watcher做相应的处理,从而实现特定的功能。
无论是服务端还是客户端,一旦一个Watcher被触发,zk都会将其从相应的存储中移除,因此需要反复注册
getData方法中设置的watch函数会在数据发生更新或者删除时被触发
exists在节点的存活性发生变化时触发
getChildren则在子节点的存活性发生变化时触发
文章不错?点个【在看】吧! 👇