Redis 使用lua腳本最全教程 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腳本最全教程

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章