目录
[1. 什么是分布式锁](#1. 什么是分布式锁)
[2. 分布式锁的基础实现](#2. 分布式锁的基础实现)
[3. 引入过期时间](#3. 引入过期时间)
[4. 引入校验 id](#4. 引入校验 id)
[5. 引入 lua](#5. 引入 lua)
[6. 引入 watch dog (看门狗)](#6. 引入 watch dog (看门狗))
[7. 引入 RedLock 算法](#7. 引入 RedLock 算法)
1. 什么是分布式锁
分布式锁其实就是在一个分布式系统中, 涉及到多个节点访问同一个公共资源的时候, 这个时候就需要通过索来做互斥控制, 否则可能会出现类似 "线程安全" 的问题.
2. 分布式锁的基础实现
我们可以使用 redis 来实现 分布式锁. 当一个节点要访问一个资源的时候, 我们用 redis 创建一个 key : valiue, 这个 key 代表这个资源, value 代表这个节点, 当访问结束的删除这个 key, 当有别的节点想要访问这个资源的时候, 我们就重新设置一下, 要是发现已经设置过了, 就不进行设置了. 怎么看是否已经设置过了呢? redis 自带一种方法 setnx 操作 (即 key 不存在就设置, 存在就设置失败), 正好可以解决这个问题.
3. 引入过期时间
要是某个节点在进行访问的时候宕机了, 那么就会导致解锁操作 (删除这个key) 不能执行. 于是我们就需要引入过期时间, 来防止节点宕机.
注意这里进行过期时间的设置只能通过一个操作完成 (set ex nx 的方式), 因为如果分开操作, 比如 setnx 之后, 再来一个单独的 expire, 由于 redis 的多个指令之间不存在关联, 并且即使使用了事务也不能保证两个操作都一定成功.
4. 引入校验 id
由于我们设置的 key 是任何节点都能删除的, 于是别的节点可能误删 key, 于是我们在设置 value 的时候不能简单的进行设置, 要标明是哪个节点进行设置的.
逻辑用伪代码简单描述 :
String key = [要加锁的资源 id];
String serverId = [服务器的编号];
// 加锁, 设置过期时间为 10s
redis.set(key, serverId, "NX", "EX", "10s");
// 执⾏各种业务逻辑, ⽐如修改数据库数据.
doSomeThing();
// 解锁, 删除 key. 但是删除前要检验下 serverId 是否匹配.
if (redis.get(key) == serverId) {
redis.del(key);
}
我们会发现, 解锁逻辑 get 和 del 的操作并不是原子的.
5. 引入 lua
使用 Lua 脚本完成解锁逻辑实现 原子性 的功能
if redis.call('get',KEYS[i]) == ARGV[i] then
return redis.call('del',KEYS[1])
else
return 0
end;
上述代码可以编写成一个 .lua 后缀的文件, 由 redis-cli 或者 redis-plus-plus 或者 jedis 等客户端加载, 并发送给 Redis 服务器, 由 Redis 服务器来执行这段逻辑. 一个 lua 脚本会被 Redis 服务器以原子的方式来执行.
6. 引入 watch dog (看门狗)
要是我们在执行任务的过程中, key 就已经过期了该怎么办呢?
这个时候我们引入 watch dog, 本质上是加锁的服务器上的一个单独的线程, 通过这个线程来对锁过期时间进行 "续约".
举个具体的例子 :
初始情况下设置过期时间为 10s. 同时设定看门狗线程每隔 3s 检测一次. 那么当 3s 时间到的时候, 看门狗就会判定当前任务是否完成,.
- 如果任务已经完成, 则直接通过 lua 脚本的方式, 释放锁 (删除 key).
- 如果任务未完成, 则把过期时间重写设置为10s. (即"续约")
要是该服务器挂了, 看门狗线程也就随之挂了, 此时无人续约, 这个 key 自然就可以迅速过期, 让其他服务器能够获取到锁了.
7. 引入 RedLock 算法
服务器 1 向 master 节点进行加锁操作. 这个写入 key 的过程刚刚完成, master 挂了; slave 节点升级成了新的 master 节点. 但是由于刚才写入的这个 key 尚未来得及同步给 slave 呢, 此时就相当于服务器 1 的加锁操作形同虚设了, 服务器 2 仍然可以进行加锁 (即给新的 master 写入 key. 因为新的 master 不包含刚才的 key).
这里服务器 1 和 服务器 2 都会认为自己可以对数据进行操作.
这种情况我们要如何解决呢?
这个时候, 我们只需要在加锁的时候, 让所有的主节点都参与进来, 按照一定的顺序进行加锁(并设置超时时间), 要是有一半以上的主节点顺利完成加锁的操作, 就说明加锁成功.
同理, 释放锁的时候, 也需要把所有节点都进⾏解锁操作. (即使是之前超时的节点, 也要尝试解锁, 尽量保证逻辑严密).
简⽽⾔之, Redlock 算法的核⼼就是, 加锁操作不能只写给⼀个 Redis 节点, ⽽要写个多个!! 分布式系统中任何⼀个节点都是不可靠的. 最终的加锁成功结论是 "少数服从多数的".