Redis分布式锁
Redis分布式锁
redis实现分布式锁主要实现有以下方式:
- 法一:
setnx+expire
- (value使用时间戳)
- 法三:lua脚本+法一
- 法四:
set
- 法五:Redssion框架
- 法六:Redlock+Redission
法一:setnx+expire
setnx key value
: Set if not exists 如果不存在就设置两个参数:
key
表示锁 id:锁IDvalue
通常设置为:UUID返回值:
- 为0:表示已经存在锁(可以不断尝试获取)
- 为1:表示设置成功(即获得该锁)
原理:setnx
,如果key不存在则设置,设置成功返回1,否则返回0,因此可以使用setnx抢占key,然后使用expire给该key设置过期时间
缺点:加锁与设置过期时间并不是原子操作,如果在加锁后系统错误,没设置过期时间,那么其他线程再也无法获取到锁
法二:setnx+value
原理:为了解决不是setnx与expire不是原子操作的问题,可以将value设计为系统时间+过期时间的方式,这样就无需多一次expire操作,在每次请求时,判断时间是否到期
伪代码:
1 | if(setnx == 1){ |
缺点:
- 过期时间是本地客户端产生,分布式环境下的不同系统的时间可能存在误差
- 如果在锁过期的一瞬间,有多个请求同时获取锁,可能会出现锁的过期时间被其他锁覆盖的情况(锁只有一个请求拿到,但是校验锁过期的逻辑由其他线程来完成)。
- 锁的value设置为时间,可能会存在被其他线程误释放的问题
法三:lua脚本+法一
redis可以保证lua脚本的原子性,因此可以使用lua+setnx+expire
法四:set
在Redis2.6.12 起,set
命令完全覆盖了setnx
,而且还可以设置过期时间
set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds
:设置失效时长,单位秒PX milliseconds
:设置失效时长,单位毫秒NX
:key不存在时设置value,成功返回OK,失败返回(nil)XX
:key存在时设置value,成功返回OK,失败返回(nil)
存在的问题:
- 可能锁时间过期了,但是业务逻辑还没有执行完成:此时锁就会被错误释放
- 锁可能会被别的线程错误删除(这种错误可以给value设置一个唯一值解决)
法五:Redssion框架
为了解决锁过期,但是业务还没执行完成的问题,Redssion给出了一种解决方案:
在某一个线程拿到锁后,额外启动一个线程watchdog
看门狗,每隔10s,检测对应线程是否还持有锁,如果还持有,那么就延长锁时间。
法六:Redlock
法一到法六均是单击情况下的redis,对于分布式方案使用Redlock
Redlock:是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布 式锁了,否则加锁失败。
加锁步骤:
- 客户端获取当前时间
- 客户端按顺序依次向N个Redis实例执行加锁操作
- 客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时
只有满足两个条件,才算真正的加锁成功:
- 有半数以上的Redis节点加锁成功
- 总耗时没有超过锁的有效时间