前言
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就状态同步过程如下:
- Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超过半数节点的票数,它就可以当选准 Leader。
- Discovery(发现阶段):在这个阶段,Followers跟准Leader进行通信,同步Followers最近接收的事务提议。
- Synchronization(同步阶段):同步阶段主要是利用Leader前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后准Leader才会成为真正的Leader。
- Broadcast(广播阶段): 到了这个阶段,Zookeeper集群才能正式对外提供事务服务,并且Leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
watch机制
ZooKeeper支持一种Watch操作,Client可以在某个ZNode上设置一个Watcher,来Watch该ZNode上的变化。如果该ZNode上有相应的变化,就会触发这个Watcher,把相应的事件通知给设置Watcher的Client。需要注意的是,ZooKeeper中的Watcher是一次性的,即触发一次就会被取消,如果想继续Watch的话,需要客户端重新设置Watcher。
watch机制的特点:
- 一次性触发数据发生改变时,一个watcher event会被发送到client, 但是client只会收到一次这样的信息。
- watcher event异步发送watcher的通知事件从server发送到client是 异步的,这就存在一个问题,不同的客户端和服务器之间通过socket进 行通信,由于网络延迟或其他因素导致客户端在不通的时刻监听到事件, 由于Zookeeper本身提供了 ordering guarantee,即客户端监听事件后, 才会感知它所监视znode发生了变化。所以我们使用Zookeeper不能期 望能够监控到节点每次的变化。Zookeeper只能保证最终的一致性,而无 法保证强一致性。
- 数据监视Zookeeper有数据监视和子数据监视getdata() and exists()设置数据监视,getchildren()设置了子节点监视。
- setData()执行成功则会触发znode上设置的data watch。一个 成功的create()操作会触发被创建的znode上的数据watch,以及其父节 点上的child watch。而一个成功的delete。操作将会同时触发一个znode 的data watch和child watch(因为这样就没有子节点了),同时也会触发其 父节点的child watch。
- 当一个客户端连接到一个新的服务器上时,watch将会被以任意会话 事件触发。当与一个服务器失去连接的时候,是无法接收到watch的。而 当client重新连接时,如果需要的话,所有先前注册过的watch,都会被 重新注册。通常这是完全透明的。只有在一个特殊情况下,watch可能会 丢失:对于一个未创建的znode的exist watch,如果在客户端断开连接期 间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个 watch事件可能会被丢失。
- Watch是轻量级的,其实就是本地JVM的Callback,服务器端只是存 了是否有设置了 Watcher的布尔类型
数据复制
Zookeeper作为一个集群提供一致的数据服务,自然,它要在所有机器间 做数据复制。
数据复制的好处:
- 容错:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作;
- 提高系统的扩展能力:把负载分布到多个节点上,或者增加节点来提高系统的负载能力;
- 提高性能:让客户端本地访问就近的节点,提高用户访问速度。
从客户端读写访问的透明度来看,数据复制集群系统分下面两种:
- 写主(WriteMaster):对数据的修改提交给指定的节点。读无此限制,可 以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称读 写分离;
- 写任意(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安装
- 下载
下载地址:https://zookeeper.apache.org/releases.html#download - 解压缩(我是放在/usr/local/zk目录下)
tar -zxvf apache-zookeeper-3.6.3-bin.tar.gz
- 修改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
- 配置环境变量
vi /etc/profile
#添加以下内容
#export ZOOKEEPER=/usr/local/zk/zookeeper
#export PATH=$PATH:$ZOOKEEPER/bin
#使其生效
source /etc/profile
- 创建myid文件
mkdir -p /usr/local/zk/zookeeper/zkdata
cd /usr/local/zk/zookeeper/zkdata
echo 1 > myid
- 分发安装包到其他机器(我这里在单机模拟集群)
cp -r zookeeper zookeeper2
cp -r zookeeper zookeeper3
- 修改其他机器的配置文件
在zookeeper2上:vim /usr/local/zk/zookeeper2/zkdata/myid
修改myid为:2
在zookeeper3上:vim /usr/local/zk/zookeeper2/zkdata/myid
修改myid为:3 - 启动zookeeper
zkServer.sh start start-foreground
- 查看运行状态
zkServer.sh status
- 关闭Zookeeper服务
zkServer.sh stop