Redis 使用lua脚本最全教程

也可以参考:redis使用lua脚本

为什么使用:
(1) 减少网络开销: 在Redis操作需求需要向Redis发送5次请求,而使用脚本功能完成同样的操作只需要发送一个请求即可,减少了网络往返时延。

(2) 原子操作: Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说在编写脚本的过程中无需担心会出现竞态条件,也就无需使用事务。事务可以完成的所有功能都可以用脚本来实现。

(3) 复用: 客户端发送的脚本会永久存储在Redis中,这就意味着其他客户端(可以是其他语言开发的项目)可以复用这一脚本而不需要使用代码完成同样的逻辑。

(4) 速度快:见 与其它语言的性能比较, 还有一个 JIT编译器可以显著地提高多数任务的性能; 对于那些仍然对性能不满意的人, 可以把关键部分使用C实现, 然后与其集成, 这样还可以享受其它方面的好处。

 

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 second
1) "key1"
2) "key2"
3) "first"
4) "second"
1
2
3
4
5
其中
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
1
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()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。

Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。

3、redis使用Lua
通过return 返回结果,通过redis.call执行redis命令:

eval "return redis.call('keys','*')" 0
1
以上命令返回所有的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
1
展开如下

local redisKeys = redis.call('keys',KEYS[1]..'*');
for i,k in pairs(redisKeys) do
redis.call('del',k);
end;
return redisKeys;
1
2
3
4
5
以下命令删除所有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
1
像这样:


批量生产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
1


删除所有值为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
1
删除所有永不过期的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
1
获取所有值为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
1
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
1
展开如下

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
2
3
4
5
6
7
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
1
展开如下

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
2
3
4
5
6
7
8
9
10
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);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
使用很简单,以下用上边使用过的两个脚本作为示例:


@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()));

}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
输出结果,返回的结果是List<List>:

[a]
[b]
[a]
[b]
[test1]
[test10]
[test2]
[test3]
[test4]
[test5]
[test6]
[test7]
[test8]
[test9]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
查看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());
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
使用很简单:


@Test
public void testDeleteKeysWithPre() {
List<String> list = redisUtil.deleteKeysWithPre("DAWN");
list.forEach(System.out::println);
}
1
2
3
4
5
6
END

参考:Redis 使用lua脚本最全教程

 

posted @ 2022-10-09 16:35  aspirant  阅读(9014)  评论(0编辑  收藏  举报