脚本
hello lua
1
2
3
4
5
6
|
cat > test01.lua <<EOF
local msg = "Hello, lua"
return msg
EOF
redis-cli EVAL "$(cat test01.lua)" 0
|
使用参数
数字2声明keys有2个 后面跟着两个keys参数
之后就是argv参数
1
2
3
4
5
6
7
|
cat > test02.lua <<EOF
local link_id = redis.call("INCR", KEY[1])
redis.call("HSET", KEYS[2], link_id, ARGV[1]+1)
return msg
EOF
redis-cli EVAL "$(cat test02.lua)" 0 req-count tom
|
不用写key个数
1
2
3
4
5
6
7
8
9
10
11
|
cat > test03.lua <<EOF
local keys, values = KEYS, ARGV
for i, k in ipairs(keys) do
redis.call('SET', k, values[i])
end
return "added 数量 == " ..tostring(table.getn(keys)) ;
EOF
redis-cli --raw --eval test03.lua t1 t2 , apple milk
#--raw 保证中文能正常显示
|
key和argv的数量任意, 不是必须个数相同
1
|
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2],ARGV[3]}" 2 k1 k2 a01 a02 a03
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
cat > test04.lua <<EOF
FUNCTION LIMIT_API_CALL(ip)
ts = CURRENT_UNIX_TIME()
keyname = ip+":"+ts
current = GET(keyname)
IF current != NULL AND current > 10 THEN
ERROR "too many requests per second"
END
IF current == NULL THEN
MULTI
INCR(keyname, 1)
EXPIRE(keyname, 1)
EXEC
ELSE
INCR(keyname, 1)
END
PERFORM_API_CALL()
EOF
redis-cli EVAL "$(cat test04.lua)" 1 aa
|
参数KEYS与ARGV区别
KEYS 与 ARGV 有啥区别么
所有的 key 会被hash计算,以决定在哪个节点执行该脚本。
执行执行lua脚本时
如果是单节点的redis , 那没啥可想的。
但如果是个redis集群时, java程序调用的脚本会送去哪个节点执行呢
key参数就是用来计算hash槽的
在向redis中存取数据, redis会对key做hash,来决定数据存在哪个节点上。
而在使用lua脚本时, 这段命令
1
|
redis-cli EVAL "return {KEYS[1],KEYS[2]}" 2 aa bb
|
如果碰巧 redis对 aa, bb计算hash后认为二者会存放在同一个节点上
则可顺利执行, 如果计算hash后认为二者会放在不同的redis节点, 则报错
而如果某脚本没有key参数, 并且脚本内操作某个key,
并且当前执行脚本的节点不是存放该key的节点, 又会报错。
解决方案
1
|
redis-cli EVAL "return {KEYS[1],KEYS[2]}" 2 aa{p001} bb{p001}
|
原先是使用aa 或 bb来计算脚本在哪个节点执行
在key中添加{p001}后, 会使用 p001 来计算hash槽 ,redis存数据也是用的该操作。
缓存脚本
在redis中缓存脚本, 可降低每次调用时的网络传输开销。
1
2
3
4
5
6
7
|
root@14f65f9493f9:/data# redis-cli
127.0.0.1:6379> eval "return 'hello lua' " 0
"hello lua"
127.0.0.1:6379> SCRIPT LOAD "return 'hello lua' "
"f5af5790869c1e2e22a7323a495df22361869cb3"
127.0.0.1:6379> EVALSHA f5af5790869c1e2e22a7323a495df22361869cb3 0
"hello lua"
|
清空脚本, 查询脚本是否存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
SCRIPT LOAD "return 'script-01' "
SCRIPT LOAD "return 'script-02' "
SCRIPT LOAD "return 'script-03' "
SCRIPT FLUSH
SCRIPT EXISTS
root@14f65f9493f9:/data# redis-cli
127.0.0.1:6379> SCRIPT LOAD "return 'script-01' "
"a4dc1a08bdb719a36ef7024631b9f52f3aeb4bb8"
127.0.0.1:6379> SCRIPT FLUSH
OK
127.0.0.1:6379> SCRIPT LOAD "return 'script-02' "
"51fde76dc5a18a281860810e647bed0ec8b51a1a"
127.0.0.1:6379> SCRIPT LOAD "return 'script-03' "
"f31e93b333d0724260b9d6d00a8f3a93393f9280"
127.0.0.1:6379> SCRIPT EXISTS a4dc1a08bdb719a36ef7024631b9f52f3aeb4bb8 51fde76dc5a18a281860810e647bed0ec8b51a1a f31e93b333d0724260b9d6d00a8f3a93393f9280
1) (integer) 0
2) (integer) 1
3) (integer) 1
127.0.0.1:6379>
|
先缓存了脚本01, 然后清空了缓存,接着加载脚本02,03
然后查询脚本 01,02,03 是否存在
返回值是个数组对应查询参数的数组 0表示不存在,1则存在
java代码在使用 evalsha 时, 应先使用 evalsha 执行调用
如正常执行, 则继续业务逻辑
如报错 说脚本不存在,则再使用 eval 执行调用 (spring-data-redis就是这么做的)
原因是,redis虽然能缓存脚本, 但有脚本可以被清空。
另外集群下,每个节点起初都是没有缓存该脚本的。
原子性
原子性说的是每条语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
set a1 cat
set a2 cat
redis.call('SET', 'a1', 'pig');
assert(1 == 2) ; //通过断言打断程序
redis.call('SET', 'a2', 'pig');
127.0.0.1:6379> set a1 cat
OK
127.0.0.1:6379> set a2 cat
OK
127.0.0.1:6379> eval "redis.call('SET', 'a1', 'pig'); assert(1 == 2) ; redis.call('SET', 'a2', 'pig'); return 8; " 0
(error) ERR Error running script (call to f_45ed8b0eaab2e214f43d761a58f5c1e4c263c24d): @user_script:1: user_script:1: assertion failed!
127.0.0.1:6379>
127.0.0.1:6379> get a1
"pig"
127.0.0.1:6379> get a2
"cat"
127.0.0.1:6379>
|
程序先设置 a1, a2 都为 cat
然后通过lua脚本将二者更新为pig 执行到中途挂了
遇上a1 更新成功, a2更新失败了
并不是想象的要么a1, a2都变成pig 要是都还是cat
调试脚本
1
2
3
4
5
6
7
8
9
10
11
12
|
cat > debug01.lua <<EOF
local k1 = ARGV[1]
local k2 = ARGV[2]
redis.debug(k1)
redis.debug(k2)
print("Hello 01")
print("Hello 02")
redis.breakpoint()
return "hello lua"
EOF
redis-cli --ldb --eval debug01.lua aa bb , ag1 ag2
|
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
root@14f65f9493f9:/data# redis-cli --ldb --eval debug01.lua aa bb , ag1
Lua debugging session started, please use:
quit -- End the session.
restart -- Restart the script in debug mode again.
help -- Show Lua script debugging commands.
* Stopped at 1, stop reason = step over
-> 1 local k1 = ARGV[1]
lua debugger> n
* Stopped at 2, stop reason = step over
-> 2 local k2 = ARGV[2]
lua debugger> s
* Stopped at 3, stop reason = step over
-> 3 redis.debug(k1)
lua debugger> s
<debug> line 3: "ag1"
* Stopped at 4, stop reason = step over
-> 4 redis.debug(k2)
lua debugger> c
<debug> line 4: nil
* Stopped at 8, stop reason = redis.breakpoint() called
-> 8 return "hello lua"
lua debugger>
|
输入n 或 s 执行单行
输入c 执行到 redis.breakpoint()
1
2
3
4
5
6
7
8
|
lua debugger> p
<value> k1 = "ag1"
<value> k2 = nil
lua debugger> p keys
No such variable.
lua debugger> p KEYS
<value> {"aa"; "bb"}
|
打印变量
同步、异步
LDB调试基于client、server模式
–ldb 异步同步, 所有操作都会被回滚,支持多个调试程序同时调试
–ldb-sync-mode 同步模式,会阻塞其他请求,且操作不会被回滚
当心不要在生产环境进行调试操作
更多参数
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
|
lua debugger> help
Redis Lua debugger help:
[h]elp Show this help.
[s]tep Run current line and stop again.
[n]ext Alias for step.
[c]continue Run till next breakpoint.
[l]list List source code around current line.
[l]list [line] List source code around [line].
line = 0 means: current position.
[l]list [line] [ctx] In this form [ctx] specifies how many lines
to show before/after [line].
[w]hole List all source code. Alias for 'list 1 1000000'.
[p]rint Show all the local variables.
[p]rint <var> Show the value of the specified variable.
Can also show global vars KEYS and ARGV.
[b]reak Show all breakpoints.
[b]reak <line> Add a breakpoint to the specified line.
[b]reak -<line> Remove breakpoint from the specified line.
[b]reak 0 Remove all breakpoints.
[t]race Show a backtrace.
[e]eval <code> Execute some Lua code (in a different callframe).
[r]edis <cmd> Execute a Redis command.
[m]axlen [len] Trim logged Redis replies and Lua var dumps to len.
Specifying zero as <len> means unlimited.
[a]bort Stop the execution of the script. In sync
mode dataset changes will be retained.
Debugger functions you can call from Lua scripts:
redis.debug() Produce logs in the debugger console.
redis.breakpoint() Stop execution like if there was a breakpoing.
in the next line of code.
|
数据类型转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
eval "return redis.call('get', 'xxyy');" 0
eval "local a = redis.call('get', 'xxyy'); if a == nil then return 0; else return 1; end" 0
eval "local a = redis.call('get', 'xxyy'); if a == false then return 0; else return 1; end" 0
127.0.0.1:6379> get xxyy
(nil)
127.0.0.1:6379> eval "return redis.call('get', 'xxyy');" 0
(nil)
127.0.0.1:6379> eval "local a = redis.call('get', 'xxyy'); if a == nil then return 0; else return 1; end" 0
(integer) 1
127.0.0.1:6379> eval "local a = redis.call('get', 'xxyy'); if a == false then return 0; else return 1; end" 0
(integer) 0
127.0.0.1:6379>
|
redis中 不存在该key时, 则查出一个
从redis中查出空(即nil类型)时, lua脚本中后被转为false
1
2
3
4
5
6
7
8
9
10
11
|
set num1 66
object encoding num1
eval "local t = redis.call('get', 'num1'); return type(t);" 0
127.0.0.1:6379> set num1 66
OK
127.0.0.1:6379> object encoding num1
"int"
127.0.0.1:6379> eval "local t = redis.call('get', 'num1'); return type(t);" 0
"string"
|
按照类型转换 redis-int 应该变成 lua-number 才对
从官方文档
1
2
3
|
https://redis.io/commands/get
Return value
Bulk string reply: the value of key, or nil when key does not exist.
|
以及帮助命令
1
2
3
4
5
6
|
127.0.0.1:6379> help get
GET key
summary: Get the value of a key
since: 1.0.0
group: string
|
可得 GET
命令的返回值为 Bulk string
数据类型转换规则
当lua脚本中使用从redis中加载的数据 , 或lua脚本中的变量作为返回值返回时,
就会出现数据类型转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
Redis数据 转为 Lua数据
Redis integer reply -> Lua number
Redis bulk reply -> Lua string
Redis multi bulk reply -> Lua table (may have other Redis data types nested)
Redis status reply -> Lua table with a single ok field containing the status
Redis error reply -> Lua table with a single err field containing the error
Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type
Lua数据 转为 Redis数据
Lua number -> Redis integer reply (the number is converted into an integer)
Lua string -> Redis bulk reply
Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)
Lua table with a single ok field -> Redis status reply
Lua table with a single err field -> Redis error reply
Lua boolean false -> Redis Nil bulk reply.
Lua boolean true -> Redis integer reply with value of 1.
https://redis.io/commands/eval#conversion-between-lua-and-redis-data-types
|
-
lua只有一种数字类型 number 没有 integer 和 float差异
因此redis会把lua-number的小数部分丢弃, 变成integer
因此如果希望返回值是 float 类型 , 应该把它变成字符串返回
-
当从lua中返回的table中包含nil时, nil后面的数据都会被丢弃
-
如果返回的table中 含有 kv数据时,该元素会被丢弃
下面这个例子演示了这几种情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
eval "return {1, 2, {3.99, 'tom'}, 4, nil, 5}" 0
127.0.0.1:6379> eval "local array = {'aa', 'bb', nil, 'cc'}; local alength= table.getn(array); return alength; " 0
(integer) 4
127.0.0.1:6379> eval "local array = {'aa', 'bb', nil, 'cc'}; return array; " 0
1) "aa"
2) "bb"
127.0.0.1:6379>
127.0.0.1:6379> eval "return {1, 2, {3.99, 'tom'}, 4, nil, 5}" 0
1) (integer) 1
2) (integer) 2
3) 1) (integer) 3
2) "tom"
4) (integer) 4
|
redis中提供的lua包
- base lib.
- table lib.
- string lib.
- math lib.
- struct lib.
- cjson lib.
- cmsgpack lib.
- bitop lib.
- redis.sha1hex function.
- redis.breakpoint and redis.debug function in the context of the Redis Lua debugger.
文章作者
duansheli
上次更新
2019-12-25
(325c7b3)