# 目录
[TOC]
# 一、Zookeeper 的应用场景
1、分布式锁
利用 `Zookeeper` 的临时顺序节点,可以轻松实现分布式锁。
2、服务注册与发现
利用 `Znode` 和 `Watcher`,可以实现分布式服务的注册和发现。
最著名的应用就是阿里的分布式 `RPC` 框架 `Dubbo`。
3、共享配置和状态信息
Redis 的分布式解决方案 `Codis`,就是利用了 Zookeeper 来存放 数据路由表 和 `codis-proxy` 节点的元信息。同时 `codis-config` 发起的命令都会通过 ZooKeeper 同步到各个存活的 codis-proxy。
此外,Kafka、HBase、Hadoop,也都依靠Zookeeper同步节点信息,实现高可用。
> 引自百度百科:
>
> Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别 (不支持的命令列表), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务。
# 二、什么是临时顺序节点?
> Zookeeper 的分布式锁是由 “临时顺序节点” 的特性来实现的。
Zookeeper 的数据结构是树形的,这棵树由多个 `Znode ` 节点组成。
`Znode` 分为以下四个类型:
**1、持久节点(PERSISTENT)**
默认的节点类型。创建节点的客户端与 `Zookeeper` 断开连接后,该节点依旧存在。
**2、持久节点顺序节点(PERSISTENT_SEQUENTIAL)**
所谓顺序节点,就是在创建节点时,`Zookeeper` 根据创建的时间顺序为该节点编号,如下图

**3、临时节点(EPHEMERAL)**
和持久节点相反,临时节点会在客户端与 `Zookeeper` 断开连接后被删除。
如下图所示,客户端与服务端建立连接后,自动创建一个临时节点

当客户端断开连接后,之前创建的临时节点也会被销毁

**4、临时顺序节点(EPHEMERAL_SEQUENTIAL)**
临时顺序节点 结合了 “临时节点” 和 “顺序节点” 的特性:在创建节点时 Zookeeper 根据创建的时间顺序给该节点进行编号,并根据该编号的顺序去分配临界资源的访问顺序,当创建节点的客户端与 Zookeeper 断开连接后,临时节点也会被删除。
# 三、Zookeeper分布式锁的原理
Zookeeper 分布式锁恰恰应用了临时顺序节点的特性,具体实现的步骤如下
## 0x01:获取锁
首先,在 Zookeeper 当中创建一个持久节点 `ParentLock`。 当第一个 `Client` 接入后想要获取锁时,需要在 `ParentLock` 这个节点下创建一个 临时顺序节点,这里我们称为 `Lock1`。如下图所示

在这之后,Client1 查找 `ParentLock` 下面的所有顺序节点并根据其序号进行排序,判断自己所创建的节点 `Lock1` 是否为第一个顺序节点,如果是则成功获取锁。

这时候,如果再有一个 Client2 接入,则会在 `ParentLock` 下再创建一个临时顺序节点 `Lock2`

Client2 查找 `ParentLock` 下面所有的临时顺序节点并进行排序,判断自己所创建的节点 Lock2 是不是顺序最靠前的一个,结果发现节点 `Lock2` 并不是最小的。
于是,Client2 开始向它前一位的顺序节点 `Lock1` 注册 `Watcher` ,用于监听 `Lock1` 节点的状态。这意味着 Client2 抢锁失败,进入到等待状态。

这时候,如果又有一个 Client3 接入并获取了锁,并重复了 Client2 的操作,向 `Lock2` 注册 `Watcher`

这样一来,Client1 获得了锁,Client2 监听了 Lock1 , Client3 监听了 Lock2 。形成了一个等待队列。
## 0x02:释放锁
释放锁分为两种情况:
情况1:任务完成,客户端显式的释放锁
当任务完成时,client 1 会显式的调用删除节点 `Lock1` 的指令,将其创建的节点删除。

情况2:任务执行过程中,客户端崩溃
获取锁的 Client1 在任务执行过程中如果出现崩溃,则会断开与 `Zookeeper` 服务端的连接,根据临时节点的特性,与之相关联的节点 `Lock1` 也会随之删除。

此时由于 Client2 一致监听着 Lock1 的存在状态,当 Lock1 节点被删除后,Client2 会立即收到通知,这时候 Client2 会再次查询 `ParentLock` 下面的所有节点,确认自己创建的节点 `Lock2` 是不是当前最小接地那,如果是,则 Lock2 当前的持有者 Client2 获得了锁。

同理,如果 Client2 也因为任务完成 或者 节点崩溃而删除了节点 `Lock2`,那么 `Client3` 就会接到通知。
## 0x03:Zookeeper 和 Redis 分布式锁的比较
| 分布式锁 | 优点 | 缺点 |
| --------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| Zookeeper | 1、有封装好的框架,容易实现<br />2、有等待锁的队列,大幅度的提高了抢锁的效率 | 添加和删除节点的性能较低 |
| Redi | Set 和 Del 指令的性能较高 | 1、实现复杂,需要在代码层面考虑超时、原子性、误删等情况。<br />2、没有等待锁的队列,只能在客户端自旋来等待锁,效率低下。 |
基于Zookeeper的分布式锁原理