分布式锁
程序员文章站
2022-07-05 11:50:32
...
Redis分布式锁实现方式
- 基于 REDIS 的 SETNX()、EXPIRE() 方法做分布式锁
- setnx(lockkey, 1) :如果返回 0,则说明占位失败(获取锁失败);如果返回 1,则说明占位成功(获取锁成功)
- expire() :命令对 lockkey 设置超时时间,为的是避免死锁问题
- 执行完业务代码后,通过 delete 命令删除 key(即释放锁)
注:如果在setnx(lockkey, 1)之后在expire()之前发生宕机,仍然会产生死锁(因为没有过期时间,锁不会被释放)
- 基于 REDIS 的 SETNX()、GET()、GETSET()方法做分布式锁
- setnx(lockkey, 1) :如果返回 0,则说明占位失败(获取锁失败);如果返回 1,则说明占位成功(获取锁成功)
- get(lockkey): 获取值 oldExpireTime ,并将这个 value 值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取
- getset(lockkey, newExpireTime): 会返回当前 lockkey 的值currentExpireTime。
- 基于 REDLOCK 做分布式锁
- 客户端获取当前时间,以毫秒为单位。
- 客户端尝试获取 N 个节点的锁
- 客户端计算在获得锁的时候花费了多少时间,方法是用当前时间减去在步骤一获取的时间,只有客户端获得了超过 3 个节点的锁,而且获取锁的时间小于锁的超时时间,客户端才获得了分布式锁。
- 客户端获取的锁的时间为设置的锁超时时间减去步骤三计算出的获取锁花费时间。
- 如果客户端获取锁失败了,客户端会依次删除所有的锁。
// 优点: 性能高
// 缺点:失效时间设置多长时间为好?
// 如何设置的失效时间太短,方法没等执行完,锁就自动释放了,那么就会产生并发问题。
// 如果设置的时间太长,其他获取锁的线程就可能要平白的多等一段时间。
ZooKeeper分布式锁
- 锁相关基础知识
- zk 一般由多个节点构成(单数),采用 zab 一致性协议。因此可以将 zk 看成一个单点结构,对其修改数据其内部自动将所有节点数据进行修改而后才提供查询服务。
- zk 的数据以目录树的形式,每个目录称为 znode, znode 中可存储数据(一般不超过 1M),还可以在其中增加子节点。
- 子节点有三种类型。序列化节点,每在该节点下增加一个节点自动给该节点的名称上自增。临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除。最后就是普通节点。
- Watch 机制,client 可以监控每个节点的变化,当产生变化会给 client 产生一个事件。
- 锁原理:
- 利用临时节点与 watch 机制。每个锁占用一个普通节点 /lock,当需要获取锁时在 /lock 目录下创建一个临时节点,创建成功则表示获取锁成功,失败则 watch/lock 节点,有删除操作后再去争锁
// 优点:临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁。
// 缺点:所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大。
// 优化:
// 上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同。
// 只有序号最小的可以拥有锁,如果这个节点序号不是最小的则 watch 序号比本身小的前一个节点 (公平锁)。
- 实现方式
- 在 /lock 节点下创建一个有序临时节点 (EPHEMERAL_SEQUENTIAL)。
- 判断创建的节点序号是否最小,如果是最小则获取锁成功。不是则取锁失败,然后 watch 序号比本身小的前一个节点。
- 当取锁失败,设置 watch 后则等待 watch 事件到来后,再次判断是否序号最小。
- 取锁成功则执行代码,最后释放锁(删除该节点)。
参考: