Lua脚本
早睡蛋
# 未使用Lua脚本出现的问题
当我们执行完逻辑,在判断锁是自己的,然后准备释放锁之前,锁过期了,然后第二个请求进来了,这样delete锁操作释放的就是第二个请求的锁,这样我们的锁机制就失效了。
问题代码:
//先判断是否是自己的锁,再进行解锁
if (StringUtils.equals(redisTemplate.opsForValue().get("lock"), uuid)) {
//在判断和删除之前,因为没有保证原子性,所以会出现误删情况
redisTemplate.delete("lock");
}
1
2
3
4
5
2
3
4
5
问题分析:
虽然我们获取锁和添加锁时间有原子性操作的命令,但是我们判断锁和释放锁并没有一条原子性操作命令,所以问题会变的繁琐。
解决方案:
借助Lua脚本。
# Lua脚本
Redis天生支持Lua脚本语言,在Redis客户端内,执行Lua脚本命令如下:
EVAL script numbers key[key...] arg[arg...]
1
script为Lua脚本字符串
numbers为key列表元素个数
为什么Lua脚本能确保原子性?
因为Lua能一次性发送多条指令给Redis客户端,Redis是单线程的,执行指令遵循one by one规则,所以在执行指令期间是不允许其他指令插队。
实例:
127.0.0.1:6379> EVAL "print('hello world')" 0
(nil)
1
2
2
输出值为nil,原因是Lua打印的是脚本内的返回值,而不是print的内容。
127.0.0.1:6379> EVAL "return'hello world'" 0
"hello world"
1
2
2
# Lua脚本变量类型
-- test.lua 文件脚本
a = 5 -- 全局变量
local b = 5 -- 局部变量
function joke()
c = 5 -- 全局变量
local d = 6 -- 局部变量
end
joke()
print(c,d) --> 5 nil
do
local a = 6 -- 局部变量
b = 6 -- 对局部变量重新赋值
print(a,b); --> 6 6
end
print(a,b) --> 5 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注意:在Redis里,是不支持我们创建全局变量的,创建全局变量会报错:
127.0.0.1:6379> EVAL "a=5 return a" 0
(error) ERR Error running script (call to f_5f2737ecdb3f8fa17d982773a2901a4015443ed7): @enable_strict_lua:8: user_script:1: Script attempted to create global variable 'a'
1
2
2
创建局部变量:
127.0.0.1:6379> EVAL "local a=5 return a" 0
(integer) 5
1
2
2
# Lua脚本循环
for循环语法:
for var=exp1,exp2,exp3 do
<执行体>
end
1
2
3
2
3
var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。
# Lua脚本流程控制
if语句
--[ 0 为 true ]
if(0)
then
print("0 为 true")
end
1
2
3
4
5
2
3
4
5
控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,true和非nil为真。
要注意的是Lua中 0 为 true
if...else语句
if(布尔表达式)
then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end
1
2
3
4
5
6
2
3
4
5
6
if...else if...else语句
if( 布尔表达式 1)
then
--[ 在布尔表达式 1 为 true 时执行该语句块 --]
elseif( 布尔表达式 2)
then
--[ 在布尔表达式 2 为 true 时执行该语句块 --]
elseif( 布尔表达式 3)
then
--[ 在布尔表达式 3 为 true 时执行该语句块 --]
else
--[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# Lua动态传递参数
以我们上面的if..else为例:
127.0.0.1:6379> EVAL "if 10>20 then return 10 else return 20 end" 0
(integer) 20
1
2
2
我们要做的就是将10和20这两个变量改为动态参数。
127.0.0.1:6379> EVAL "if KEYS[1]>ARGV[1] then return KEYS[2] else return ARGV[2] end" 2 10 20 30 40
"40"
1
2
2
2为KEYS的个数,说明前两个为KEYS,10,20为KEYS[1],KEYS[2],30,40为ARGV[1],ARGV[2]