天天看点

Zookeeper原理与实战

前言

Zookeeeper介绍

  • 从服务角度来看:Zookeeper是一个分布式协调服务的开源框架,主要用来解决分布式集群中应用系统的一致性问题
  • 从数据结构角度来看:ZooKeeper本质上是一个分布式的小文件存储系统。提供基于类似于文件系统的目录树方式的数据存储,并且可以对树中的节点进行有效管理。从而用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理
  • 从设计模式角度来看:Zookeeper是一个分布式服务管理框架,它讲注册的服务作为数据进行存储,一旦某些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些服务做出相应的反应,从而实现集群中类似Master/Slave管理模式。

Zookeeper特性

  • 最终数据一致性:集群中每个服务器保存一份相同的数据副本,client无论连接到哪个服务器,展示的数据最终保证都是一致的。
  • 可靠性:如果消息被其中一台服务器接受,那么将被所有的服务器接受。
  • 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
  • 数据更新原子性:一次数据更新要么成功(半数以上节点成功),要么失败,不存在中间状态。
  • 实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。

Zookeeper使用场景

  • 配置管理:在我们的应用中除了代码外,还有各种配置文件,比如数据库连接等。如果我们配置非常多,有很多服务器都需要这个配置,而且还还需要经常去修改,这个时候往往需要寻找一种集中管理配置的服务。Zookeeper将配置信息放它的znode 下,当有配置发生改变时,也就是znode发生变化时,可以通过改变它某个目录节点的内容,并利用watcher通知给各个客户端,从而更改配置。
  • 名字服务:为了通过网络访问一个系统,我们得知道对方的IP地址,但是IP地址对人非常不友好,特别是在我们的服务特别多的时候,如果我们在本地保存服务的地址的时候将非常不方便,但是如果我们只需要访问一个统一的入口,那么维护起来将方便得多了。Zookeeper通过创建一个全局的路径,即是唯一的路径作为一个名字,指向集群中提供的服务的地址,或者一个远程的对象等。
  • 分布式锁:在一个分布式环境中,为了提高可靠性,我们的集群的每台服务器上都部署着同样的服务。但是,如果集群中的每个服务器使用相同的资源的话,那相互之间就要协调,而如果我们只让一个服务进行操作,那又存在单点,就可以使用分布式锁,在某个时刻只让一个服务去使用共享资源。zookeeper上的一个znode可以看作是一把锁,通过createznode的方式来实现。所有客户端都去创建/distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的distribute_lock节点就释放出锁。
  • 集群管理:在分布式的集群中,经常会由于硬件故障,软件故障,网络抖动等问题,有些节点会时常上下线切换。这个时候,集群中其他机器需要感知到这种变化,然后根据这种变化做出对应的决策。client端会对Zookeeper某个znode注册一个watcher监听事件,当该znode发生变化时,这些client会收到Zookeeper的通知,然后client可以根据znode变化来做出业务上的改变等。

Zookeeper原理

Zookeeper中的角色

  • Leader:领导者,负责进行投票的发起和决议,更新系统状态
  • Learner:
    • Follower:追随者接收客户请求并向客户端返回结果,在选主过程中进行投票
    • Observer:观察者不参与投票,仅同步leader的状态。目的是为了扩展系统,提高读取速度。
  • Client:请求发起方

保证事务的顺序一致性

Zookeeper采用了 递增的事务Id 来标识,所有的proposal(提议)都在被提出的时候加上了 zxid ,

zxid实际上是一个64位的数字,高32位是 epoch用来标识leader是否发生改变,如果有新的leader产生出来,epoch会自增,低32位用来递增计数。当新产生 proposal的时候,会依据数据库的两阶段过程,首先会向其他的server发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会 开始执行。

选举机制

当leader崩溃或者leader失去大多数的follower,这时zookeeper进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。

Zk的选举算法有两种,一种是基于basic paxos 实现的,另外一种是基于 fast paxos 算法实现的。系统默认的选举算法为fast paxos 。

Zookeeper 选主流程(fast paxos) fast paxos流程是在选举过程中,某Server首先向所有Server提议自己要 成为leader,当其它Server收到提议以后,解决epoch和zxid的冲突, 并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流 程,就一定能选举出Leader。

同步机制

Zookeeper使用了Zab协议来保证各个Server之间的同步。

Zab协议有两种模式,它们分别是 **恢复模式(选主) **和广播模式(同步)。

当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server 具有相同的系统状态。

Zookeeper就状态同步过程如下:

  1. Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超过半数节点的票数,它就可以当选准 Leader。
  2. Discovery(发现阶段):在这个阶段,Followers跟准Leader进行通信,同步Followers最近接收的事务提议。
  3. Synchronization(同步阶段):同步阶段主要是利用Leader前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后准Leader才会成为真正的Leader。
  4. Broadcast(广播阶段): 到了这个阶段,Zookeeper集群才能正式对外提供事务服务,并且Leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。

watch机制

ZooKeeper支持一种Watch操作,Client可以在某个ZNode上设置一个Watcher,来Watch该ZNode上的变化。如果该ZNode上有相应的变化,就会触发这个Watcher,把相应的事件通知给设置Watcher的Client。需要注意的是,ZooKeeper中的Watcher是一次性的,即触发一次就会被取消,如果想继续Watch的话,需要客户端重新设置Watcher。

watch机制的特点:

  1. 一次性触发数据发生改变时,一个watcher event会被发送到client, 但是client只会收到一次这样的信息。
  2. watcher event异步发送watcher的通知事件从server发送到client是 异步的,这就存在一个问题,不同的客户端和服务器之间通过socket进 行通信,由于网络延迟或其他因素导致客户端在不通的时刻监听到事件, 由于Zookeeper本身提供了 ordering guarantee,即客户端监听事件后, 才会感知它所监视znode发生了变化。所以我们使用Zookeeper不能期 望能够监控到节点每次的变化。Zookeeper只能保证最终的一致性,而无 法保证强一致性。
  3. 数据监视Zookeeper有数据监视和子数据监视getdata() and exists()设置数据监视,getchildren()设置了子节点监视。
  4. setData()执行成功则会触发znode上设置的data watch。一个 成功的create()操作会触发被创建的znode上的数据watch,以及其父节 点上的child watch。而一个成功的delete。操作将会同时触发一个znode 的data watch和child watch(因为这样就没有子节点了),同时也会触发其 父节点的child watch。
  5. 当一个客户端连接到一个新的服务器上时,watch将会被以任意会话 事件触发。当与一个服务器失去连接的时候,是无法接收到watch的。而 当client重新连接时,如果需要的话,所有先前注册过的watch,都会被 重新注册。通常这是完全透明的。只有在一个特殊情况下,watch可能会 丢失:对于一个未创建的znode的exist watch,如果在客户端断开连接期 间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个 watch事件可能会被丢失。
  6. Watch是轻量级的,其实就是本地JVM的Callback,服务器端只是存 了是否有设置了 Watcher的布尔类型

数据复制

Zookeeper作为一个集群提供一致的数据服务,自然,它要在所有机器间 做数据复制。

数据复制的好处:

  1. 容错:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作;
  2. 提高系统的扩展能力:把负载分布到多个节点上,或者增加节点来提高系统的负载能力;
  3. 提高性能:让客户端本地访问就近的节点,提高用户访问速度。

从客户端读写访问的透明度来看,数据复制集群系统分下面两种:

  1. 写主(WriteMaster):对数据的修改提交给指定的节点。读无此限制,可 以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称读 写分离;
  2. 写任意(Write Any):对数据的修改可提交给任意的节点,跟读一样。这 种情况下,客户端对集群节点的角色与变化透明。

对Zookeeper来说,它采用的方式是写任意。通过增加机器,它的读吞吐 能力和响应能力扩展性非常好,而写,随着机器的增多吞吐能力肯定下降(这也是它建立observer的原因),而响应能力则取决于具体实现方式,是延迟复制保持最终一致性,还是立即复制快速响应。

数据存储

  • DataTree:DataTree是内存数据存储的核心,是一个树结构,代表了内存中一份完整的数据。DataTree不包含任何与网络、客户端连接及请求处理相关的业务逻辑,是一个独立的组件。
  • DataNode:DataNode是数据存储的最小单元,其内部除了保存了结点的数据内容、ACL列表、节点状态之外,还记录了父节点的引用和子节点列表两个属性,其也提供了对子节点列表进行操作的接口。
  • ZKDatabase:Zookeeper的内存数据库,管理Zookeeper的所有会话、DataTree存储和事务日志。ZKDatabase会定时向磁盘dump快照数据,同时在Zookeeper启动时,会通过磁盘的事务日志和快照文件恢复成一个完整的内存数据库。

ZooKeeper Client API

ZooKeeper Client Library提供了丰富直观的API供用户程序使用,下面是一些常用的API:

  • create(path, data, flags): 创建一个ZNode, path是其路径,data是要存储在该ZNode上的数据,flags常用的有: PERSISTEN, PERSISTENT_SEQUENTAIL, EPHEMERAL, EPHEMERAL_SEQUENTAIL
  • delete(path, version): 删除一个ZNode,可以通过version删除指定的版本, 如果version是-1的话,表示删除所有的版本
  • exists(path, watch): 判断指定ZNode是否存在,并设置是否Watch这个ZNode。这里如果要设置Watcher的话,Watcher是在创建ZooKeeper实例时指定的,如果要设置特定的Watcher的话,可以调用另一个重载版本的exists(path, watcher)。以下几个带watch参数的API也都类似
  • getData(path, watch): 读取指定ZNode上的数据,并设置是否watch这个ZNode
  • setData(path, watch): 更新指定ZNode的数据,并设置是否Watch这个ZNode
  • getChildren(path, watch): 获取指定ZNode的所有子ZNode的名字,并设置是否Watch这个ZNode
  • sync(path): 把所有在sync之前的更新操作都进行同步,达到每个请求都在半数以上的ZooKeeper Server上生效。path参数目前没有用
  • setAcl(path, acl): 设置指定ZNode的Acl信息
  • getAcl(path): 获取指定ZNode的Acl信息

Zookeeper安装

  1. 下载

    下载地址:https://zookeeper.apache.org/releases.html#download

  2. 解压缩(我是放在/usr/local/zk目录下)

    tar -zxvf apache-zookeeper-3.6.3-bin.tar.gz

  3. 修改zookeeper配置
    #zookeeper根目录改个名字
    mv apache-zookeeper-3.6.3-bin zookeeper
    #进入conf目录下
    cd zookeeper/conf
    #zookeeper的配置文件zoo.cfg,可复制conf/zoo_sample.cfg
    cp zoo_sample.cfg zoo.cfg
    vim zoo.cfg
    #修改内容
    dataDir=/usr/local/zk/zookeeper/data  
    dataLogDir=/usr/local/zk/zookeeper/log  
    #添加内容(节点地址:同步端口:选举端口) 
    server.1=192.168.124.18:2881:3881  
    server.2=192.168.124.18:2882:3882  
    server.3=192.168.124.18:2883:3883
               
  4. 配置环境变量
    vi /etc/profile
    #添加以下内容
    #export ZOOKEEPER=/usr/local/zk/zookeeper
    #export PATH=$PATH:$ZOOKEEPER/bin
    
    #使其生效
    source /etc/profile
               
  5. 创建myid文件
    mkdir -p /usr/local/zk/zookeeper/zkdata
    cd  /usr/local/zk/zookeeper/zkdata  
    echo 1 > myid
               
  6. 分发安装包到其他机器(我这里在单机模拟集群)

    cp -r zookeeper zookeeper2

    cp -r zookeeper zookeeper3

  7. 修改其他机器的配置文件

    在zookeeper2上:

    vim /usr/local/zk/zookeeper2/zkdata/myid

    修改myid为:2

    在zookeeper3上:

    vim /usr/local/zk/zookeeper2/zkdata/myid

    修改myid为:3
  8. 启动zookeeper

    zkServer.sh start start-foreground

  9. 查看运行状态

    zkServer.sh status

  10. 关闭Zookeeper服务

    zkServer.sh stop

继续阅读