什么是分布式锁
以往的锁都是只能在当前进程中⽣效, 在分布式的这
种多个进程多个主机的场景下就⽆能为⼒了。
因此提供分布式锁,加锁就是往redis上设置一个特殊的key:value
,完成操作后,释放锁就是删除这个key:value
;其他服务器尝试加锁时,也就是设置key:value
时,如果发现该key:value
对已经存在,就认为加锁失败
刚才买票场景,使用 mysql 的事务也可以批量执行 查询 + 修改 操作但是分布式系统中,要访问的共享资源不一定是 mysql…
t也可能是其他的存储介质没有事务.也可能是执行一段特定的操作,是通过统一的服务器完成执行动作…
过期时间设置
为了解决某个拿到锁的服务器突然宕机,导致不能释放锁,别的服务器永远拿不到锁的情况,需要给redis设置过期时间。
可以通过 set ex nx
命令设置,设置 key 的过期时间,为 1000ms 那么意味着即使出现极端情况,某个服务器挂了,没有正确释放锁。这个锁最务保持 1000ms,也就会自动释放。
注意⚠️
校验ID
因为分布式锁的加锁和解锁操作本质就是设置一个key:value
和删除一个key:value
,因此可能因为代码考虑不周全,出现一个服务器A加锁了,另一个服务器B把锁给解了。
因此可以引入校验机制,谁加到锁,谁来解锁。
方案:
- 给服务器编号,每个服务器有自己的身份id
- 加锁设置
key:value
时,key可以是被加锁的资源name,value设置成加锁服务器的id - 后续解锁时进行校验,先查询一下这个锁对应的服务器编号,判断这个id是不是当前想要解锁的服务器的id,是,就执行
del
解锁,不是,就判定解锁失败。(服务器上的逻辑代码)
String key = [要加锁的资源 id];
String serverId = [服务器的编号];
// 加锁, 设置过期时间为 10s
redis.set(key, serverId, "NX", "EX", "10s");
// 执⾏各种业务逻辑, ⽐如修改数据库数据.
doSomeThing();
// 解锁, 删除 key. 但是删除前要检验下 serverId 是否匹配.
if (redis.get(key) == serverId) {
redis.del(key);
}
lua脚本
出现以上问题的本质是因为get
和del
操作不是原子的;因此引入lua
脚本(lua是一个编程语言,redis可以内嵌lua)
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end;
过期时间的续约(看门狗)
所谓 watch dog, 本质上是加锁的服务器上的⼀个单独的线程, 通过这个线程来对锁过期时间进⾏ “续约”。
注意, 这个线程是业务服务器上的, 不是 Redis 服务器的
Redlock 算法
存在一种情况,服务器加锁时给redis的master写入了key:value
,但是在同步数据前master挂了,哨兵选举一个slave作为新的master,但此时没有刚刚服务器设置的key:value
,就出现,明明我加锁了却说现在没加上的情况。
按照顺序进行加锁,当加锁成功的节点数超过总节点数的⼀半, 才视为加锁成功,这样的话, 即使有某些节点挂了, 也不影响锁的正确性。解锁时,也都会设置一遍del
。
那么是否可能出现上述节点都同时遇到了 “⼤冤种-都失败” 情况呢? 理论上这件事是可能发⽣的, 但是概率太⼩了。⼯程上就可以忽略不计了