zooKeeper全网资源总结
zooKeeper底层原理&应用总结 强烈推荐!全网最强zk学习资料
zk的应用场景与原理总结
官方说法:解决分布式应用的数据管理问题.
如:统一命名服务,状态同步服务(分布式锁),集群的管理,分布式应用配置项的管理.
zookeeper: 文件系统+通知机制
zk维护一个类似于文件系统的数据结构,每一个子目录项为一个Znode,提供对数据的内存型KV存储.
客户端通过注册监听(watch)它关心的目录节点,当目录节点发生变化的时候,zk会通知客户端.
命名服务
在zk的文件系统里创建一个目录,即有一个唯一的路径,
Zk可以通过顺序节点的特性来生成全局唯一ID,从而对分布式系统提供命名服务。
可用于文件搜索与发现.
配置管理
假如现在有四台机器运行着四个相同的客户端程序,程序有很多配置,假如要修改配置的话,逐个修改很麻烦对吧.我们不如把配置都放在zk上,保存在zk的某个目录节点当中,然后所有相关应用程序对这个目录节点进行监听.一旦配置发生变化,那么每个程序都会收到zk的通知。
具体做法是通过Watcher机制实现数据的发布和订阅,客户端节点可以对某个ZNode注册监听,之后如果要统一修改客户端的配置,只要直接把新的配置写入该ZNode,所有服务节点就可以收到这个事件。
对于管理集群机器的加入与退出问题:
所有机器可以约定在父目录GroupMembers下创建临时目录节点,然后监听父目录节点的子节点变化消息.
一旦有机器挂掉,这个机器与zk的链接断开.其所创建的临时目录节点会被删除,所有其他机器都会收到通知:这个机器已经退出了.
机器新加入也是类似的.
对于master选举问题:
每个机器对应一个目录节点,那么我们可以对目录节点做编号,根据编号选master即可.
锁服务分为两类,一个是保持独占,另一个是控制时序.
对于第一类,把zk上的一个znode看作是一把锁,通过createznode的方式来实现.所有客户端都去创建/distribute lock节点,最终成功创建的客户端即拥有这个锁,用完之后就要删除自己创建的锁
对于第二类
/distribute_lock已经预先存在,所有客户端都要在其下面创建临时顺序来对目录节点做编号,与选master一样,编号最小的就获得锁.
zk的核心是原子广播,这个机制保证了各个Server之间的同步.背后是Zab协议
Zab协议有两种模式:
1.恢复模式:用于选主
2.广播模式:用于同步
当服务启动或者领导者崩溃的时候,Zab进入恢复模式,选出新的领导者.然后直到大多数server完成了对leader状态的同步.
ZooKeeper 还会为每一个 ZooKeeper 事务赋予名为 ZXID 的 64 位唯一 ID 进行标识,其中低 32 位为该事务在此次任期中的序列号,高 32 位为当前 Leader 所属任期的 epoch 值,用于区分不同 Leader 发来的消息。
选主与同步的具体流程可参考:
zk原理解析
- 索引信息和集群中机器节点状态存放在zk的一些指定节点,供各个客户端订阅使用。
- 系统日志(经过处理后的)存储,这些日志通常2-3天后被清除。
应用中用到的一些配置信息集中管理,在应用启动的时候主动来获取一次,并且在节点上注册一个Watcher,以后每次配置有更新,实时通知到应用,获取最新配置信息。
消息中间件的消息队列通常有个offset,这个offset存放在zk上,这样集群中每个发送者都能知道当前的发送进度。(如Kafka就是依赖zk来运行的)
分布式命名服务
利用Znode的树架构,可以创建一个全局唯一的path
分布式通知/协调来解耦
ZooKeeper 中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对 ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系统能 够收到通知,并作出相应处理。
- 另一种心跳检测机制:检测系统和被检测系统之间并不直接关联起来,而是通过zk上某个节点关联,大大减少系统耦合。
- 另一种系统调度模式:某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作,实际上是修改 了ZK上某些节点的状态,而zk就把这些变化通知给他们注册Watcher的客户端,即推送系统,于是,作出相应的推送任务。
- 另一种工作汇报模式:一些类似于任务分发系统,子任务启动后,到zk来注册一个临时节点,并且定时将自己的进度进行汇报(将进度写回这个临时节点),这样任务管理者就能够实时知道任务进度。
总之,使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合。
提供分布式系统协调服务,提供一些基本原语API来辅助上层分布式应用实现进程间的协调.
从功能上来看,ZooKeeper提供了一个基于目录树结构的内存型KV存储.
数据统一以ZNode的形式保存在各个ZooKeeper节点的内存中,数据的变更由Leader节点通过Zab协议同步给所有的follower节点.
注:Zab协议是类似于Raft的分布式同步协议.
工作流程:
写操作可以分为三步:
1.Leader对写操作进行预处理,转换为等价ZooKeeper事务
在预处理阶段,Leader 首先会将客户端发来的写请求转换为等价的幂等 ZooKeeper 事务。每个事务都明确的表明了其执行前的期望状态和执行完成后的结果状态。考虑到 Leader 永远持有最新的数据,Leader 是最适合使用自身保存的数据来计算对应的 ZooKeeper 事务的
什么叫做幂等性?
用户对同一操作发起的一次请求或者多个请求的结果是一致的
2.Leader通过Zab协议向所有Follower节点Propose该事务
事务的幂等性还为 Zab 的实现带来的便利,使得 Zab 无论是在正常的数据传递还是节点恢复时都不需要保证消息传递的 exactly-once 语义,只需要保证消息传递的顺序以及 at-least-once 投递即可
3.Leader收到大多数Follower的ACK信息,对事务进行commit,持久化到存储当中
为了应对节点失效,ZooKeeper会对保存的数据周期性地保存在磁盘中以生成快照,以便在节点失效重启后能够快速从最近的快照中恢复数据状态.
生成数据快照与客户端请求处理并发进行,这意味着ZooKeeper生成的数据快照可能不会对应ZooKeeper在任意一个时间点上的实际状态.
主写读从
与Raft类似的是,ZAB通过主写读从来实现数据的同步(都是由主节点同步到从节点)
两种状态:消息广播与崩溃恢复 zk就来回在这两种状态中切换
消息广播:
类似于一个2PC,在整个消息的广播过程中,Leader 服务器会每个事务请求生成对应的 Proposal,并为其分配一个全局唯一的递增的事务 ID(ZXID),之后再对其进行广播
1.主节点把数据复制到follower过程中
2.等待follower回应Ack,最低超过半数即成功
3.超过半数则成功回应,执行commit,同时提交自己
崩溃恢复:
崩溃指Leader与过半的Follower失去联系
ZAB设计的选举算法:
能够确保提交被Leader提交事务,同时丢弃没有被leader提交的事务.
具体做法是选举算法要保证选出来的leader拥有最大的ZXID.
那么这个新选出来的leader一定会具有所有已经提交的提案
假设1:Leader 在复制数据给所有 Follwer 之后,没有发送commit之前,怎么办?
假设2:Leader 在收到 Ack 并提交了自己,同时发送了部分 commit 出去之后崩溃怎么办?
解决1: 最终会丢弃那些没有提交的数据
解决2: 最终会同步所有服务器的数据(选举出来的leader肯定会拥有最新最全的数据)
一、什么情况下zab协议会进入崩溃恢复模式?
1、当服务器启动时
2、当leader 服务器出现网络中断,崩溃或者重启的情况
3、当集群中已经不存在过半的服务器与Leader服务器保持正常通信。
二、zab协议进入崩溃恢复模式会做什么?
1、当leader出现问题,zab协议进入崩溃恢复模式,并且选举出新的leader。当新的leader选举出来以后,如果集群中已经有过半机器完成了leader服务器的状态同(数据同步),退出崩溃恢复,进入消息广播模式。
2、当新的机器加入到集群中的时候,如果已经存在leader服务器,那么新加入的服务器就会自觉进入崩溃恢复模式,找到leader进行数据同步。
三、特殊情况下需要解决的两个问题:
1、已经被处理的事务请求(proposal)不能丢(commit的)
2、没被处理的事务请求(proposal)不能再次出现
什么时候会出现事务请求被丢失呢?
当 leader 收到合法数量 follower 的 ACKs 后,就向各个 follower 广播 COMMIT 命令,同时也会在本地执行 COMMIT 并向连接的客户端返回「成功」。但是如果在各个 follower 在收到 COMMIT 命令前 leader 就挂了,导致剩下的服务器并没有执行都这条消息。
如何解决 已经被处理的事务请求(proposal)不能丢(commit的)呢?
1、选举拥有 proposal 最大值(即 zxid 最大) 的节点作为新的 leader:由于所有提案被 COMMIT 之前必须有合法数量的 follower ACK,即必须有合法数量的服务器的事务日志上有该提案的 proposal,因此,zxid最大也就是数据最新的节点保存了所有被 COMMIT 消息的 proposal 状态。
2、新的 leader 将自己事务日志中 proposal 但未 COMMIT 的消息处理。
3、新的 leader 与 follower 建立先进先出的队列, 先将自身有而 follower 没有的 proposal 发送给 follower,再将这些 proposal 的 COMMIT 命令发送给 follower,以保证所有的 follower 都保存了所有的 proposal、所有的 follower 都处理了所有的消息。通过以上策略,能保证已经被处理的消息不会丢。
问题二出现的场景是:
当 leader 接收到消息请求生成 proposal 后就挂了,其他 follower 并没有收到此 proposal,因此经过恢复模式重新选了 leader 后,这条消息是被跳过的。 此时,之前挂了的 leader 重新启动并注册成了 follower,他保留了被跳过消息的 proposal 状态,与整个系统的状态是不一致的,需要将其删除。
解决方案是:
Zab 通过巧妙的设计 zxid 来实现这一目的。一个 zxid 是64位,高 32 是纪元(epoch)编号,每经过一次 leader 选举产生一个新的 leader,新 leader 会将 epoch 号 +1。低 32 位是消息计数器,每接收到一条消息这个值 +1,新 leader 选举后这个值重置为 0。这样设计的好处是旧的 leader 挂了后重启,它不会被选举为 leader,因为此时它的 zxid 肯定小于当前的新 leader。当旧的 leader 作为 follower 接入新的 leader 后,新的 leader 会让它将所有的拥有旧的 epoch 号的未被 COMMIT 的 proposal 清除。
崩溃恢复之后要进行数据同步.Leader首先确认事务是否都已经被过半的Follower提交了.目的是完成保持数据一致
当 Follower 链接上 Leader 之后,Leader 服务器会根据自己服务器上最后被提交的 ZXID 和 Follower 上的 ZXID 进行比对,比对结果要么回滚,要么和 Leader 同步。
基于的ID信息:
1.服务器ID
编号越大,选举的优先级越大
2.Zxid:数据ID
服务器中存放的最大数据ID,值越大说明数据越新,在选举算法中数据越新权重越大
3.Epoch 逻辑时钟
相当于记录选举的轮数,每一轮都会加1
4.Server状态
选举流程:
一:开始选举,每个服务器读取自身的数据ID
二:发送投票信息
a.每个Server第一轮都会投给自己
b.投票信息包括所选举leader的服务器ID,数据ID(zxid),epoch
三:接受投票信息
假设服务器B接收到来自服务器A的数据
1.服务器A处于选举状态
1)首先要判断epoch逻辑时钟值:
a)如果发送过来的Epoch大于目前的Epoch,那么首先要更新本逻辑时钟Epoch,同时清空本轮逻辑时钟收集到的其他server选举数据
然后根据zxid最大值和leader serverid最大值来判断是否需要更新当前自己的选举leader:
先看zxid,zxid大的先胜出
然后再判断leader serverid,leader serverid大的胜出
然后再把自己最新的选举结果广播给其他server
b)如果发送过来的逻辑时钟小于目前时钟,说明对方的server处在一个较早的epoch,因此需要把目前本地的最新epoch发送回去
c) 如果发送过来的逻辑时钟Epoch等于目前的逻辑时钟。再根据上述判断规则rules judging来选举leader ,然后再将自身最新的选举结果(也就是上面提到的三种数据(leader Serverid,Zxid,Epoch)广播给其他server)
2)
其次,判断服务器是不是已经收集到了所有服务器的选举状态:若是,根据选举结果设置自己的角色(FOLLOWING还是LEADER),退出选举过程就是了。
最后,若没有收到没有收集到所有服务器的选举状态:也可以判断一下根据以上过程之后最新的选举leader是不是得到了超过半数以上服务器的支持,如果是,那么尝试在200ms内接收一下数据,如果没有新的数据到来,说明大家都已经默认了这个结果,同样也设置角色退出选举过程。
Hadoop权威指南
https://mr-dai.github.io/zookeeper/
http://nil.csail.mit.edu/6.824/2018/papers/zookeeper.pdf
非常详细的总结
https://www.cnblogs.com/shuaiandjun/p/9383655.html