zookeeper基础知识
2021-09-12 15:58:20 2 举报
AI智能生成
zookeeper基础知识,资料整理
作者其他创作
大纲/内容
用途
微服务注册中心
命名服务:Zookeeper可以创建一个「全局唯一的路径」,这个路径就可以作为一个名字。
被命名的实体可以是「集群中的机器,服务的地址,或者是远程的对象」等
被命名的实体可以是「集群中的机器,服务的地址,或者是远程的对象」等
分布式锁
配置管理
把程序的这些配置信息「保存在zk的znode节点」下,
当你要修改配置,即znode会发生变化时,可以通过改变zk中某个目录节点的内容,利用「watcher通知给各个客户端」,从而更改配置。
当你要修改配置,即znode会发生变化时,可以通过改变zk中某个目录节点的内容,利用「watcher通知给各个客户端」,从而更改配置。
集群管理
监控集群机器状态,剔除机器和加入机器。zookeeper可以方便集群机器的管理,它可以实时监控znode节点的变化,
一旦发现有机器挂了,该机器就会与zk断开连接,对用的临时目录节点会被删除,其他所有机器都收到通知。
新机器加入也是类似酱紫,所有机器收到通知:有新兄弟目录加入啦
一旦发现有机器挂了,该机器就会与zk断开连接,对用的临时目录节点会被删除,其他所有机器都收到通知。
新机器加入也是类似酱紫,所有机器收到通知:有新兄弟目录加入啦
znode类型
持久节点(PERSISTENT)
一直存在于Zk服务器上。直到手动删除
持久顺序节点(PERSISTENT_SEQUENTIAL)
父节点会维护一个自增整性数字,用于子节点的创建的先后顺序
临时节点(EPHEMERAL)
临时节点的生命周期与客户端的会话绑定,一旦客户端会话失效(非TCP连接断开),那么这个节点就会被自动清理掉。zk规定临时节点只能作为叶子节点。
临时顺序节点(EPHEMERAL_SEQUENTIAL)
基本特性同临时节点,添加了顺序的特性
多用于分布式锁
znode节点里面存储的是什么
存储数据、访问权限、子节点引用、节点状态信息
「data:」 znode存储的业务数据信息
「acl:」 记录客户端对znode节点的访问权限,如IP等。
「child:」 当前节点的子节点引用
「stat:」 包含Znode节点的状态信息,比如「事务id、版本号、时间戳」等等。
「acl:」 记录客户端对znode节点的访问权限,如IP等。
「child:」 当前节点的子节点引用
「stat:」 包含Znode节点的状态信息,比如「事务id、版本号、时间戳」等等。
为了保证高吞吐和低延迟,以及数据的一致性,znode只适合存储非常小的数据,不能超过1M,最好都小于1K
Watcher监听机制
Zookeeper 允许客户端向服务端的某个Znode注册一个Watcher监听
客户端首先将Watcher注册到服务端,
同时将Watcher对象保存到客户端的Watch管理器中。
当ZooKeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的Watch管理器会触发相关Watcher来回调相应处理逻辑,从而完成整体的数据发布/订阅流程
同时将Watcher对象保存到客户端的Watch管理器中。
当ZooKeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的Watch管理器会触发相关Watcher来回调相应处理逻辑,从而完成整体的数据发布/订阅流程
当服务端的一些指定事件触发了这个Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能
客户端根据 Watcher通知状态和事件类型做出业务上的改变
可以把Watcher理解成客户端注册在某个Znode上的触发器,
当这个Znode节点发生变化时(增删改查),就会触发Znode对应的注册事件,注册的客户端就会收到异步通知,然后做出业务的改变
当这个Znode节点发生变化时(增删改查),就会触发Znode对应的注册事件,注册的客户端就会收到异步通知,然后做出业务的改变
工作原理
ZooKeeper的Watcher机制主要包括客户端线程、客户端 WatcherManager、Zookeeper服务器三部分。
1,客户端向ZooKeeper服务器注册Watcher的同时,
2,会将Watcher对象存储在客户端的WatchManager中。
3,当zookeeper服务器触发watch事件后,会向客户端发送通知, 客户端线程从 WatcherManager 中取出对应的 Watcher 对象来执行回调逻辑。
1,客户端向ZooKeeper服务器注册Watcher的同时,
2,会将Watcher对象存储在客户端的WatchManager中。
3,当zookeeper服务器触发watch事件后,会向客户端发送通知, 客户端线程从 WatcherManager 中取出对应的 Watcher 对象来执行回调逻辑。
Watcher特性总结
「一次性」: 一个Watch事件是一个一次性的触发器。一旦被触发就会移除,再次使用时需要重新注册。
「异步的」 : Zookeeper服务器发送watcher的通知事件到客户端是异步的,
不能期望能够监控到节点每次的变化,Zookeeper只能保证最终的一致性,而无法保证强一致性。
「轻量级」 :Watcher 通知非常简单,它只是通知发生了事件,而不会传递事件对象内容。
WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容;
「客户端串行」: 执行客户端 Watcher 回调的过程是一个串行同步的过程。只有回调后客户端才能看到最新的数据状态。一个Watcher回调逻辑不应该太多,以免影响别的watcher执行
注册 watcher用getData、exists、getChildren方法
触发 watcher用create、delete、setData方法
「异步的」 : Zookeeper服务器发送watcher的通知事件到客户端是异步的,
不能期望能够监控到节点每次的变化,Zookeeper只能保证最终的一致性,而无法保证强一致性。
「轻量级」 :Watcher 通知非常简单,它只是通知发生了事件,而不会传递事件对象内容。
WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容;
「客户端串行」: 执行客户端 Watcher 回调的过程是一个串行同步的过程。只有回调后客户端才能看到最新的数据状态。一个Watcher回调逻辑不应该太多,以免影响别的watcher执行
注册 watcher用getData、exists、getChildren方法
触发 watcher用create、delete、setData方法
public byte[] getData(final String path, Watcher watcher, Stat stat);
public List<String> getChildren(final String path, Watcher watcher);
public Stat exists(final String path, Watcher watcher);
public List<String> getChildren(final String path, Watcher watcher);
public Stat exists(final String path, Watcher watcher);
List<String> children = zooKeeper.getChildren(nodePath, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 获取数据, 并继续监控. this 表示将监控器本身作为监控器继续监控
try {
List<String> childs = zooKeeper.getChildren(nodePath, this);
System.out.println("自定义节点监控-根节点/下的子节点: " + childs + ", event-type:" + event.getType());
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
@Override
public void process(WatchedEvent event) {
// 获取数据, 并继续监控. this 表示将监控器本身作为监控器继续监控
try {
List<String> childs = zooKeeper.getChildren(nodePath, this);
System.out.println("自定义节点监控-根节点/下的子节点: " + childs + ", event-type:" + event.getType());
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Zookeeper的特性
「顺序一致性」:从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去
事务ID,即zxid。ZooKeeper的在选举时通过比较各结点的zxid和机器ID选出新的主结点的。
zxid由Leader节点生成,有新写入事件时,Leader生成新zxid并随提案(proposal)一起广播,
每个结点本地都保存了当前最近一次事务的zxid,zxid是递增的,所以谁的zxid越大,就表示谁的数据是最新的
zxid由Leader节点生成,有新写入事件时,Leader生成新zxid并随提案(proposal)一起广播,
每个结点本地都保存了当前最近一次事务的zxid,zxid是递增的,所以谁的zxid越大,就表示谁的数据是最新的
ZXID 由两部分组成:
任期:完成本次选举后,直到下次选举前,由同一 Leader 负责协调写入;
事务计数器:单调递增,每生效一次写入,计数器加一
ZXID 的低 32 位是计数器,所以同一任期内,ZXID 是连续的,每个结点又都保存着自身最新生效的 ZXID,
通过对比新提案的 ZXID 与自身最新 ZXID 是否相差“1”,来保证事务严格按照顺序生效的
通过对比新提案的 ZXID 与自身最新 ZXID 是否相差“1”,来保证事务严格按照顺序生效的
并且Leader是事务请求的唯一调度和处理者,这两步,保证集群事务处理的顺序性
「原子性」:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,
要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用
要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用
「单一视图」:无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的
「可靠性:」 一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来
「实时性(最终一致性)」
Zookeeper 仅仅能保证在一定的时间段内,客户端最终一定能够从服务端上读取到最新的数据状态。
ZooKeeper 是通过两阶段提交保证数据的最终一致性,并且通过严格按照 ZXID 的顺序生效提案保证其顺序一致性
Leader 在收到半数以上 ACK 后会将提案生效并广播(commit)给所有 Follower 结点
Leader 为了保证提案按 ZXID 顺序生效,
使用了一个 ConcurrentHashMap,记录所有ZXID未提交的提案,命名为 outstandingProposals,key 为 ZXID,Value 为提案的信息
使用了一个 ConcurrentHashMap,记录所有ZXID未提交的提案,命名为 outstandingProposals,key 为 ZXID,Value 为提案的信息
1,每发起一个提案,会将提案的 ZXID 和内容放到 outstandingProposals 中,作为待提交的提案
2,收到 Follower 的 ACK 信息后,根据 ACK 中的 ZXID 从 outstandingProposals 中找到对应的提案,对 ACK 计数
3,执行 tryToCommit 尝试将提案提交,判断流程:
先判断当前 ZXID 之前是否还有未提交提案,如果有,当前提案暂时不能提交;
Leader 是如何判断当前 ZXID 之前是否还有未提交提案的呢?
由于前提是保证顺序提交的,所以 Leader 只需判断 outstandingProposals 里,当前 ZXID 的前一个 ZXID 是否存在
再判断提案是否收到半数以上 ACK,如果达到半数则可以提交;
如果可以提交,将当前 ZXID 从 outstandingProposals 中清除并向 Followers 广播提交当前提案
不是说leader必须保证一条数据被全部follower都commit了才会让你读取到数据,
而是过程中可能你会在不同的follower上读取到不一致的数据,但是最终一定会全部commit后一致,让你读到一致的数据的
而是过程中可能你会在不同的follower上读取到不一致的数据,但是最终一定会全部commit后一致,让你读到一致的数据的
zab协议
主从节点一致性问题
主服务器挂了,或者重启了
崩溃恢复:Leader挂了,进入该模式,选一个新的leader出来
主从服务器之间同步数据
消息广播:把更新的数据,从Leader同步到所有Follower
分布式协调服务Zookeeper专门设计的一种 支持崩溃恢复 的 原子广播 协议 ,是Zookeeper保证数据一致性的核心算法,保证主从节点一致性问题
在Zookeeper中主要依赖Zab协议来实现数据一致性,基于该协议,zk实现了一种主备模型(即Leader和Follower模型)的系统架构来保证集群中各个副本之间数据的一致性
Zookeeper 客户端会随机的链接到 zookeeper 集群中的一个节点,
如果是读请求,就直接从当前节点中读取数据;
如果是写请求,那么节点就会转发给 Leader 来提交事务,
Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交
如果是读请求,就直接从当前节点中读取数据;
如果是写请求,那么节点就会转发给 Leader 来提交事务,
Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交
Zab 协议的特性
Zab 协议需要确保那些已经在 Leader 服务器上提交(Commit)的事务最终被所有的服务器提交
Zab 协议需要确保丢弃那些只在 Leader 上被提出而没有被提交的事务
Zab 协议实现的 作用
使用一个单一的主进程(Leader)来接收并处理客户端的事务请求(也就是写请求),
并采用了Zab的原子广播协议,将服务器数据的状态变更以 事务proposal (事务提议)的形式广播到所有的副本(Follower)进程上去
并采用了Zab的原子广播协议,将服务器数据的状态变更以 事务proposal (事务提议)的形式广播到所有的副本(Follower)进程上去
保证一个全局的变更序列被顺序引用
同一个Leader发起的事务要按顺序被apply
选举产生时,先前Leader的事务被apply应用之后,新选举出来的Leader才能再次发起事务
比如新的leader有一个未提交的proposal,就先广播出去,一样的接受过半ack后进行commit
Zab协议原理
发现
zookeeper集群必须选举出一个 Leader 进程,同时 Leader 会维护一个 Follower 可用客户端列表。将来客户端可以和这些 Follower节点进行通信
同步
Leader 要负责将本身的数据与 Follower 完成同步,做到多副本存储。
这样也是提现了CAP中的高可用和分区容错。Follower将队列中未处理完的请求消费完成后,写入本地事务日志中
这样也是提现了CAP中的高可用和分区容错。Follower将队列中未处理完的请求消费完成后,写入本地事务日志中
广播
Leader 可以接受客户端新的事务Proposal请求,将新的Proposal请求广播给所有的 Follower
Zab协议核心
所有的事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被叫做 Leader服务器。其他剩余的服务器则是 Follower服务器
Leader服务器 负责将一个客户端事务请求,转换成一个 事务Proposal,并将该 Proposal 分发给集群中所有的 Follower 服务器,也就是向所有 Follower 节点发送数据广播请求
分发之后Leader服务器需要等待所有Follower服务器的反馈(Ack请求),在Zab协议中,只要超过半数的Follower服务器进行了正确的反馈后(也就是收到半数以上的Follower的Ack请求),那么 Leader 就会再次向所有的 Follower服务器发送 Commit 消息,要求其将上一个 事务proposal 进行提交
协议过程
崩溃恢复模式
当整个集群启动过程中,或者当 Leader 服务器出现网络中断、崩溃退出或重启等异常时,Zab协议就会 进入崩溃恢复模式,选举产生新的Leader
当Leader出现崩溃退出或者机器重启,亦或是集群中不存在超过半数的服务器与Leader保存正常通信,Zab就会再一次进入崩溃恢复,发起新一轮Leader选举并实现数据同步。同步完成后又会进入消息广播模式,接收事务请求
消息广播模式
与二段提交相似,但是却又不同。二段提交要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要求所有的参与者要么全部成功,要么全部失败。二段提交会产生严重的阻塞问题
Zab协议中 Leader 等待 Follower 的ACK反馈消息是指“只要半数以上的Follower成功反馈即可,不需要收到全部Follower反馈”
Zookeeper 服务器角色
Leader服务器
Leader服务器是整个ZooKeeper集群工作机制中的核心
事务请求的唯一调度和处理者,保证集群事务处理的顺序性
集群内部各服务的调度者
Follower
Follower服务器是ZooKeeper集群状态的跟随者
处理客户端非事务请求,转发事务请求给Leader服务器
参与事务请求Proposal的投票
参与Leader选举投票
Observer
Observer是3.3.0 版本开始引入的一个服务器角色,它充当一个观察者角色——观察ZooKeeper集群的最新状态变化并将这些状态变更同步过来
处理客户端的非事务请求,转发事务请求给 Leader 服务器
不参与任何形式的投票
工作状态
LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。
FOLLOWING:跟随者状态。表明当前服务器角色是Follower。
LEADING:领导者状态。表明当前服务器角色是Leader。
OBSERVING:观察者状态。表明当前服务器角色是Observer。
FOLLOWING:跟随者状态。表明当前服务器角色是Follower。
LEADING:领导者状态。表明当前服务器角色是Leader。
OBSERVING:观察者状态。表明当前服务器角色是Observer。
如何选举Leader
假设现在ZooKeeper集群有五台服务器,它们myid分别是服务器1、2、3、4、5
服务器1(myid=1)启动,当前只有一台服务器,无法完成Leader选举
服务器2(myid=2)启动,此时两台服务器能够相互通讯,开始进入Leader选举阶段
1. 每个服务器发出一个投票
服务器1 和 服务器2都将自己作为Leader服务器进行投票,然后各自将这个投票发给集群中的其他所有机器
2. 接受来自各个服务器的投票
每个服务器都会接受来自其他服务器的投票,同时,服务器会校验投票的有效性
3. 处理投票
会将别人的投票跟自己的投票PK
优先检查ZXID。ZXID比较大的服务器优先作为leader
如果ZXID相同的话,就比较myid,myid比较大的服务器作为leader
4. 统计投票
每次投票后,服务器会统计所有投票,判断是否有过半的机器接受到相同的投票信息
服务器2收到两票,少于3(n/2+1,n为总服务器5),所以继续保持LOOKING状态
服务器3(myid=3)启动,继续进入Leader选举阶段
跟前面流程一致,服务器1和2先投自己一票,因为服务器3的myid最大,所以大家把票改投给它。此时,服务器为3票(大于等于n/2+1),所以服务器3当选为Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING
跟前面流程一致,服务器1和2先投自己一票,因为服务器3的myid最大,所以大家把票改投给它。此时,服务器为3票(大于等于n/2+1),所以服务器3当选为Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING
服务器4启动,发起一次选举。
此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。选票信息结果:服务器3为3票,服务器4为1票。服务器4并更改状态为FOLLOWING
此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。选票信息结果:服务器3为3票,服务器4为1票。服务器4并更改状态为FOLLOWING
服务器5启动,发起一次选举。
同理,服务器也是把票投给服务器3,服务器5并更改状态为FOLLOWING
同理,服务器也是把票投给服务器3,服务器5并更改状态为FOLLOWING
投票结束,服务器3当选为Leader
Zookeeper的分布式锁
使用临时顺序节点特性实现分布式锁
获取锁过程 (创建临时节点,检查序号最小)
当第一个客户端请求过来时,Zookeeper客户端会创建一个持久节点/locks
Client1想获得锁,需要在locks节点下创建一个顺序节点lock1
Client1会查找locks下面的所有临时顺序子节点,判断自己的节点lock1是不是排序最小的那一个,如果是,则成功获得锁
Client2前来尝试获得锁,它会在locks下再创建一个临时节点lock2
client2一样也会查找locks下面的所有临时顺序子节点,判断自己的节点lock2是不是最小的,此时,发现lock1才是最小的,于是获取锁失败
client2向它排序靠前的节点lock1注册Watcher事件,用来监听lock1是否存在,也就是说client2抢锁失败进入等待状态
后面监听前面的方式,可以避免羊群效应。所谓羊群效应就是每个节点挂掉,所有节点都去监听,然后做出反映,这样会给服务器带来巨大压力,所以有了临时顺序节点,当一个节点挂掉,只有它后面的那一个节点才做出反映
Client3来尝试获取锁,它会在locks下再创建一个临时节点lock3
client3一样也会查找locks下面的所有临时顺序子节点,判断自己的节点lock3是不是最小的,发现自己不是最小的,就获取锁失败。它也是不会甘心的,它会向在它前面的节点lock2注册Watcher事件,以监听lock2节点是否存在
释放锁 (删除临时节点,监听通知)
释放锁。如果是任务完成,Client1会显式调用删除lock1的指令
如果是客户端故障了,根据临时节点得特性,lock1也是会自动删除的
lock1节点删除,Client2立刻收到通知,也会查找locks下面的所有临时顺序子节点,发下lock2是最小,就获得锁
脑裂问题
假死:由于心跳超时(网络原因导致的)认为leader死了,但其实leader还存活着。
脑裂:由于假死会发起新的leader选举,选举出一个新的leader,但旧的leader网络又通了,导致出现了两个leader ,有的客户端连接到老的leader,而有的客户端则连接到新的leader。
脑裂:由于假死会发起新的leader选举,选举出一个新的leader,但旧的leader网络又通了,导致出现了两个leader ,有的客户端连接到老的leader,而有的客户端则连接到新的leader。
Zookeeper集群中是不会轻易出现脑裂问题的,原因在于过半机制
过半机制
在领导者选举的过程中,如果某台zkServer获得了超过半数的选票,则此zkServer就可以成为Leader了
举例
如果现在集群中有5台zkServer,那么half=5/2=2,那么也就是说,领导者选举的过程中至少要有三台zkServer投了同一个zkServer,才会符合过半机制,才能选出来一个Leader
过半机制中为什么是大于,而不是大于等于
防止脑裂问题
假如机房1有3台,机房2有3台,leader在机房1,机房之间出现网络故障,但是此时过半机制的条件是 "节点数 > 3",所以机房2不能选出leader
脑裂产生原因
leader节点与Zookeeper集群网络断开,但是与其他集群角色之间的网络没有问题
如何解决"脑裂"问题
Quorums (法定人数) 方式
比如3个节点的集群,Quorums = 2, 也就是说集群可以容忍1个节点失效,这时候还能选举出1个lead,集群还可用。比如4个节点的集群,它的Quorums = 3,Quorums要超过3,相当于集群的容忍度还是1,如果2个节点失效,那么整个集群还是无效的。这是zookeeper防止"脑裂"默认采用的方法
假设某个leader假死,其余的followers选举出了一个新的leader
旧的leader复活并且仍然认为自己是leader,这个时候它向其他followers发出写请求也是会被拒绝的
因为每当新leader产生时,会生成一个epoch标号(标识当前属于那个leader的统治时期),这个epoch是递增的,followers如果确认了新的leader存在,知道其epoch,就会拒绝epoch小于现任leader epoch的所有请求
那有没有follower不知道新的leader存在呢,有可能,但肯定不是大多数,否则新leader无法产生
Zookeeper的写也遵循quorum机制,因此,得不到大多数支持的写是无效的,旧leader即使各种认为自己是leader,依然没有什么作用
采用Redundant communications (冗余通信)方式
集群中采用多种通信方式,防止一种通信方式失效导致集群中的节点无法通信
避免zookeeper"脑裂"
在follower节点切换的时候不在检查到老的leader节点出现问题后马上切换,而是在休眠一段足够的时间,确保老的leader已经获知变更并且做了相关的shutdown清理工作了然后再注册成为master就能避免这类问题了,这个休眠时间一般定义为与zookeeper定义的超时时间就够了,但是这段时间内系统可能是不可用的,但是相对于数据不一致的后果来说还是值得的
设置仲裁机制。例如设置参考IP(如网关IP)
当心跳线完全断开时,2个节点都各自ping一下 参考IP
不通则表明断点就出在本端,不仅“心跳”、还兼对外“服务”的本端网络链路断了,即使启动(或继续)应用服务也没有用了,那就主动放弃竞争,让能够ping通参考IP的一端去起服务
ping不通参考IP的一方干脆就自我重启,以彻底释放有可能还占用着的那些共享资源。
0 条评论
下一页