Lua脚本在redis中的使用学习

0.前言

不同于之前遇到的redisTemplate的简单set、get方法,这里是使用Redis脚本执行redis操作。

DefaultRedisScript<List> script = LuaUtils.queryByVinsScript();
List<String> keys = LuaUtils.queryByVinsKeys(vins);
redisTemplate.execute(script, keys, redisArgs);

1.使用redis脚本的原因

在一些使用redis的场景中,会经常遇到并发问题。为了控制命令的执行顺序,redis提供了脚本执行功能。

Redis 保证脚本会以原子性(atomic)的方式执行: 当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。

2.案例

public static List<String> queryByVinsKeys(Set<String> vins) {
    return vins.stream().map(vin -> PREFIX_LATEST + vin).collect(Collectors.toList());
}

/**
 * @return 数据内容:DATA|DATA|....|DATA
 */
public static DefaultRedisScript<List> queryByVinsScript() {
    String luaScript = "local datas = {} ";
    luaScript += "for i = 1, #KEYS do ";
    luaScript += "  local data = redis.call('hget', KEYS[i], 'data') ";
    luaScript += "  if data ~= false then ";
    luaScript += "      table.insert(datas, data) ";
    luaScript += "  end ";
    luaScript += "end ";
    luaScript += "return datas ";
    DefaultRedisScript<List> redisScript = new DefaultRedisScript<>();
    redisScript.setScriptText(luaScript);
    redisScript.setResultType(List.class);
    return redisScript;
}

上面的两个方法,一个是获取指定脚本中的keys,一个是获取指定脚本script,这两个参数是执行redis脚本必须要传的参数。

还有一个参数redisArgs,是想传入脚本的key以外的参数,也可以参与脚本的执行,这个参数是选填。

  • keys

这里不必详述,你要查询的存储在redis中的数据对应的keys,可以看到它的结构是List,可以传递一个或者多个key

  • script

这里是要执行的脚本内容,脚本是用Lua编写的。其中每一行代码的含义如下:

-- 定义一个空表datas,用于存储查询到的数据
local datas = {}
-- 定义一个循环,循环从1开始,循环的次数是传入的keys的数量;#KEYS-获取KEYS的长度
for i = 1, #KEYS do  
	-- 循环体第一行,执行redis命令查询数据存储到变量data里,相当于命令:hget 第i个key 'data',i是循环的次数,'data'是要查询的字段名
    -- 例如我在redis中存的是hash类型的数据。其中keys有:student1、student2、student3...,每一个key对应的value是类似这样结构			{"id":1,"name":"zhangsan","age":10}的数据,那么我要获取student1、student2、student3的name,这里第一次循环执行的命令就是 hget student1 'name'
	local data = redis.call('hget', KEYS[i], 'data')   
    -- 循环体第二行,判断:如果变量data不等于false;~=-不等于
    if data ~= false then  
        -- 循环体第三行,如果上面的判断成立:把变量data存到循环体外面定义的空表datas中
        table.insert(datas, data)  
    -- 循环体第四行,结束判断。
    end 
-- 结束本次循环
end 
--返回表datas
return datas

3.Lua语法

3-1 Lua数据类型

数据类型

描述

nil

这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。

boolean

包含两个值:false和true。

number

表示双精度类型的实浮点数

string

字符串由一对双引号或单引号来表示

function

由 C 或 Lua 编写的函数

userdata

表示任意存储在变量中的C数据结构

thread

表示执行的独立线路,用于执行协同程序

table

Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。

其中table就是上面的案例中用到的。

local的意思的局部变量,如果前面不加则默认为全局变量

-- 创建一个空的 table
local tbl1 = {}
 
-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}

-- 遍历在控制台打印输出表tbl2的索引
for key, val in pairs(tbl2) do
    print("Key", key)
end
-- 执行结果
Key    1
Key    2
Key    3
Key    4

Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。 Lua 里表的默认初始索引一般以 1 开始

-- table_test.lua 脚本文件
a = {}
a["key"] = "value"
key = 10
a[key] = 22
a[key] = a[key] + 11
for k, v in pairs(a) do
    print(k .. " : " .. v)
end

相当于表table的存储数据结构如下表所示,由值和索引组成,根据索引读和写值。


“value”

22


值n

索引

“key”

10


索引n

  • 注意:通过索引获取值时,常规写法是:table[i]。当索引为字符串类型时的,也可以简化为:table.i。
> site = {}
> site["key"] = "www.runoob.com"
> print(site["key"])
www.runoob.com
> print(site.key)
www.runoob.com

3-2 Lua 变量

  • 变量在使用前,需要在代码中进行声明,即创建该变量
  • 如果不用local声明,则默认为全局变量。
  • 未声明和未赋值的变量默认值未nil,相当于不存在。
  • 删除变量的方法为:给变量赋值为nil。
-- 声明变量
a = 5               -- 全局变量
local b = 5         -- 局部变量
c,d = 1,2
-- 给变量重新赋值
a = "hello" .. "world"  -- 字符串拼接语法:str ... str
b = b + 1				-- 数字类型计算语法:num + num

-- 给多个变量赋值时,当变量的个数与值的个数不匹配时的规则
a, b, c = 0, 1
print(a,b,c)             --> 0   1   nil
 
a, b = a+1, b+1, b+2     -- value of b+2 is ignored
print(a,b)               --> 1   2
 
a, b, c = 0
print(a,b,c)             --> 0   nil   nil

3-3 Lua循环

for循环语法

-- var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1
-- 相当于 for(int var = exp1;i <= exp2;i + exp3)
for var=exp1,exp2,exp3 do  
    <执行体>  
end

for循环例子

for i=1,f(x) do
    print(i)
end
 
for i=10,1,-1 do
    print(i)
end

for的三个表达式在循环开始前一次性求值,以后不再进行求值。比如上面的f(x)只会在循环开始前执行一次,其结果用在后面的循环中。

function f(x)  
    print("function")  
    return x*2  
end  
for i=1,f(5) do 
    print(i)  
end
-- 运行结果
function
1
2
3
4
5
6
7
8
9
10
  • 泛型for循环

泛型 for 循环通过一个迭代器函数来遍历所有值,类似 java 中的 foreach 语句。

Lua 编程语言中泛型 for 循环语法格式:

--打印数组a的所有值  
a = {"one", "two", "three"}
for i, v in ipairs(a) do
    print(i, v)
end

i是数组索引值,v是对应索引的数组元素值。ipairs是Lua提供的一个迭代器函数,用来迭代数组。

days = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}  
for i,v in ipairs(days) do
    print(v) 
end
-- 运行结果
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday