简介
- zookeeper是一个分布式协调服务(为其他分布式程序服务)
- 它自身就是一个分布式服务
- 提供的服务:主从协调、服务器节点动态上下线感知、统一配置管理、分布式共享锁、统一名称服务……
- 核心功能:
- 管理用户程序提交的数据
- 为用户程序提供数据节点监听服务
- 角色:leader;follower(observer)
- Leader作为整个ZooKeeper集群的主节点,负责响应所有对ZooKeeper状态变更的请求。它会将每个状态更新请求进行排序和编号,以便保证整个集群内部消息处理的FIFO。
- Follower的逻辑就比较简单了。除了响应本服务器上的读请求外,follower还要处理leader的提议,并在leader提交该提议时在本地也进行提交。
- 如果ZooKeeper集群的读取负载很高,或者客户端多到跨机房,可以设置一些observer服务器,以提高读取的吞吐量。Observer和Follower比较相似,只有一些小区别:首先observer不属于法定人数,即不参加选举也不响应提议;其次是observer不需要将事务持久化到磁盘,一旦observer被重启,需要从leader重新同步整个名字空间。
机制
只要有半数以上的节点存活,集群就能提供服务(==适合装在奇数台机器上==)
特性
- Zookeeper:一个leader,多个follower组成的集群
- 全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
- 分布式读写,更新请求转发,由leader实施
- 更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
- 数据更新原子性,一次数据更新要么成功,要么失败
- 实时性,在一定时间范围内,client能读到最新数据
数据结构
- 层次化的目录结构,命名符合常规文件系统规范
- 每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识
- 节点Znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点)
- 客户端应用可以在节点上设置监视器
节点类型
- Znode有两种类型:
短暂(ephemeral)(断开连接自己删除)
持久(persistent)(断开连接不删除) - Znode有四种形式的目录节点(默认是persistent )
- PERSISTENT
- PERSISTENT_SEQUENTIAL(持久序列/test0000000019 )
- EPHEMERAL
- EPHEMERAL_SEQUENTIAL
- 创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护
- 在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序
================================重点内容选举机制==================================
zookeeper的选举机制(全新集群paxos:算法)
举个简单明了的例子
共五人参加选举,第一轮只能选自己,获得选票大于5/2(即2)时,成为leader.
回合一:
A启动,开始第一轮选举,A选择自己.
投票结果:[(A-->A)]
A只获得一票,小于2所以不能成为leader,进入LOOKING(竞选状态)。
回合二:
B启动起来,投票给自己,告诉其他人,并接收到A的(第一回合)投票信息;
此时,A接收到B的(第二回合)投票信息,A知道进入第二回合了,重新投票,由于B的leaderID大,所以A投给了B.
投票结果:[(A-->B),(B-->B)]
B获得两票,但是仍没有超过2,所以A,B进入LOOKING(竞选状态)。
回合三:
C启动起来,投票给自己,告诉其他人,并接收到了A和B的投票信息
此时,AB也接收到了C的投票信息,一看第三回合了,重新投票,由于C的leaderID最大,
就将自己的一票都给了C
投票结果:[(A-->C),(B-->C),(C-->C)]
C获得三票,大于2,所以C进入leaderinng状态
剩下回合,由于C获得的票数已经超过半数,所以D和E的投票是不能改变现实的
=============有人可能会疑问,D的leader比C大啊,他们应该都选D啊?=================
不要误解了,当集群中没有leader的时候,大家在新的回合会将自己的一票投给leaderID大的同学,
但是如果已经有了leader,那在新的回合将不会改变他们的投票信息
非全新集群的选举机制(数据恢复)
那么,初始化的时候,是按照上述的说明进行选举的,但是当zookeeper运行了一段时间之后,有机器down掉,重新选举时,选举过程就相对复杂了。
需要加入数据id、leader id和逻辑时钟。
数据id:数据新的id就大,数据每次更新都会更新id。
Leader id:就是我们配置的myid中的值,每个机器一个。
逻辑时钟:这个值从0开始递增,每次选举对应一个值,也就是说: 如果在同一次选举中,那么这个值应该是一致的 ; 逻辑时钟值越大,说明这一次选举leader的进程更新.
选举的标准就变成:
1. 逻辑时钟小的选举结果被忽略,重新投票
2. 统一逻辑时钟后,数据id大的胜出
3. 数据id相同的情况下,leader id大的胜出
根据这个规则选出leader。
tips:
选举状态
LOOKING,竞选状态。
FOLLOWING,随从状态,同步leader状态,参与投票。
OBSERVING,观察状态,同步leader状态,不参与投票。
LEADING,领导者状态。
选举消息内容
在投票完成后,需要将投票信息发送给集群中的所有服务器,它包含如下内容。
服务器ID
数据ID
逻辑时钟
选举状态
数据id越大权重越大,优先级越高,
服务器id越大权重越大,优先级越高,
逻辑时钟(即选举编号,每次选举的逻辑时钟都不同.目的:为了保证投票的是同一个选举会议)
补充
源码
QuorumPeer
主要看这个类,只有LOOKING状态才会去执行选举算法。每个服务器在启动时都会选择自己做为领导,然后将投票信息发送出去,循环一直到选举出领导为止。
public void run() {
//.......
try {
while (running) {
switch (getPeerState()) {
case LOOKING:
if (Boolean.getBoolean("readonlymode.enabled")) {
//...
try {
//投票给自己...
setCurrentVote(makeLEStrategy().lookForLeader());
} catch (Exception e) {
//...
} finally {
//...
}
} else {
try {
//...
setCurrentVote(makeLEStrategy().lookForLeader());
} catch (Exception e) {
//...
}
}
break;
case OBSERVING:
//...
break;
case FOLLOWING:
//...
break;
case LEADING:
//...
break;
}
}
} finally {
//...
}
}
FastLeaderElection
它是zookeeper默认提供的选举算法,核心方法如下:具体的可以与本文上面的流程图对照。
public Vote lookForLeader() throws InterruptedException {
//...
try {
HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();
int notTimeout = finalizeWait;
synchronized(this){
//给自己投票
logicalclock.incrementAndGet();
updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
}
//将投票信息发送给集群中的每个服务器
sendNotifications();
//循环,如果是竞选状态一直到选举出结果
while ((self.getPeerState() == ServerState.LOOKING) &&
(!stop)){
Notification n = recvqueue.poll(notTimeout,
TimeUnit.MILLISECONDS);
//没有收到投票信息
if(n == null){
if(manager.haveDelivered()){
sendNotifications();
} else {
manager.connectAll();
}
//...
}
//收到投票信息
else if (self.getCurrentAndNextConfigVoters().contains(n.sid)) {
switch (n.state) {
case LOOKING:
// 判断投票是否过时,如果过时就清除之前已经接收到的信息
if (n.electionEpoch > logicalclock.get()) {
logicalclock.set(n.electionEpoch);
recvset.clear();
//更新投票信息
if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
updateProposal(n.leader, n.zxid, n.peerEpoch);
} else {
updateProposal(getInitId(),
getInitLastLoggedZxid(),
getPeerEpoch());
}
//发送投票信息
sendNotifications();
} else if (n.electionEpoch < logicalclock.get()) {
//忽略
break;
} else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
proposedLeader, proposedZxid, proposedEpoch)) {
//更新投票信息
updateProposal(n.leader, n.zxid, n.peerEpoch);
sendNotifications();
}
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
//判断是否投票结束
if (termPredicate(recvset,
new Vote(proposedLeader, proposedZxid,
logicalclock.get(), proposedEpoch))) {
// Verify if there is any change in the proposed leader
while((n = recvqueue.poll(finalizeWait,
TimeUnit.MILLISECONDS)) != null){
if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
proposedLeader, proposedZxid, proposedEpoch)){
recvqueue.put(n);
break;
}
}
if (n == null) {
self.setPeerState((proposedLeader == self.getId()) ?
ServerState.LEADING: learningState());
Vote endVote = new Vote(proposedLeader,
proposedZxid, proposedEpoch);
leaveInstance(endVote);
return endVote;
}
}
break;
case OBSERVING:
//忽略
break;
case FOLLOWING:
case LEADING:
//如果是同一轮投票
if(n.electionEpoch == logicalclock.get()){
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
//判断是否投票结束
if(termPredicate(recvset, new Vote(n.leader,
n.zxid, n.electionEpoch, n.peerEpoch, n.state))
&& checkLeader(outofelection, n.leader, n.electionEpoch)) {
self.setPeerState((n.leader == self.getId()) ?
ServerState.LEADING: learningState());
Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch);
leaveInstance(endVote);
return endVote;
}
}
//记录投票已经完成
outofelection.put(n.sid, new Vote(n.leader,
IGNOREVALUE, IGNOREVALUE, n.peerEpoch, n.state));
if (termPredicate(outofelection, new Vote(n.leader,
IGNOREVALUE, IGNOREVALUE, n.peerEpoch, n.state))
&& checkLeader(outofelection, n.leader, IGNOREVALUE)) {
synchronized(this){
logicalclock.set(n.electionEpoch);
self.setPeerState((n.leader == self.getId()) ?
ServerState.LEADING: learningState());
}
Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch);
leaveInstance(endVote);
return endVote;
}
break;
default:
//忽略
break;
}
} else {
LOG.warn("Ignoring notification from non-cluster member " + n.sid);
}
}
return null;
} finally {
//...
}
}