ZooKeeper:架构和算法

ZooKeeper主要用来解决分布式应用场景中存在的一些问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置管理等。

它支持Standalone模式和分布式模式,在分布式模式下,能够为分布式应用提供高性能和可靠地协调服务,而且使用ZooKeeper可以大大简化分布式协调服务的实现,为开发分布式应用极大地降低了成本。

总体架构

ZooKeeper分布式协调服务框架的总体架构,如图所示:

image

ZooKeeper集群由一组Server节点组成,这一组Server节点中存在一个角色为Leader的节点,其他节点都为Follower。

  • 写请求:写请求会序列化,每个写请求转发给leader通过ZAB协议达到一致性和顺序性(所有节点更新顺序一致)。
  • 读请求:与chubby不同,chubby所有读写请求必须转发给leader,在zookeeper中,所有的server(leader和follower)都可以响应读请求。这样会带来不一致的问题,zab协议还没有把写消息同步到所有节点上。zookeeper可以通过sync调用(怎么实现的?)强制同步。
  • zxid编号:每次读写,zookeeper都会返回一个zxid编号,zookeeper保证返回的数据不会比客户端传过来的zxid编号更新
  • 模糊快照和日志:zookeeper周期性地将内存中的数据保存在磁盘中形成模糊快照(模糊的含义是不一定是最新的),zookeeper将更新操作都会先写入磁盘。zookeeper保证更新操作都是“幂等的”,所以可以通过模糊快照和日志恢复内存数据。

数据模型

image

zookeeper通过树形命名空间来进行整个系统的协调,其中的每个节点称为znode,znode有两种持久节点和临时节点。临时节点和会话绑定,会话下线临时节点删除(心跳监控是否宕机)

zookeeper的全部数据都保存在内存中,以此来实现高吞吐,低延迟。

API

//创建节点,flag可以指定递增,临时节点
string create(path, data, acl, flags)

//删除节点
void delete(path, expectedVerstion)

//改
stat setData(path, data, expectedVersion)

//查
(data, stat)  getData(path, watch)

//存在
stat exist(path, watch)

//获取孩子节点
String[] getChildren(path, watch)

//同步
void sync(path)

所有有关查的api(3个),可以设置监视器,状态发生变化可以通知

主要功能

领导者选举

image

在一个特定的目录下,创建一个leader临时节点。热备机周期性地检查这个节点是否存在,如果不存在,就尝试在这个目录下创建leader节点,升级为leader。

(data, stat) = zookeeper.get(handle, "/services/myservice/leader",true);
if(stat == none)
    path = zookeeper.create(handle, "/services/myservice/leader", 临时节点)
    if(path = none)//失败,获取当前leader
        (data, stat) = zookeeper.get(handle, "/services/myservice/leader",true);
    else
        成功,自己是leader

配置管理(pub-sub服务)

创建特定的节点,用于管理集群的配置信息。所有客户端看见的都是一致的内容,并且可以设置watch在节点内容变更的时候通知客户端。

注意:不要使用zookeeper同步大块数据,进行会急剧下降。会走一致性协议同步,写磁盘,然后在反应在内存中。
image

成员管理

成员管理的目的就是,集群内某个节点宕机或者新加入节点,zookeeper可以及时感知。实现方式是为每台机器创建临时节点,临时节点和会话绑定。同时,监控客户端可以通过getChildren设置watch,临时节点有变更可以及时通知。

image

任务分配

image

  1. 新任务到来:在tasks目录下创建任务节点
  2. 任务分配:监控进程感知到tasks目录下的节点变化,触发任务分配。图中task1分配到machine1的目录下,代表task1分配给machine1
  3. 任务执行:machine1发现自己目录下的任务,执行该任务。结束后删除machine1目录下的task1和task目录下的task1。

锁服务

创建互斥锁

  1. 在l目录下,创建以lock为前缀的递增临时节点,返回节点编号
  2. 获取l目录下所有节点信息
  3. 如果刚刚创建的编号是所有节点编号最小的(最先创建节点的),获得锁
  4. 没有获得锁,进入第2步

创建读写锁

创建写锁

  1. 在l目录下,创建以write为前缀的递增临时节点,返回节点编号
  2. 获取l目录下所有节点信息
  3. 如果刚刚创建的编号是所有节点编号最小的(最先创建节点的),获得锁
  4. 没有获得锁,进入第2步

创建读锁

  1. 在l目录下,创建以read为前缀的递增临时节点,返回节点编号
  2. 获取l目录下所有节点信息
  3. 如果刚刚创建的编号比所有write前缀的编号小,获得读锁
  4. 没有获得锁,进入第2步

释放锁

删除临时节点

双向路障同步

任务统一开始

  1. 在路障节点下创建当前任务节点,代表该任务到达路障
  2. 检测路障节点下任务个数是否足够,足够代表可以继续统一执行

任务统一离开

  1. 任务结束后,任务路障节点下的任务节点
  2. 如果路障节点下没有任务节点,代表所有任务已经执行结束

ZAB协议(ZooKeeper Atomic Broadcast原子消息广播协议)

zab协议所有事务请求必须由leader协调,首先leader发起proposal消息,大多数server同意后,然后leader发送commit消息。

zxid编号

  1. 低32位为计数器,客户端每次请求+1
  2. 高32位为epochID,每次选举新leader+1

状态和阶段

  1. Looking:系统刚启动时或者Leader崩溃后正处于选举状态
  2. Following:Follower节点所处的状态,Follower与Leader处于数据同步阶段
  3. Leading:Leader所处状态,当前集群中有一个Leader为主进程

image

zookeeper主要分为5个阶段,选举,发现,同步,广播

  1. 选举:Looking状态中选举出Leader节点,Leader的lastZXID总是最新的
  2. 发现:Follower节点向准Leader推送FOllOWERINFO,该信息中包含了上一周期的epoch,接受准Leader的NEWLEADER指令,检查newEpoch有效性,准Leader要确保Follower的epoch与ZXID小于或等于自身的
  3. 同步:将Follower与Leader的数据进行同步,由Leader发起同步指令,最总保持集群数据的一致性
  4. 广播:Leader广播Proposal与Commit,Follower接受Proposal与Commit;

选举

zab必须确保选举出来的leader具有最大的zxid(这和raft很像啊)。这里要注意一下,和raft一样,选举可能会出现两种结果:

  1. 上一轮的多数派(此时leader zxid可能不是最大的,需要同步的时候调用trunc)
  2. 上一轮的少数派,leader zxid真的是最大的(但是这个最大的zxid没有提交,在同步阶段提交?)

过程详解:

  1. 每个Follower都向其他节点发送选自身为Leader的Vote投票请求,等待回复;
  2. Follower接受到的Vote如果比自己的ZXID更新时则投票,并更新自身的Vote,否则拒绝投票;
  3. 每个Follower中维护着一个投票记录表,当某个节点收到过半的投票时,结束投票并把该Follower选为Leader,投票结束;

恢复(发现和同步)

发现

  1. leader生成新的zxid和epoch,接受follower发送来的FOllOWERINFO(含有当前节点的LastZXID)
  2. leader向follower发送NEWLEADER;Leader根据follower发送过来的LastZXID根据数据更新策略向Follower发送更新指令;

同步

  1. SNAP:如果follower数据太老,epoch还在上上一轮,leader将发送快照snap指令给follower同步
  2. DIFF:正常同步阶段
  3. TRUNC:如果Follower是上一轮的少数派(通过对比ztid),发送TRUNC指令让follower丢弃这段数据

广播

进入正常leader提交阶段,产生递增的zxid,接受半数投票后,再提交。

与raft的区别

我觉得zab和raft没有本质的区别,它们唯一的不同点就是如何处理上一个leader的残留日志。

  • raft中,需要本轮iterm有日志提交后,才提交以前的
  • zab中,在同步阶段全部解决
posted on 2017-05-26 18:10  bitError  阅读(3752)  评论(0编辑  收藏  举报