一、ZK的数据模型
数据模型是ZK用来存储和处理数据的一种逻辑结构,就像用MySQL一样。
启动ZK:
1、配置文件
tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
2、启动ZK服务端
bin/zkServer.sh start
3、启动ZK客户端,连接服务端
bin/zkCli.sh -server 127.0.0.1:2181
4、单机版的开发环境构建完成,利用create命令创建几个节点,分别为
create /locks
create /servers
create /works
最终在ZK服务器会得到一个层级关系的数据结构。

ZK的数据模型是一种树形结构,具有一个固定的根节点(/),可以在根节点下创建子节点,并在子节点下继续创建下一级节点。每一层级用 / 隔开,且只能用绝对路径(get /work/task1)的方式查询ZK节点,而不能用相对路径。

二、znode节点类型与特性
ZK的数据节点分为持久节点,临时节点和有序节点三种类型:
1、持久节点:
一旦将节点创建为持久节点,该数据节点会一直存储在ZK服务器上,即使创建该节点的客户端与服务端的会话关闭了,该节点依然不会被删除,除非显式调用delete函数进行删除操作。这种节点最为常用,几乎所有业务场景都会包含持久节点的创建。
2、临时节点:
所谓临时节点是指,如果将节点创建为临时节点,那么该节点数据不会一直存储在ZK服务器上。当创建该临时节点的客户端会话因超时或发生异常而关闭时,该节点也相应在ZK服务器上被删除。当然也可以主动调用delete删除。
应用:可以利用临时节点的这一特性来做服务器集群内机器运行情况的统计,将集群设置为“/servers”节点,并为集群下的每台服务器创建一个临时节点“/servers/host”,当服务器下线时,该节点自动被删除,最后统计临时节点的个数就可以知道集群中的运行情况。

3、有序节点:
有序节点并不算一种单独种类的节点,而是在持久节点和临时节点的基础上,增加一个节点有序的性质。所谓节点有序是说在创建有序节点的时候,ZK服务器会自动使用一个单调递增的数字作为后缀,追加到我们创建的节点后边。例如一个客户端创建了一个路径为 works/task- 的有序节点,那么 ZooKeeper 将会生成一个序号并追加到该节点的路径后,最后该节点的路径为 works/task-1。
ZK的每个节点都维护有这些内容:一个二进制数组(byte data[]),用来存储节点的数据、ACL访问控制、子节点数据(因为临时节点不允许有子节点,所以其子节点字段为null),除此之外,每个数据节点还有一个记录自身状态信息的字段stat。
三、节点的状态结构
每个节点都有属于它自己的状态信息。打开之前的客户端,执行 stat /zk_test,可以看到控制台输出了一些信息,这就是节点的状态信息。

状态属性如下:

四、数据节点的版本
ZK为数据节点引入了版本的概念,每个数据节点有三种类型的版本信息,对数据节点的任何更新操作都会引起版本号的变化。ZK的版本信息表示的是对节点数据内容,子节点信息或ACL信息的修改次数。
使用ZK实现锁:
悲观锁: 假设一个具有n个进程的应用,同时访问临界区资源,通过进程创建ZK节点/locks的方式获取锁。
线程a成功创建节点“/locks”的方式获取锁后继续执行。

这时进程b也要访问临界区资源,于是b也尝试创建“/locks”节点来获取锁,因为之前a已经创建过该节点,所以b创建失败无法获得锁。

这里也会隐含一个问题。当a因为异常中断导致节点始终存在,其他进程就无法再次创建节点而无法获取锁,导致死锁。解决:将该节点设置为临死节点,并在服务器端添加监听事件来通知其他进程重新获取锁。
乐观锁: 可以分为读取、校验、写入三个步骤。CAS(Compare-And-Swap),即比较并替换,就是一个乐观锁的实现。CAS 有 3 个操作数,内存值 V,旧的预期值 A,要修改的新值 B。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。
在 ZooKeeper 中的 version 属性就是用来实现乐观锁机制中的“校验”的,ZooKeeper 每个节点都有数据版本的概念,在调用更新操作的时候,假如有一个客户端试图进行更新操作,它会携带上次获取到的 version 值进行更新。而如果在这段时间内,ZooKeeper 服务器上该节点的数值恰好已经被其他客户端更新了,那么其数据版本一定也会发生变化,因此肯定与客户端携带的 version 无法匹配,便无法成功更新,因此可以有效地避免一些分布式更新的并发问题。
源码分析:
在 ZooKeeper 的底层实现中,当服务端处理 setDataRequest 请求时,首先会调用 checkAndIncVersion 方法进行数据版本校验。ZooKeeper 会从 setDataRequest 请求中获取当前请求的版本 version,同时通过 getRecordForPath 方法获取服务器数据记录 nodeRecord, 从中得到当前服务器上的版本信息 currentversion。如果 version 为 -1,表示该请求操作不使用乐观锁,可以忽略版本对比;如果 version 不是 -1,那么就对比 version 和 currentversion,如果相等,则进行更新操作,否则就会抛出 BadVersionException 异常中断操作。

问题:为什么 ZooKeeper 不能采用相对路径查找节点呢?
这是因为 ZooKeeper 大多是应用场景是定位数据模型上的节点,并在相关节点上进行操作。像这种查找与给定值相等的记录问题最适合用散列来解决。因此 ZooKeeper 在底层实现的时候,使用了一个 hashtable,即 hashtableConcurrentHashMap<String, DataNode> nodes ,用节点的完整路径来作为 key 存储节点数据。这样就大大提高了 ZooKeeper 的性能。
653

被折叠的 条评论
为什么被折叠?



