zookeeper

简介

zookeeper是一种分布式协调服务,用于管理大型主机,在分布式环境中协调和管理服务是一个复杂的过程.zookeeper通过其简单的架构和API解决了这个问题

zookeeper允许开发人员专注于核心应用程序逻辑,而不担心应用程序的分布式特性

场景

  • 分布式协调组件:在分布式系统中需要有zookeeper作为分布式协调组件,协调分布式系统中的状态
  • 分布式锁:zk在实现分布式锁上,可以做到强一致性,关于分布式锁的相关知识,在ZAB协议中介绍(redis的AP是最终一致性)
  • 无状态华的实现

安装

conf下的

zoo_sample改为

zoo.cfg

# zk时间配置中的基本单位
tickTime=2000
#允许follower初始化连接到leader最大时长,表示tickTime时间倍数,即initLimit*tickTime
initLimit=10
#允许follower与leader数据同步最大时长,它表示tickTime时间倍数
syncLimit=5
#数据存储目录以及日志保存目录
dataDir=/tmp/zookeeper
#对客户端提供的端口号
clientPort=2181
#单个客户端与zookeeper最大并发连接数
maxClientCnxns=60
# 保存的数据快照数量,之外将会被删除
autopurge.snapRetainCount=3
#自动触发清除任务时间间隔,小时为单位,默认0
autopurge.purgeInterval=1

启动服务器./zkServer.sh start ../conf/zoo.cfg

查看服务器状态./zkServer.sh status ../conf/zoo.cfg

停止服务器./zkServer.sh start ../conf/zoo.cfg

内部数据模型

内部格式

zk中的数据是保存在节点上的,节点就是znode,多个znode之间构成一棵树的目录结构

zk的数据模型是什么样子呢?它很像数据结构当中的树,也很想文件系统中的目录

树是由节点锁组成,zookeeper的数据存储同样也是基于节点,这种节点叫做znode

但是不同树的节点,znode的引用方式是路径引用,类似于文件路径

随便输入未被定义的命令就会跳出帮助

ls / #查看根节点的内容
create /test1  #创建test1这个节点
ls /test1    #才看test1下的节点
create /test2 abc    #创建test2节点并存放数据abc
get /test2    #获取/test2下的数据
delete /test2    #删除test2节点 
get -s /test2    #查看元数据

zk中的znode包含了四个部分

  1. data保存数据
  2. acl:权限,定义了什么样的用户能够操作这个节点,并且能够进行怎样的操作

    • c:create 创建权限
    • w: write更新权限
    • r:read读取权限
    • d:delete:删除权限
    • a:admin管理者权限,允许对该节点进行acl权限设置
  3. stat:描述当前znode的元数据
  4. child:当前节点的子节点

zk中节点znode的类型

  1. 持久节点:创建出的节点,在会话结束后依旧存在.保存数据
  2. 持久序号节点:创建出的节点,根据先后数据,会在节点之后带上一个数值,越后执行数值越大,适用于分布式锁的应用场景-单调递增(在create后加上-s)
  3. 临时节点:临时节点是在会话结束后,自动被删除的,通过这个特性,zk可以实现服务注册与发现的效果.当做注册中心用(create 加上-e)

  1. 临时序号节点 跟持久序号节点相同,适用于临时的分布式锁 cretae -e -s
  2. Container容器节点(3.5.3版本新增) Container日期节点,当容器中没有任何子节点,该容器会被zk定期清除(60s) (create -c)
  3. TTL节点:可以指定节点的到期时间,到期后被zk定时删除.只能通过系统设置,zookeeper.extendedTypesEnabled=true开启

持久化机制

zk的数据运行在内存中,zk提供了俩种持久化机制

  • 事务日志:zk把执行的命令一日志的形式保存在dataLogDir的路径文件中(如果没有dataLogDir,就按DataDir指定的路径)
  • 数据快照:zk会在一定的时间间隔内做一次内存的数据快照,把时刻的内存数据保存在快照文件中.zk通过俩种形式的持久化,在回复时先回复快照文中的数据到内存中,在用日志文件中的数据做增量恢复,这样快些

使用

cli

ls -R /test1    #递归查询
get -s /test1    #查看详细数据

cZxid:创建节点的事务IDmZxid:修改节点的事务ID

pZxid:添加和删除子节点的事务IDctime:节点创建的时间

mtime:节点最近修改的时间

dataVersion:节点内数据的版本,每更新一次数据,版本会+1aclVersion:此节点的权限版本

ephemeralOwner:如果当前节点是临时节点,该值是当前节点所有者的session id。如果节点不是临时节点,则该值为零。dataLength:节点内数据的长度

numChildren:该节点的子节点个数

deleteall /test 1 #删除全部,包括子目录
delete -v 0 /test1    #乐观锁删除-v后面的参数是版本号

权限设置

addauth diget xiaowang:123456 #注册当前会话的账号和密码
create /test-node abcd auth:xiaowang:123456:cdwra

curator客户端

curator是netflix开源的一套zookeeper客户端框架,curator是对zookeper支持最好的客户端框架.curator封装了大部分zk的功能,比如leader选举,分布式锁等,减少了技术人员使用zk时底层细节开发工作

添加maven

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.14</version>
</dependency>

分布式锁

读锁:大家都可以读,要想上读锁的前提:之前的锁没有写锁

写锁:只有得到写锁才能写.要想上写锁的前提是,之前没有任何锁

如何上读锁

  • 创建一个临时序号节点,节点的数据是read,表示读锁
  • 获取当前zk中序号逼自己小的所有节点
  • 判断最小节点是否是读锁:(因为只要有一个读锁在就不可能上写锁,所以只要最小节点是读锁,就不可能存在写锁)

    • 如果不是读锁的话,啧上锁失败,最小节点设置监听.阻塞等待,zk的watch机制会当最小节点发生变化时通知当前节点,于是在执行第二部的流程
    • 如果是读锁的话,则上锁成功

如何上写锁

  1. 创建一个临时序号节点,节点的数据是write,表示是写锁
  2. 获取zk中所有自己子节点
  3. 判断自己是否是最小的节点

    • 如果是则上写锁成功
    • 如果不是,说明前面还有锁,啧上锁失败,监听最小的节点,如果最小节点有变化,则回到第二部

羊群效应

如果用上述的上锁方式,只要有节点发生变化,就会触发其他节点的监听事件,这样的话对zk的压力非常大,就是羊群效应.可以调整成链式监听

watch机制

我们可以把watch理解成是注册在Znode上的触发器.当这个Znode发生改变,也就是调用了,create,delete,setData方法的时候,将会触发Znode上注册的对于事件,请求Watch客户端会接收到异步通知

#一个客户端
create /test9
get -w /test9 #监听
#另一个客户端
set /test9 ccc

这时候第一个客户端就会被通知值发生修改,可以再次get -w /test9 这样就能获取值的同时在进行一次监听

具体交互过程

  • 客户端调用getData方法,watch参数是true.服务器接到请求,返回节点数据,并且在对于的哈希表里被插入的Watch的Znode路径,以及Watcher列表
  • 当被watch的Znode已删除,服务端会查找哈希表,找到该Znode对于的所有Watcher,异步通知客户端,并且删除哈希表中对应的key-value

客户端使用了NIO通信模式监听服务端的调用

create /test xxx
get -w /test #一次性监听节点
ls -w /test #监听目录,创建删除子节点会受到通知,子节点中新增节点不会有通知
ls -R -w /test #对于子节点中子节点的变化,但内容的变化不会收到通知

get是监听节点内容的变化,ls是监听节点目录

集群

zookeeper集群节点中有三种角色

  • leader:处理集群所有的事务请求,集群中只能有一个leader
  • follower:只能处理读请求,参与leader选举
  • Observer:只能处理读请求,提升集群读性能,但不能参与leader选举

ZAB协议

zookeeper作为非常重要的分布式协调组件,需要进行集群部署,集群会以一主多从的形式进行部署.zookeeper为了保证数据一致性,使用ZAB(zookeeper Atomic Broadcast)协议,这个协议解决了Zookeeper的崩溃回复和主从数据同步问题

ZAB协议定义的四种节点状态

  • Looking:选举节点
  • Following:Follower节点(从节点)所处的状态
  • Leading:leader(主节点)所处状态
  • Observing:观察者节点所处的状态

leader选举

流程

zk的leader选举分为俩种情况,第一种是启动的时候,第二种是leader崩溃的时候

说zk的leader选举之前需要了解俩个东西,myid和zxid

myid:zk集群中服务器的唯一标识,称为myid,列入有三个zk服务器,编号分别为1,2 ,3

zxid:zxid为Long类型,Zxid有64位,其中高32位标识epoch,低32位标识xid,即由俩部分组成epoch和xid。每个leader都会具有一个不同的epoch值,每一个时期,新的leader产生,则会更新所有的zkServer的zid中的epoch,,而xid为zk事务id,每一次写操作都是一个事务,都会有一个xid。每一个写操作都需要由leader发起一个提议,由所有follwer表示是否同意本次写操作(简单理解为当前zk服务发生了多少次变化)

选举机制

集群中半数zkServer同意,则产生新的leader(搭建集群时一般都是奇数个)三台服务器,最多允许一台宕机,四台服务器也最多允许一台宕机

启动的情况如下:
选举过程:假设有三台服务器,server1(myid=1),server2(myid=2),server3(myid=3),

server1启动之后给自己投票,发布投票结果(myid,zxid)。
server2启动之后同样给自己投票,发布投票结果(myid,zxid),跟server1交换投票结果,zxid一样,server2的myid大于server1的myid,所有server2当选leader。

先比较的是zxid,(如果zxid不一样会选大的)因为zxid一样再去比较myid

server3启动之后虽然myid比前两台服务器的myid都大,但是因为这个时候server2已经胜出了,所有server2也只能是作为一个follwer。

崩溃恢复时的leader选举

leader周期性的会像follower发送ping命令,像follower续约

当一段时间follower没有收到ping命令的时候,会尝试与leader进行socket连接,如果无法连接

follower会从follower状态变为loking状态,然后与其他变为looking状态的zk服务器重新进行一次选举(此时集群不能对外提供服务)

主从数据同步

NIO和BIO的应用场景

NIO

由于客户端连接的2181端口,用的是NIO模式与客户端建立连接

客户端开启watch时,也使用NIO,等待Zookeeper服务器的回调

BIO在集群选举时,多个节点之间的偷拍头寻端口,使用BIO进行通信

一致性

zookeeper在同步数据时,追求的不是强一致性,而是顺序一致性(事务id单调递增),遵循的是cp模型

zk可以很好的保证数据的一致性,不会因为节点的宕机而导致数据的丢失,在对于数据一致性要求较高的场景中使用即可.zk不是一个专门用来做存储的组件,在正式换种多数是扮演了一个注册中心的角色,是整个系统的协调者,如果让zk来承担较多的数据存储从而实现分布式锁并不是一个很好的方案,因此大多数情况下我们会用Etcd来实现CP模式的分布式锁,Etcd的CAS机制对于分布式锁的存储更友好;

Last modification:August 17, 2022
如果觉得我的文章对你有用,请随意赞赏