Redis分布式锁

引言:前天写了第一个分布式锁,记录一下。

Redis分布式锁

redis实现分布式锁主要实现有以下方式:

  • 法一:setnx+expire
  • (value使用时间戳)
  • 法三:lua脚本+法一
  • 法四:set
  • 法五:Redssion框架
  • 法六:Redlock+Redission

法一:setnx+expire

setnx key value: Set if not exists 如果不存在就设置

两个参数:

  • key 表示锁 id:锁ID
  • value通常设置为:UUID

返回值:

  • 为0:表示已经存在锁(可以不断尝试获取)
  • 为1:表示设置成功(即获得该锁)

原理:setnx,如果key不存在则设置,设置成功返回1,否则返回0,因此可以使用setnx抢占key,然后使用expire给该key设置过期时间

缺点:加锁与设置过期时间并不是原子操作,如果在加锁后系统错误,没设置过期时间,那么其他线程再也无法获取到锁

法二:setnx+value

原理:为了解决不是setnx与expire不是原子操作的问题,可以将value设计为系统时间+过期时间的方式,这样就无需多一次expire操作,在每次请求时,判断时间是否到期

伪代码:

1
2
3
4
5
6
7
8
9
if(setnx == 1){
// 拿到锁,设置了value
}else{
// 没拿到锁
get//锁时间
if(时间已过期){
//重新设置过期时间
}
}

缺点:

  • 过期时间是本地客户端产生,分布式环境下的不同系统的时间可能存在误差
  • 如果在锁过期的一瞬间,有多个请求同时获取锁,可能会出现锁的过期时间被其他锁覆盖的情况(锁只有一个请求拿到,但是校验锁过期的逻辑由其他线程来完成)。
  • 锁的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)

存在的问题:

  1. 可能锁时间过期了,但是业务逻辑还没有执行完成:此时锁就会被错误释放
  2. 锁可能会被别的线程错误删除(这种错误可以给value设置一个唯一值解决)

法五:Redssion框架

为了解决锁过期,但是业务还没执行完成的问题,Redssion给出了一种解决方案:

在某一个线程拿到锁后,额外启动一个线程watchdog看门狗,每隔10s,检测对应线程是否还持有锁,如果还持有,那么就延长锁时间。

法六:Redlock

法一到法六均是单击情况下的redis,对于分布式方案使用Redlock

Redlock:是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布 式锁了,否则加锁失败。

加锁步骤:

  1. 客户端获取当前时间
  2. 客户端按顺序依次向N个Redis实例执行加锁操作
  3. 客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时

只有满足两个条件,才算真正的加锁成功:

  • 有半数以上的Redis节点加锁成功
  • 总耗时没有超过锁的有效时间