redis分布式锁网上方案很多,这里简单的介绍一种
加锁步骤
1.创建锁对象,内部创建一个随机数
2.使用SET KEY VALUE NX EX xxxSecond
, 如果成功创建了KEY 则证明加锁成功VALUE 就是第一步创建的随机数
3.如果未能成功加锁需要不断取重试,直到超时或者获取锁
解锁步骤
解锁需要去判断KEY对应的值是否是创建时的随机数,如果不是就不能删除,只有是的时候才能删除,因为如果不是自己的随机数可能是因为锁过期被别人加锁了,不能去删除别人的锁, 检查和删除必须是原子操作,所以我们可以使用lua脚本保证原子操作
if redis.call("GET", KEYS[1])==ARGV[1] then
redis.call("DEL", KEYS[1])
return true
else
return false
end
上面的lua脚本很容易懂,就是用来判断key对应的值是否是参数的值,如果是就删除key并返回成功,否则返回失败;失败的情况就是上述说的锁过期被其他程序加锁
优化
加锁是需要不断去重试,访问次数过多可能会给redis造成压力,比如100ms抢一次,一个线程1s钟要请求redis 10次, 如果是10线程抢锁,那么1s就是100次,抢锁的越多就会将redis请求数放大10倍
应对这种情况我们可以考虑,进程内部先去加互斥锁,解锁的时候去解互斥锁, 然后抢到锁的线程再去抢redis锁
优点: 这样如果有两个进程,各有5个线程去抢锁,则实际只有两个线程去访问redis,抢到锁后只有另一个进程的1个线程继续抢,这种已经在生产环境中得到实践
缺点: 造成锁竞争的不公平,同一个进程其他线程更容易抢到锁,因为互斥锁解锁同一个进程的其他线程可以更快的感知
还有一种想法未得到验证,通过redis的发布订阅来改进锁性能
锁过期问题,没有个安全的方法去估计过期时间
针对这种情况,可以考虑锁续期逻辑,比如默认过期时间是30s,我们到20s的时候去延长过期时间
可以考虑使用下面的续期逻辑
if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("EXPIRE", KEYS[1], ARGV[2])
return true
else
return false
end
先去判断锁是否是自己的,如果是则进行续期
参考资料
redis set命令 从2.6.12版本开始,redis为SET命令增加了一系列选项
EX seconds – 设置键key的过期时间单位时秒
PX milliseconds – 设置键key的过期时间单位时毫秒
NX – 只有键key不存在的时候才会设置key的值
XX – 只有键key存在的时候才会设置key的值
redlock
redlock 和 普通的redis lock的区别就是,redlock需要使用多个redis(奇数个),采用大多数原则, 锁住大多数redis就算上锁成功