Redis 使用lua脚本最全教程
1、redis 使用lua脚本的语法Redis Eval 命令 - 执行 Lua 脚本redis 127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second1) "key1"2) "key2"3) "first"4) "second"其中script: 参数是一段 Lua 5
1、redis 使用lua脚本的语法
redis 127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
其中
script: 参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数。
numkeys: 用于指定键名参数的个数。
key [key …]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
arg [arg …]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。
可以直接通过 redis-cli --eval执行写好的lua脚本:
redis-cli --eval /test.lua 0
2、Lua
lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
print('hello world')
-- 注释
a=1
b="abc"
c={}
d=print
c={"a","b","c"}
print(type(a))
print(type(b))
print(type(c))
print(type(d))
-- 多行注释
[[
-------- Output ------
number
string
table
function
]]
a="single 'quoted' string and double \"quoted\" string inside"
b='single \'quoted\' string and double "quoted" string inside'
c= [[ multiple line
with 'single'
and "double" quoted strings inside.]]
print(a)
print(b)
print(c)
[[
-------- Output ------
single 'quoted' string and double "quoted" string inside
single 'quoted' string and double "quoted" string inside
multiple line
with 'single'
and "double" quoted strings inside.
]]
a,b,c,d,e = 1, 2, "three", "four", 5
a,b,c,d,e = 1, 1.123, 1E9, -123, .0008
print("a="..a, "b="..b, "c="..c, "d="..d, "e="..e)
[[
-------- Output ------
a=1 b=1.123 c=1000000000 d=-123 e=0.0008
]]
address={} -- empty address
address.Street="Wyman Street"
address.StreetNumber=360
address.AptNumber="2a"
address.City="Watertown"
address.State="Vermont"
address.Country="USA"
print(address.StreetNumber, address["AptNumber"])
-- end 结束
a=1
if a==1 then
print ("a is one")
end
c=3
if c==1 then
print("c is 1")
elseif c==2 then
print("c is 2")
else
print("c isn't 1 or 2, c is "..tostring(c))
end
a=1
b=(a==1) and "one" or "not one"
print(b)
-- b = ((a==1) ? "one" : "not one")
-- 循环
a=1
while a~=5 do -- Lua uses ~= to mean not equal
a=a+1
io.write(a.." ")
end
a=0
repeat
a=a+1
print(a)
until a==5
for a=1,6,3 do io.write(a) end
-- 14
[[
for (int i = 1; i < 6; i += 3) {
printf(i);
}
]]
for key,value in pairs({1,2,3,4}) do print(key, value) end
[[
-------- Output ------
1 1
2 2
3 3
4 4
]]
a={1,2,3,4,"five","elephant", "mouse"}
for i,v in pairs(a) do print(i,v) end
[[
-------- Output ------
1 1
2 2
3 3
4 4
5 five
6 elephant
7 mouse
]]
-- break
a=0
while true do
a=a+1
if a==10 then
break
end
end
-- 函数
function myFirstLuaFunctionWithMultipleReturnValues(a,b,c)
return a,b,c,"My first lua function with multiple return values", 1, true
end
a,b,c,d,e,f = myFirstLuaFunctionWithMultipleReturnValues(1,2,"three")
print(a,b,c,d,e,f)
[[
-------- Output ------
1 2 three My first lua function with multiple return values 1 true
]]
-- local 局部变量
function myfunc()
local b=" local variable"
a="global variable"
print(a,b)
end
function printf(fmt, ...)
io.write(string.format(fmt, ...))
end
printf("Hello %s from %s on %s\n",
os.getenv"USER" or "there", _VERSION, os.date())
-- Math functions:
-- math.abs, math.acos, math.asin, math.atan, math.atan2,
-- math.ceil, math.cos, math.cosh, math.deg, math.exp, math.floor,
-- math.fmod, math.frexp, math.huge, math.ldexp, math.log, math.log10,
-- math.max, math.min, math.modf, math.pi, math.pow, math.rad,
-- math.random, math.randomseed, math.sin, math.sinh, math.sqrt,
-- math.tan, math.tanh
-- String functions:
-- string.byte, string.char, string.dump, string.find, string.format,
-- string.gfind, string.gsub, string.len, string.lower, string.match,
-- string.rep, string.reverse, string.sub, string.upper
-- Table functions:
-- table.concat, table.insert, table.maxn, table.remove, table.sort
-- IO functions:
-- io.close , io.flush, io.input, io.lines, io.open, io.output, io.popen,
-- io.read, io.stderr, io.stdin, io.stdout, io.tmpfile, io.type, io.write,
-- file:close, file:flush, file:lines ,file:read,
-- file:seek, file:setvbuf, file:write
print(io.open("file doesn't exist", "r"))
-- OS functions:
-- os.clock, os.date, os.difftime, os.execute, os.exit, os.getenv,
-- os.remove, os.rename, os.setlocale, os.time, os.tmpname
-- require导入包
require( "iuplua" )
ml = iup.multiline
{
expand="YES",
value="Quit this multiline edit app to continue Tutorial!",
border="YES"
}
dlg = iup.dialog{ml; title="IupMultiline", size="QUARTERxQUARTER",}
dlg:show()
print("Exit GUI app to continue!")
iup.MainLoop()
Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。
Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。
3、redis使用Lua
通过return 返回结果,通过redis.call执行redis命令:
eval "return redis.call('keys','*')" 0
以上命令返回所有的key,类似于直接执行 keys *
以下命令删除dict*格式的所有key值
eval "local redisKeys = redis.call('keys',KEYS[1]..'*');for i,k in pairs(redisKeys) do redis.call('del',k);end;return redisKeys;" 1 dict
展开如下
local redisKeys = redis.call('keys',KEYS[1]..'*');
for i,k in pairs(redisKeys) do
redis.call('del',k);
end;
return redisKeys;
以下命令删除所有key值
eval "local sum = 0;for i,k in pairs(redis.call('keys','*')) do redis.call('del', k);sum=sum+1;end; return 'clear '..sum..' key'" 0
像这样:
批量生产key值,设置过期时间,参数: 2、 key个数、 key前缀、 key的值、 key的过期时间(可选)
eval "for i=1,KEYS[1],1 do local k=KEYS[2]..i; redis.call('set',k,ARGV[1]);if ARGV[2] then redis.call('expire',k,ARGV[2]) end;end;return redis.call('keys',KEYS[2]..'*');" 2 10 test 0 20
删除所有值为0的key,参数:0、值X
eval "local ks = {};for i,k in pairs(redis.call('keys','*')) do local v = redis.call('get',k);if v==ARGV[1] then redis.call('del',k);table.insert(ks,k); end;end;return ks;" 0 0
删除所有永不过期的key
eval "local ks = {};for i,k in pairs(redis.call('keys','*')) do local ttl = redis.call('ttl',k);if ttl==-1 then redis.call('del',k);table.insert(ks,k); end;end;return ks;" 0
获取所有值为0,并以test为前缀的key列表,参数:2、x、y
eval "local ks = {};for i,k in pairs(redis.call('keys',KEYS[1]..'*')) do local v = redis.call('get',k);if v==ARGV[1] then table.insert(ks,k); end;end;return ks;" 1 test 0
redis分布式锁实现,之加锁。如果不存在lock,则设置local为233,并设置过期时间为60,如果返回1表示加锁成功,返回0则加锁失败,该操作是原子操作,可以由等效命令 set lock 233 nx ex 60
代替:
eval "if redis.call('get',KEYS[1]) then return 0;else redis.call('set',KEYS[1],ARGV[1]);redis.call('expire',KEYS[1],ARGV[2]);return 1;end;" 1 lock 233 60
展开如下
if redis.call('get',KEYS[1])
then return 0;
else
redis.call('set',KEYS[1],ARGV[1]);
redis.call('expire',KEYS[1],ARGV[2]);
return 1;
end
redis分布式锁实现,之释放锁。如果不存在lock,则无需释放,如果存在lock并且值和传入的值一致,那么删除lock,释放成功,其他情况返回释放失败。成功:1,失败0。
eval "local v = redis.call('get',KEYS[1]);if v then if v~=ARGV[1] then return 0;end;redis.call('del',KEYS[1]);end;return 1;" 1 lock 233
展开如下
local v = redis.call('get',KEYS[1]);
if v then
-- 如果和传入的值不同,返回0表示失败
if v~=ARGV[1] then
return 0;
end;
-- 删除key
redis.call('del',KEYS[1]);
end;
return 1;
1、A程序加锁lock_a,设置值233,加锁600秒,返回1成功
2、B程序尝试给lock_a加锁,返回0,失败
3、B程序尝试释放A的锁,(这当然是不允许的),B不知道lock_a的值,释放锁失败,返回0
4、A程序释放锁,返回1,释放成功
5、B程序尝试再给lock_a加锁,加锁成功
4、redisTemplate执行脚本的方法封装
@Component
public class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 执行 lua 脚本
* @author hengyumo
* @since 2021-06-05
*
* @param luaScript lua 脚本
* @param returnType 返回的结构类型
* @param keys KEYS
* @param argv ARGV
* @param <T> 泛型
*
* @return 执行的结果
*/
public <T> T executeLuaScript(String luaScript, Class<T> returnType, String[] keys, String... argv) {
return redisTemplate.execute(RedisScript.of(luaScript, returnType),
new StringRedisSerializer(),
new GenericToStringSerializer<>(returnType),
Arrays.asList(keys),
(Object[])argv);
}
}
使用很简单,以下用上边使用过的两个脚本作为示例:
@Resource
private RedisUtil redisUtil;
@Test
@SuppressWarnings("unchecked")
public void testExecuteLuaScript() {
String script = "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}";
List<Object> list = (List<Object>)redisUtil.executeLuaScript(script,
List.class, new String[] {"a", "b"}, "a", "b");
list.forEach(x -> System.out.println(x.toString()));
script = "for i=1,KEYS[1],1 do local k=KEYS[2]..i; redis.call('set',k,ARGV[1]);" +
"if ARGV[2] then redis.call('expire',k,ARGV[2]) end;end;" +
"return redis.call('keys',KEYS[2]..'*');";
list = (List<Object>)redisUtil.executeLuaScript(script,
List.class, new String[] {"10", "test"}, "0", "60");
list.forEach(x -> System.out.println(x.toString()));
}
输出结果,返回的结果是List<List>:
[a]
[b]
[a]
[b]
[test1]
[test10]
[test2]
[test3]
[test4]
[test5]
[test6]
[test7]
[test8]
[test9]
查看redis:
封装方法:删除以key为前缀的所有键值
// 以下命令删除xxx*格式的所有key值
private final static String LUA_SCRIPT_CLEAR_WITH_KEY_PRE =
"local redisKeys = redis.call('keys',KEYS[1]..'*');" +
"for i,k in pairs(redisKeys) do redis.call('del',k);end;" +
"return redisKeys;";
/**
* @author hengyumo
* @since 2021-06-05
*
* 删除以key为前缀的所有键值
* @param keyPre 前缀
* @return 返回删除掉的所有key
*/
public List<String> deleteKeysWithPre(String keyPre) {
@SuppressWarnings("unchecked")
List<Object> result = executeLuaScript(LUA_SCRIPT_CLEAR_WITH_KEY_PRE, List.class, new String[] {keyPre});
return result.stream().map(x -> {
if (x instanceof List) {
@SuppressWarnings("unchecked")
List<String> list = (List<String>) x;
if (list.size() > 0) {
return list.get(0);
}
}
return null;
}).filter(Objects::nonNull).collect(Collectors.toList());
}
使用很简单:
@Test
public void testDeleteKeysWithPre() {
List<String> list = redisUtil.deleteKeysWithPre("DAWN");
list.forEach(System.out::println);
}
END
写作不易,您的小小一个👍会让我更加有动力。
那在终点之前,我愿意再爱一遍。 ——墨
更多推荐
所有评论(0)