Lua脚本

# 未使用Lua脚本出现的问题

当我们执行完逻辑,在判断锁是自己的,然后准备释放锁之前,锁过期了,然后第二个请求进来了,这样delete锁操作释放的就是第二个请求的锁,这样我们的锁机制就失效了。

问题代码:

//先判断是否是自己的锁,再进行解锁
            if (StringUtils.equals(redisTemplate.opsForValue().get("lock"), uuid)) {
            //在判断和删除之前,因为没有保证原子性,所以会出现误删情况
                redisTemplate.delete("lock");
            }
1
2
3
4
5

问题分析:

虽然我们获取锁和添加锁时间有原子性操作的命令,但是我们判断锁和释放锁并没有一条原子性操作命令,所以问题会变的繁琐。

解决方案:

借助Lua脚本。

# Lua脚本

菜鸟教程:Lua介绍 (opens new window)

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

输出值为nil,原因是Lua打印的是脚本内的返回值,而不是print的内容。

127.0.0.1:6379> EVAL "return'hello world'" 0
"hello world"
1
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

注意:在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

创建局部变量:

127.0.0.1:6379> EVAL "local a=5 return a" 0
(integer) 5
1
2

# Lua脚本循环

for循环语法:

for var=exp1,exp2,exp3 do  
    <执行体>  
end
1
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

控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,true和非nil为真。

要注意的是Lua中 0 为 true

if...else语句

if(布尔表达式)
then
   --[ 布尔表达式为 true 时执行该语句块 --]
else
   --[ 布尔表达式为 false 时执行该语句块 --]
end
1
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

# Lua动态传递参数

以我们上面的if..else为例:

127.0.0.1:6379> EVAL "if 10>20 then return 10 else return 20 end" 0
(integer) 20
1
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为KEYS的个数,说明前两个为KEYS,10,20为KEYS[1],KEYS[2],30,40为ARGV[1],ARGV[2]