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包含了四个部分
- data保存数据
acl:权限,定义了什么样的用户能够操作这个节点,并且能够进行怎样的操作
- c:create 创建权限
- w: write更新权限
- r:read读取权限
- d:delete:删除权限
- a:admin管理者权限,允许对该节点进行acl权限设置
- stat:描述当前znode的元数据
- child:当前节点的子节点
zk中节点znode的类型
- 持久节点:创建出的节点,在会话结束后依旧存在.保存数据
- 持久序号节点:创建出的节点,根据先后数据,会在节点之后带上一个数值,越后执行数值越大,适用于分布式锁的应用场景-单调递增(在create后加上-s)
- 临时节点:临时节点是在会话结束后,自动被删除的,通过这个特性,zk可以实现服务注册与发现的效果.当做注册中心用(create 加上-e)
- 临时序号节点 跟持久序号节点相同,适用于临时的分布式锁 cretae -e -s
- Container容器节点(3.5.3版本新增) Container日期节点,当容器中没有任何子节点,该容器会被zk定期清除(60s) (create -c)
- 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机制会当最小节点发生变化时通知当前节点,于是在执行第二部的流程
- 如果是读锁的话,则上锁成功
如何上写锁
- 创建一个临时序号节点,节点的数据是write,表示是写锁
- 获取zk中所有自己子节点
判断自己是否是最小的节点
- 如果是则上写锁成功
- 如果不是,说明前面还有锁,啧上锁失败,监听最小的节点,如果最小节点有变化,则回到第二部
羊群效应
如果用上述的上锁方式,只要有节点发生变化,就会触发其他节点的监听事件,这样的话对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机制对于分布式锁的存储更友好;