Redis实现分布式锁Lua脚本

2.9k 词

简单锁

采用键值对存储,键是锁的标识,值是全局唯一值(如uuid)。

获取锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14


local key = KEYS[1] -- 锁标识
local value = ARGV[1] -- 全局唯一值
local ttl = tonumber(ARGV[2]) or 0 -- 过期时间,默认不过期

if (redis.call('setnx', key, value) == 1) then
if (ttl > 0) then
redis.call('expire', key, ttl)
end
return 1
end

return 0

释放锁

1
2
3
4
5
6
7
8
9
10
11


local key = KEYS[1] -- 锁标识
local value = ARGV[1] -- 全局唯一值

if (redis.call('get', key) == value) then
redis.call('del', key)
return 1
end

return 0

运行效果

将获取锁和释放锁的代码分别保存到lock_aquire.lua和lock_release.lua文件。

1
2
$ vi lock_aquire.lua
$ vi lock_release.lua

将两个lua脚本文件加载到redis中,分别返回一个sha值。

1
2
3
4
$ redis-cli script load "$(cat lock_aquire.lua)"
"cc5c8c3fe44fb651fb06e4c39300749ced0ae22b"
$ redis-cli script load "$(cat lock_release.lua)"
"c6a18bc663c9d324644615321834667b914b8350"

在redis客户端中执行evalsha命令,传入步骤2生成的sha值。

1
2
3
4
5
6
7
8
redis> evalsha cc5c8c3fe44fb651fb06e4c39300749ced0ae22b 1 dist_lock 111111
(integer) 1 # 客户端111111获取锁成功
redis> evalsha cc5c8c3fe44fb651fb06e4c39300749ced0ae22b 1 dist_lock 222222
(integer) 0 # 客户端222222获取锁失败
redis> evalsha c6a18bc663c9d324644615321834667b914b8350 1 dist_lock 111111
(integer) 1 # 客户端111111释放锁成功
redis> evalsha cc5c8c3fe44fb651fb06e4c39300749ced0ae22b 1 dist_lock 222222
(integer) 1 # 客户端222222获取锁成功

可重入锁

采用Hash结构存储,只有一个字段,字段的键是全局唯一值(如uuid),代表锁的拥有者,字段的值是个计数器。每获取一次锁计数器加1,每释放一次锁计数器减1,当计数器减为0时删除锁。

获取锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


local key = KEYS[1] -- 锁标识
local value = ARGV[1] -- 全局唯一值
local ttl = tonumber(ARGV[2]) or 0 -- 过期时间,默认不过期

if (redis.call('exists', key) == 0) then
redis.call('hincrby', key, value, 1)
if (ttl > 0) then
redis.call('expire', key, ttl)
end
return 1
end

if (redis.call('hexists', key, value) == 1) then
redis.call('hincrby', key, value, 1)
return 1
end

return 0

释放锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14


local key = KEYS[1] -- 锁标识
local value = ARGV[1] -- 全局唯一值

if (redis.call('hexists', key, value) == 1) then
-- 当计数器为0时才真正删除锁
if (redis.call('hincrby', key, value, -1) < 1) then
redis.call('del', key)
end
return 1
end

return 0

运行效果

将获取锁和释放锁的代码分别保存到lock_aquire.lua和lock_release.lua文件。

1
2
$ vi lock_aquire.lua
$ vi lock_release.lua

将两个lua脚本文件加载到redis中,分别返回一个sha值。

1
2
3
4
$ redis-cli script load "$(cat lock_aquire.lua)"
"464f5e1f5a94422063a0ed087e4e545dd7786d07"
$ redis-cli script load "$(cat lock_release.lua)"
"f33de5bbd21fcb322e226d1ebcb5a6fdd51f4f2f"

在redis客户端中执行evalsha命令,传入步骤2生成的sha值。

1
2
3
4
5
6
7
8
9
10
11
12
redis> evalsha 464f5e1f5a94422063a0ed087e4e545dd7786d07 1 dist_lock 111111
(integer) 1 # 客户端111111获取锁成功
redis> evalsha 464f5e1f5a94422063a0ed087e4e545dd7786d07 1 dist_lock 222222
(integer) 0 # 客户端222222获取锁失败
redis> evalsha 464f5e1f5a94422063a0ed087e4e545dd7786d07 1 dist_lock 111111
(integer) 1 # 客户端111111再次获取锁
redis> evalsha f33de5bbd21fcb322e226d1ebcb5a6fdd51f4f2f 1 dist_lock 111111
(integer) 1 # 客户端111111释放锁成功
redis> evalsha f33de5bbd21fcb322e226d1ebcb5a6fdd51f4f2f 1 dist_lock 111111
(integer) 1 # 客户端111111再次释放锁
redis> evalsha 464f5e1f5a94422063a0ed087e4e545dd7786d07 1 dist_lock 222222
(integer) 1 # 客户端222222获取锁成功

参考链接