> For the complete documentation index, see [llms.txt](https://jun-wang.gitbook.io/learnjava/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jun-wang.gitbook.io/learnjava/ji-shu-xue-xi/jia-gou-xue-xi/fen-bu-shi-suo.md).

# 分布式锁

## 一、分布式锁简介

我们知道在一个多线程的程序中如果多个线程竞争同一个资源，可以加锁来避免对资源的同时访问。那么在多个机器的多个线程如何互斥的竞争同一个资源呢？这个时候就需要分布式锁了。

使用分布式锁主要解决两个问题：

* 效率：使用分布式锁可以避免不同节点重复相同的工作，这些工作会浪费资源。比如用户付了钱之后有可能不同节点会发出多封短信。
* 正确性：加分布式锁同样可以避免破坏正确性的发生，如果多个节点在同一条数据上面操作，比如多个节点机器对同一个订单操作不同的流程有可能会导致该笔订单最后状态出现错误，造成损失。

## 二、分布式锁的实现

* MySQL：使用Insert操作，利用主键的唯一性，插入成功获得锁，否则失败，操作完delete掉；
* Redis：和 mysql 的方式类似，利用 Redis 的 `setnx` 命令。此命令同样是原子性操作，只有在 `key` 不存在的情况下，才能 `set` 成功。
* Zookeeper：利用 Zookeeper 的顺序临时节点，来实现分布式锁和等待队列。Zookeeper 设计的初衷，就是为了实现分布式锁服务的。

## 三、Zookeeper实现分布式锁

**zk 基本锁**

* 原理：利用临时节点与 watch 机制。每个锁占用一个普通节点 /lock，当需要获取锁时在 /lock 目录下创建一个临时节点，创建成功则表示获取锁成功，失败则 watch/lock 节点，有删除操作后再去争锁。临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁。
* 缺点：所有取锁失败的进程都监听父节点，很容易发生羊群效应，即当释放锁后所有等待进程一起来创建节点，并发量很大。

**zk 锁优化**

* 原理：上锁改为创建临时有序节点，每个上锁的节点均能创建节点成功，只是其序号不同。只有序号最小的可以拥有锁，如果这个节点序号不是最小的则 watch 序号比本身小的前一个节点 (公平锁)。
* 步骤：
* 在 /lock 节点下创建一个有序临时节点 (EPHEMERAL\_SEQUENTIAL)。
* 判断创建的节点序号是否最小，如果是最小则获取锁成功。不是则取锁失败，然后 watch 序号比本身小的前一个节点。
* 当取锁失败，设置 watch 后则等待 watch 事件到来后，再次判断是否序号最小。
* 取锁成功则执行代码，最后释放锁（删除该节点）。

> 参考：
>
> 分布式锁看这篇就够了：<https://zhuanlan.zhihu.com/p/42056183>
