脚本

1

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存数据也是用的该操作。

缓存脚本

  • eval
  • evalsha
  • script load

在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.