05.Zookeeper集群
Zookeeper介绍
Zookeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
Zookeeper具有高性能,高可用性,严格排序的访问的特性。Zookeeper的高性能意味着它可以在大型的分布式系统中使用。可靠性方面使它不会产生单点故障。严格的排序意味着可以在客户端上实现复杂的同步原语。
详细测试对比可看官方文档 : 官方文档地址
Zookeeper的读写机制
- Zookeeper是一个由多个server组成的集群
- 一个leader,多个follower
- 每个server保存一份数据副本
- 全局数据一致
- 分布式读写
- 更新请求转发,由leader实施
Zookeeper 的保证
- 更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
- 数据更新原子性,一次数据更新要么成功,要么失败
- 全局唯一数据视图,client无论连接到哪个server,数据视图都是一致的
- 实时性,在一定事件范围内,client能读到最新数据
集群中的角色
- 领导者(leader),负责进行投票的发起和决议,更新系统状态
- 学习者(learner),包括跟随者(follower)和观察者(observer),follower用于接受客户端请求并想客户端返回结果,在选主过程中参与投票
- Observer可以接受客户端连接,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展系统,提高读取速度,放大查询能力
- 客户端(client),请求发起方
-
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
-
为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
每个Server在工作过程中有三种状态:
- LOOKING:当前Server不知道leader是谁,正在搜寻
- LEADING:当前Server即为选举出来的leader
- FOLLOWING:leader已经选举出来,当前Server与之同步
Follower主要有四个功能
- 向Leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);
- 接收Leader消息并进行处理;
- 接收Client的请求,如果为写请求,发送给Leader进行投票;
- 返回Client结果。
Follower的消息循环处理如下几种来自Leader的消息:
- PING消息: 心跳消息;
- PROPOSAL消息:Leader发起的提案,要求Follower投票;
- COMMIT消息:服务器端最新一次提案的信息;
- UPTODATE消息:表明同步完成;
- .REVALIDATE消息:根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息;
- SYNC消息:返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。
其他文档:http://www.cnblogs.com/lpshou/archive/2013/06/14/3136738.html
Zookeeper中节点的状态信息各字段含义
@InterfaceAudience.Public
public class Stat implements Record {
private long czxid; // 该数据节点被创建时的事务id
private long mzxid; // 该数据节点被修改时最新的事务id(集群中会不一样)
private long ctime; // 该数据节点创建时间
private long mtime; // 该数据节点最后修改时间
private int version; // 当前节点版本号(可以理解为修改次数,每修改一次值+1)
private int cversion;// 子节点版本号(子节点修改次数,每修改一次值+1)
private int aversion; // 当前节点acl版本号(acl节点被修改次数,每修改一次值+1)
private long ephemeralOwner; // 临时节点标示,当前节点如果是临时节点,则存储的创建者的会话id(sessionId),如果不是,那么值=0
private int dataLength;// 当前节点数据长度
private int numChildren; // 当前节点子节点个数
private long pzxid; // 当前节点的父级节点事务ID
public Stat() {
}
}
其中zxid表示的是zookeeper的事务ID,由64位数字组成,分为高32位和低32位
高32位:Epoch周期数,值为最新的领导的对应的id,其实就是就是一个递增的数字
低32位:计数器,一个递增的计数器,当处理了一个事务,值+1
zxid生成的规则:ZxidUtils.makeZxid
public class ZxidUtils {
public static long getEpochFromZxid(long zxid) {
return zxid >> 32L;
}
public static long getCounterFromZxid(long zxid) {
return zxid & 0xffffffffL;
}
public static long makeZxid(long epoch, long counter) {
return (epoch << 32L) | (counter & 0xffffffffL);
}
public static String zxidToString(long zxid) {
return Long.toHexString(zxid);
}
}
Zookeeper节点数据操作流程
详细说明
- 在Client向Follwer发出一个写的请求
- Follwer把请求发送给Leader
- Leader接收到以后开始发起投票并通知Follwer进行投票
- Follwer把投票结果发送给Leader
- Leader将结果汇总后如果需要写入,则开始写入同时把写入操作通知给Leader然后commit;
- Follwer把请求结果返回给Client
Zookeeper集群的两种运行状态
leader可用状态
leader不可用状态
不可用状态恢复到可用状态根据官方测试小于200ms
Zookeeper的集群选举
Leader选举是Zookeeper中最重要的技术之一,也是保证分布式数据一致性的关键所在。
服务器启动时期的选举
服务器启动时期的Leader选举
Leader选举的时候,需要注意的是,隐式条件便是Zookeeper的集群规模至少是2台机器,这里我们以3台机器组成的服务器集群为例。在服务器集群初始化阶段,当有一台服务器(我们假设这台机器的myid为1,因此称其为Server1)启动的时候,它是无法完成Leader选举的。当第二胎机器(同样,我们假设这台服务器的myid是2,称其为Server2)也启动后,此时这两台机器已经能够进行互相通信,每台机器都试图找到一个Leader,于是便进入了Leader选举流程。
-
每个Server会发出一个投票,由于是初始情况,因此对于Server1和Server2来说,都会将自己作为Leader服务器来进行投票,每次投票包含最基本的元素有:所推举的服务器的myid和ZXID,我们以(myid,ZXID)的形式来表示。因为是初始化阶段,因此无论是Server1还是Server2,都会投给自己,即Server1的投票为(1,0),Server2的投票为(2,0),然后各自将这个投票发给集群中其他所有机器。
-
接收来自各个服务器的投票,每个服务器都会接收来自其他服务器的投票。集群中的每个服务器在接收到投票后,首先会判断该投票的有效性,包括检查是否是本轮投票,是否来自LOOKING状态的服务器。
-
处理投票,在接收到来自其他服务器的投票后,针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK的步骤及处理流程如下:
-
统计投票,每次投票后,服务器都会统计所有投票,判断是否已经有过半的及其接收到相同的投票信息。对于Server1和Server2服务器来说,都统计出集群中已经有两台机器接受了(2,0)这个投票信息。这里我们需要对“过半”的概念做一个简单的介绍。所有“过半”就是指大于集群机器数量的一半,即大于或等于(n/2+1)。对于这里由3台机器构成的集群,大于等于2台即为达到“过半”要求。,当选举票数机器相同
优先检查ZXID,ZXID比较大的服务器优先作为Leader 如果ZXID相同的话,那么就比较myid。myid比较大的服务器作为Leader服务器
-
改变服务器状态,一旦确定Leader,每个服务器就会更新自己的状态。如果是Follower,那么就变更为FOLLOWING,如果是Leader,那么就变更为LEADING。
-
服务器运行时期的Leader选举
在Zookeeper集群正常运行过程中,一旦选出一个Leader,那么所有服务器的集群角色一般不会再发生变化,也就是说,Leader服务器将一直作为集群的Leader,即使集群中有非Leader挂了或有新机器加入集群也不会影响Leader。但是一旦Leader所在机器挂了,那么整个集群将暂时无法对外提供服务,而是进入新一轮的Leader选举。服务器运行期间的Leader选举和启动时期的Leader选举基本过程一致的。
Zookeeper的Watch
Zookeeper提供了数据的发布/订阅功能,多个订阅者可同时监听某一特定主题对象,当该主题对象的自身状态发生变化时(例如节点内容改变、节点下的子节点列表改变等),会实时、主动通知所有订阅者。
Zookeeper采用了Watcher机制实现数据的发布/订阅功能。该机制在被订阅对象发生变化时会异步通知客户端,因此客户端不必在Watcher注册后轮询阻塞,从而减轻了客户端压力。
Watcher机制实际上与观察者模式类似,也可看作是一种观察者模式在分布式场景下的实现方式。
watcher架构
Watcher实现由三个部分组成:
- Zookeeper服务端;
- Zookeeper客户端;
- 客户端的ZKWatchManager对象;
客户端首先将Watcher注册到服务端,同时将Watcher对象保存到客户端的Watch管理器中。当Zookeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的Watch管理器会触发相关Watcher来回调相应处理逻辑,从而完成整体的数据发布/订阅流程。
此时小红旗是一个watcher,当小红旗被创建并注册到node1节点(会有相应的API实现)后,就会监听node1+node_a+node_b或node_a+node_b。这里两种情况是因为在创建watcher注册时会有多种途径。并且watcher不能监听到孙节点。注意注意注意,watcher设置后,一旦触发一次后就会失效,如果要想一直监听,需要在process回调函数里重新注册相同的 watcher。
Watcher特性
特性 | 说明 |
---|---|
一次性 | Watcher是一次性的,一旦被触发就会移除,再次使用时需要重新注册 |
客户端顺序回调 | Watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态。一个Watcher回调逻辑不应该太多,以免影响别的watcher执行 |
轻量级 | WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容; |
时效性 | Watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知; |
Zookeeper顺序一致性简单验证
-
启动三个1,2,3三个节点(共四个节点),三个节点已经可以选举出leader
./bin/zkServer.sh start-foreground
节点1日志
节点2日志
节点3日志
-
启动节点4
-
连接3,4节点并新增数据,会发现cZxid会自增
./bin/zkCli.sh -server 127.0.0.1:12183
./bin/zkCli.sh -server 127.0.0.1:12184
create -s /ooxx/xxx
get -s /ooxx/xxx
当进行修改时,会自动在mzxid(该数据节点被修改时最新的事务id)字段加1
4.在此时中断leader进程,并在4节点继续新增相关数据
mZxid前两次(剩余2个following节点)事务是进行了数据同步,第三次事务才是修改/ooxx节点数据
5.不同客户端创建同一个节点
leader会在此节点进行递增,不会覆盖创建,规避数据被覆盖
当删除节点重新创建,但是数据名会继续递增(leade内部维护)
Zookeeper的应用场景
- 统一的配置管理 ------------节点下可以存储1M的数据
- 分组管理 --------------Zookeeper的树结构,可以有多个节点
- 统一命名 -------------顺序一致性的能力(不会覆盖数据)
- 分布式同步 -------------临时节点
- 分布式锁 ---------临时节点下放置分布式锁(session挂掉自动消失,就会释放锁)
- 带事务(队列形式)的公平锁 --------------在持久的父节点下,建立多个同级同名(create -s 会自动的添加序列)的临时节点(可以持有多把锁),后面的锁盯住前面的锁(01-02-03)
- HA,选主(hadoop)
- 发布订阅