Zookeeper源码分析与实战 1
Last updated: Oct 289, 28089
数据模型
ZooKeeper 中的数据模型是一种树形结构,类似于 Linux 中的文件系统。
ZK 中的每个绝对路径称为一个znode,znode 有以下三种:
持久节点
在ZooKeeper中最为常用。
一旦创建了持久节点,即使创建该节点的客户端与服务器的会话关闭了,该节点依旧存在ZK服务器上。
必须使用 delete
命令才可以删除持久节点。
临时节点
最重要的特性是临时性。
当创建该节点的客户端会话因超时或发生异常而关闭时,该节点在ZK服务器上也会被删除。
可以使用 delete
命令删除临时节点。
应用场景:利用临时节点,做服务器集群内机器运行情况的统计。
有序节点
一种性质,可以应用在临时节点和持久节点上。
当我们创建有序节点时,ZK服务器会自动使用一个单调递增的数字作为后缀,追加到节点的后边。
通过生成的序号,可以直观地查看节点的创建顺序。
节点的状态结构
使用命令 stat <path>
的方式,可以查看节点的状态信息。
每个znode都有一个自己的状态属性,记录了节点的一些信息,包括如下:
状态属性 | 说明 |
---|---|
cZxid | 节点被创建时的事务ID |
ctime | 节点的创建时间 |
mZxid | 节点最后一次被更新的事务ID |
mtime | 节点最后一次被更新的时间 |
pZxid | 节点的子节点列表最后一次被修改时的事务ID |
cversion | 子节点的版本号 |
dataVersion | 节点的数据版本号 |
aclVersion | 节点的ACL版本号 |
ephemeralOwner | 创建该临时节点的会话 SessionID;如果是持久节点,则值为0 |
dataLength | 数据内容的长度 |
numChildren | 节点的子节点个数 |
实战
使用 ZooKeeper 实现分布式锁
场景:某个商品库存只剩一件,两人同时购买,结果出现一件商品卖出了两件。
解决:可以在一个用户进行操作的时候,对库存进行锁定,从而避免超卖。
锁
悲观锁
认为进程对临界区对竞争总会出现,为了保证进程在操作数据时,该条数据不被其他进程修改,该数据一直处于被锁定的状态。
在 ZK 中:
- 进程 A 首先创建一个节点
/locks
来获取锁 - 进程 B 也尝试创建
/locks
节点来获取锁,因为已经有了,所以会创建节点失败无法获得锁 - 这样就实现了一个简单的悲观锁
- 存在问题:当进程 A 因为异常中断导致
/locks
节点始终存在,其他进程就始终无法获取锁,产生死锁 - 可以通过将节点设置为临时节点的方式避免,在服务器端添加监听事件来通知其他进程重新获取锁
乐观锁
认为进程对临界区资源的竞争不会总出现,所以加锁方式没有那么激烈,不会全程的锁定资源,而是在数据进行提交更新的时候,对数据的冲突与否进行检测,如果发现冲突了,则拒绝操作。
乐观锁分为三个步骤:
- 读取
- 校验
- 写入
即 CAS(Compare-And-Swap),过程如下:
当且仅当 预期值A 和 内存值V 相同时,将内存值V 修改为 B,否则什么都不做。
在 ZK 中:
- 可以使用 dataVersion 判断该节点是否被改动过
总结
为什么 Zookeeper 不采用相对路径查找节点呢?
因为 ZK 大多应用场景是定位数据模型上的节点,并在节点上操作
这种频繁查找的操作,适合用 Map 解决
ZK 中使用 ConcurrentHashMap<String, String> nodes
结构,节点的完整路径作为 key,
这样可以通过绝对路径快速定位到数据节点,从而在数据节点上进行操作,大大提升ZK性能。