一、什么是分布式锁,与本地锁有什么区别
本地锁: 解决同一进程内多个线程操作同一资源的问题。
分布式锁: 解决多个进程同时操作同一资源的问题。
二、Redis的SETNX
Redis之所以能实现分布式锁,得益于Redis的单线程处理模式,将并发请求转为队列模式,不用担心并发线程安全问题。
用 Redis 实现分布式锁的几种方案,我们都是用 SETNX 命令(设置 key 等于某 value)。只是高阶方案传的参数个数不一样,以及考虑了异常情况。
SETNX:
set If not exist
的简写。意思就是当 key 不存在时,设置 key 的值,存在时,什么都不做。
redis-cli客户端,中设置成功则会返回OK,否则返回nil。
127.0.0.1:6379> set name 222 nx
OK
127.0.0.1:6379> set name 222 nx
(nil)
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> set name 222 nx
OK
三、实现方案
方案一:
问题: setnx占好位,业务代码异常或者程序在页面过程中宕机。没有执行删除锁逻辑,这就造成了死锁
解决: 设置锁的自动过期,即使没有删除,会自动删除。
方案二:
问题: setnx设置好,正要去设置过期时间,宕机。又死锁了。
解决: 设置过期时间和占位必须是原子的。redis支持使用setnx ex命令。
方案三:
问题: 由于业务时间很长,锁自己过期了,直接删除,有可能把别人正在持有的锁删除了。
解决: 占锁的时候,值指定为uuid,每个人匹配是自己的锁才删除。
方案四:
问题: 在查询和删除锁之间,锁自动过期,将会把别人的锁误删掉。
解决: 查询锁和删除锁必须保证原子性。使用redis+Lua脚本完成。
方案五:
问题: 业务逻辑执行时间超过了默认的过期时间,锁被自动释放,导致多个线程同时持有锁的情况。
解决: Redisson看门狗的分布式锁。
方案六:Redisson
Redisson是java中的一个工具包,这个包已经实现了上述的lua脚本执行保证原子性、锁的唯一标识判定等。除此之外,Redisson有一个看门狗机制,来自动对锁续期。
redisson在获取锁之后,会维护一个看门狗线程,在每一个锁设置的过期时间的1/3处,如果线程还没执行完任务,则不断延长锁的有效期。
那万一业务的机器宕机了呢?如果宕机了,那看门狗线程就执行不了了,就续不了期,到期将自动释放锁。
问题: 业务逻辑进入死循环,导致锁无法被释放。
解决: 干死杠精!!。
四、前面redis分布式锁存在的问题
前面两种redis分布式锁的实现方式,如果从“高可用”的层面来看,仍然是有所欠缺,也就是说当 redis 是单点的情况下,当发生故障时,则整个业务的分布式锁都将无法使用。
为了提高可用性,我们可以使用主从模式或者哨兵模式,但在这种情况下仍然存在问题,在主从模式或者哨兵模式下,正常情况下,如果加锁成功了,那么master节点会异步复制给对应的slave节点。但是如果在这个过程中发生master节点宕机,主备切换,slave节点从变为了 master节点,而锁还没从旧master节点同步过来,这就发生了锁丢失,会导致多个客户端可以同时持有同一把锁的问题。
那么,如何避免这种情况呢?redis 官方给出了基于多个 redis 集群部署的高可用分布式锁解决方案:RedLock。官方也给出了很多语言的实现:
详细内容待学习补充。。