简介
zookeeper是一个由雅虎开发并开源的分布式协调服务,它提供了强一致性的数据模型,利用zookeeper能够实现配置管理、分布式锁、分布式队列等功能。
节点类型
zk的数据存储结构与文件系统类似,znode表示一个存储节点,是zk中最小的数据单元,可以保存数据,还可以挂载子节点。
节点类型分为三种:持久节点、临时节点和顺序节点。
持久节点 | 一旦创建就一直存在,直到被删除 |
临时节点 | 临时节点的生命周期是与客户端的会话绑定的,会话消失则节点消失,临时节点只能做叶子节点,不能再创建子节点 |
顺序节点 | 顺序节点指的是自动带编号的节点 |
持久节点和临时节点能够与顺序节点组合,形成持久顺序节点和临时顺序节点。
Watcher机制
zookeeper中可在znode节点上添加一个watcher监听器来监听这个节点上的事件,一旦对应的事件被触发,Watcher的回调函数就会被执行,watcher机制是zookeeper的一个很重要的功能,利用watcher机制能够实现服务的注册与发现和分布式锁等功能。
一致性算法
拜占庭将军问题是点对点通信领域的一个基本问题,它描述了在不可靠信道上试图通过消息传递的方式就某个决议达成一致性是不可能的,换句话说paxos算法工作的前提是不存在拜占庭将军问题,也就是通信信道是安全的、可靠的。
一般情况下,分布式系统中各个模块之间的通信模型有两种,一种是共享内存,一种是消息传递,paxos算法就是基于消息传递模型。
Paxos算法
paxos是一种分布式一致性算法,它是zookeeper的zab协议的基础。
角色
在paxos算法中有三种角色,Proposer提案者,提议者,Acceptor表决者,Leaner学习者,分别对应了三种不同的行为,但很多时候,一个进程可以充当多种角色。
算法描述
每个提议者在提出提案时,都会首先获取一个全局唯一的、单调递增的编号N,然后将这个N与具体的提案进行绑定,每个表决者会在本地保存它曾经接受过的最大编号maxN,表决者只会接受编号大于本地maxN的提案。
paxo算法的执行过程划分为两个阶段,准备阶段(prepare)和接收阶段(accept)。
prepare阶段:
- 提议者创建一个编号为N的提案,然后向所有表决者发送prepare(N)的请求,用于试探集群是否支持该编号的提案
- 每个表决者在本地都保存着自己曾经接受过的提案的最大编号maxN。当表决者接收到parpare(N)请求时,会比较N与maxN的大小,有以下几种情况:
- 如果N小于maxN,则说明该提案已经过期,当前表决者采取不回应或回应Error的方式来拒绝prepare请求
- 如果N大于maxN,则说明该提案是可被接收的,当前表决这首先将该提案的编号N记录下来,然后给提案反馈,表示能够接收本次提案
- 在prepare阶段N不可能等于maxN,这是由于N的值是单调递增的;
accept阶段:
- 提案者发送prepare(N)后,若收到超过半数的表决者的反馈,就会将提案真正发送给所有的表决者;
- 当表决者收到提案者发送的提案后,会再次拿出自己曾经接收过的提案的最大编号maxN和曾经记录下的prepare阶段的最大编号,让N与它进行比较,如果N大于这两个编号,则当前表决者接受该提案,并反馈给提案者,若N小于这两个编号则采取不回应或回应Error的方式表示拒绝接受本次提案
- 如果提议者没有接受到半数以上的表决者的反馈,则重新申请提案编号,进入prepare阶段,直到提案被超过半数的表决者接受
- 如果提议者收到了超过半数表决者的反馈,则会向外广播两类信息
- 向曾经接受了提案的表决者发送”提交信号“,让他们执行其接受的提案
- 向未曾向其发送反馈的表决者发送”提案+提交信号“,让他们收到该提案后马上执行
活锁问题
由于Paxos算法中提案者在提交提案失败后会重复提交,并且每个节点都可以提交提案,所以存在活锁问题,FastPaxos算法对Paxos进行了优化,只允许一个节点提交提案,对提案的编号N具有唯一的操作权,这样就解决了活锁问题。
Zab协议
zab是zookeeper atomic broadcast的缩写,即zookeeper原子广播协议,是专门为zookeeper设计的一种支持崩溃恢复的原子广播协议,在zookeeper中,主要依赖zab协议来实现分布式系统的高可用以及保证数据的一致性。另外,zab协议是fast paxos算法的一种工业实现,但两者的目标不太一样,zab协议倾向于构建高可用系统,而paxos算法主要用于构建一个分布式一致性状态机,确保各个节点的状态都是一致的。
三种角色
- Leader:集群中唯一处理写请求的节点,负责进行投票的发起;
- Follower:接收客户端的请求,仅可处理读请求;将写请求转给leader;在选举leader的时候参与投票,具有选举权与被选举权;
- Observer:本质上就是没有选举权与被选举权的Follower;
三种数据
- zxid:一个Long类型的数据,高32位表示epoch,低32位表示事务id-xid
- epoch:每个leader都会有一个唯一的epoch
- xid:事务id,事务请求对应的流水号
处理事务请求
zab协议在处理事务请求的时候,采用两阶段提交,集群中的Leaner节点收到客户端的事务请求,会将请求转发给Leader服务器,然后再执行如下的具体过程:
Leader接收到事务请求后,生成一个全局唯一的64位zxid,然后将事务和zxid封装为Proposal,再将Proposal发送给各个Follower,当Follower接收提案后,先将提案的zxid与本地记录的最大zxid进行比较,如果当前提案的zxid大于本地的最大zxid,则将当前提案记录到本地并向Leader返回一个ack,当leader收到超过半数的ack后,leader就会向所有的follower发送commit消息,并向所有observer发送proposal,follower收到commit消息后,将日志中的事务正式更新到本地,observer收到proposal后,直接将事务更新到本地。
Leader选举
在集群启动或leader宕机时,会进行leader选举
- myid:是zk集群中服务器的唯一标识,例如有三个zk服务器,那么编号分别是1、2、3
- 逻辑时钟:是一个整数,在选举时被称为逻辑时钟,而在选举结束后称为epoch,也就是说epoch和logicclock是同一个值,是在不同情况下的不同名称
- 节点状态:zk集群中的每一台主机,在不同的阶段都会处于不同的状态,有四种,分别是LOOKING,FOLLOWING,OBSERVING和LEADING
Leader的选举过程主要包含五个步骤:(更改节点状态)、发起投票、接收投票、处理投票(验证)、统计投票、更改节点状态
进行Leader选举至少需要两台主机,这里以三台主机组成的集群为例来说明Leader的选举过程。
在集群初始化阶段,当第一台服务器Server1启动时,其会给自己投票,然后发布自己的投票结果,投票结果包含服务器的myid和zxid,使用(myid,zxid)来表示,此时Server1的投票为(1,0),由于其他机器还没有启动,所以它收不到反馈信息,Server1一直处于LOOKING状态,属于非服务状态。
当第二台服务器Server2启动时,这两台主机就可以相互通信,每台机器都试图找到Leader,选举过程如下:
- 每个Server发出一个投票。此时Server1的投票是(1,0),Server2的投票是(2,0),然后各自将这个投票发给集群中的其他机器
- 接收来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,比如检查是否是本轮投票,是否来自LOOKING状态的服务器
- 处理投票。针对每个投票,服务器都需要将别人的投票和自己的投票进行比较,比较的原则是优先检查zxid,zxid比较大的服务器优先作为leader,如果zxid相同,那么就比较myid,myid比较大的服务器作为leader服务器。
- 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有超过半数的机器收到了相同的投票信息。
- 更改服务器状态,一旦确定了Leader,每个服务器就会更新自己的状态为LEADING,如果是Follower,那么就变更为未FOLLOWING。
- 添加主机。在新的leader选举出来后Server3启动,其向发出新一轮选举,但由于当前集群中各个主机的状态并不是LOOKING,而是各司其职的服务状态,所以其职能是以Follower的身份加入到集群中。
在zookeeper运行期间,leader与非leader服务器各司其职,即便有非leader服务宕机或加入时,也不会影响leader,但如果是leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮的leader选举,其过程和启动时期的leader选举过程基本一致。
假设正在运行的右server1、server2和server3三台服务器,当前leader是server2,若某一时刻server2挂了,此时便开始新一轮的leader选举,选举过程如下:
- 变更状态。leader挂后,余下的非observer服务器会将自己的服务器状态由FOLLOWING变更为LOOKING,然后开始进入Leader选举过程
- 每个人server会发出一个投票,仍然会首先投自己,不过在运行期间每个服务器上的zxid可能是不同的,此时假定server1的zxid是1111,server3的zxid是3333,在第一轮投票中,server1和server3都投给自己,产生投票(1,111)和(3,333),然后各自将投票发送隔日集群中的所有机器
- 接收来自各个服务器的投票,与启动过程相同,集群的每个服务器收到投票后,首先判断该投票的有效性,即检查是否为本轮投票、是否来自LOOKING状态的服务器
- 处理投票,与启动过程相同,针对每个人投票,服务器都需要将别人的投票和自己 投票进行pk,对于server1而言,它的投票是(1,111),接收server3的投票为(3,333),其首先会比较zxid,server3的投票是333,大于server1的111,于是server1更新自己的投票为(3,333),然后重新投票,对于server3而言,无须更新自己的投票,只是再次向集群中所有主机发出与上一次投票即可。
- 统计投票,与启动时过程相同,对于server1和server3而言都统计出的投票为(3,333),此时便认为选出了leader,即server3
- 改变服务器的状态,与启动过程相同,一旦确定了Leader,每个服务就会更新自己的状态,server1变更为FOLLOWING,server3变更为LEADING;
Zookeeper的使用场景
- 配置维护
- 命名服务
- DNS服务
- 分布式同步
- 分布式锁
- 分布式队列