实现redis分布式锁的步骤:1、加锁;2、释放锁;3、给锁设置有效期;4、给锁设置少数值;5、通过LUA脚本实现释放锁的原子性。加锁是指,执行 setnx 为一个代表锁键设置值,如果能设置成功,则表示获得锁,失败则无法获得锁。
1、加锁
Redis 的 setnx 命令会判断键值是否存在,如果存在则不做任何操作,并返回0,如果不存在,则创建并赋值,并返回1,因此我们可以执行 setnx 为一个代表锁键设置值,如果能设置成功,则表示获得锁,失败则无法获得锁。
代码:
# 使用key为lock来表示一个锁
setnx lock 1
2、释放锁
当执行好操作之后,要释放锁的时候直接把 Redis 里的键值 lock 删除就可以了,这样其他进程才能通过 setnx 命令重新设置并获得该锁。
代码:
# 释放锁
del lock
通过上面两个命令,我们实现了一个简单的分布式锁,但这里就出现了一个问题:如果一个进程通过 setnx 命令加锁之后,在执行具体操作出错了,没有办法及时释放锁,那么其他进程就无法获得该锁,系统便无法继续往下执行,解决这个问题的办法就是为锁设置一个有效期,在这个有效期之后,自动释放锁。
3、给锁设置有效期
给锁设置有效期非常简单,直接使用 Redis 的 expire 命令就可以了,如:
# 加锁
setnx lock 1
# 给锁设置10s有效期
expire lock 10
但是,现在又出现另一个问题了,如果我们在设置了锁之后,执行 expire 命令之前该进程挂掉了,那么 expire 就没有执行成功,锁一样是没有被释放掉的,所以一定要保证上面两个命令要一起执行,怎么保证呢?
有两个方法,一个是使用 LUA 语言编写的脚本,另一个是使用 Redis 的 set 命令, set 命令后面跟 nx 参数后,执行的效果与 setnx 一致,且 set 命令可以跟 ex 参数来设置过期时间,所以我们可以使用 set 命令把 setnx 和 expire 两个合并在一起,这样就可以保证执行的原子性了。
4、给锁设置少数值
如何区分其他进程的锁,避免删除其他进程的锁呢?答案就是每个进程在加锁的时候,给锁设置一个少数值,并在释放锁的时候,判断是不是自己设置的锁。给锁设置少数值的时候,一样是使用 set 命令,少数的不同是将键值1改为一个随机生成的少数值,比如uuid。
代码:
# rand_uid表示少数id
set lock rand_id nx ex 10
当锁里的值由进程设置后,释放锁的时候,就需要判断锁是不是自己的,步骤如下:
- 通过 Redis 的 get 命令获得锁的值
- 根据获得的值,判断锁是不是自己设置的
- 如果是,通过 del 命令释放锁。
此时我们看到,释放锁需要执行三个操作,如果三个操作依次执行的话,是没有办法保证原子性的。解决这个问题的办法就是保证上述三个操作执行的原子性,即在执行释放锁的三个操作中,其他进程不可以获得锁,想要做到这一点,需要使用到LUA脚本。
5、通过LUA脚本实现释放锁的原子性
Redis 支持 LUA 脚本, LUA 脚里的代码执行的时候,其他客户端的请求不会被执行,这样可以保证原子性操作,所以我们可以使用下面脚本进行锁的释放:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
将上述脚本保存为脚本后,可以调用 Redis 客户端命令 redis-cli 来执行,如下:
# lock为key,rand_id表示key里保存的值
redis-cli --eval unlock.lua lock , rand_id
延伸阅读
分布式锁的特征
- 互斥性:任意时刻,只有一个客户端能持有锁。
- 锁超时释放:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。
- 可重入性:一个线程如果获取了锁之后,可以再次对其请求加锁。
- 高性能和高可用:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。
- 安全性:锁只能被持有的客户端删除,不能被其他客户端删除
文章标题:怎样实现redis分布式锁,发布者:Z, ZLW,转载请注明出处:https://worktile.com/kb/p/34703