深入学习 Redis 原理 - Lua

Introduction

EVAL and EVALSHA are used to evaluate scripts using the Lua interpreter built into Redis starting from version 2.6.0.

Related commands

  • EVAL
  • EVALSHA

为什么要用Lua?

  1. 减少网络开销(批量执行命令):不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输;
  2. 原子操作:Redis 将整个脚本作为一个原子执行,无需担心并发,也就无需事务;
  3. 复用:脚本会永久保存 Redis 中,其他客户端可继续使用。

Practice

EVAL

EVAL script numkeys key [key …] arg [arg …]

1
2
3
4
5
6
7
8
9
10
11
12
EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

1) "key1"
2) "key2"
3) "first"
4) "second"


eval "return redis.call('set', KEYS[1], ARGV[1])" 1 username Jason

# execute script
# redis-cli --eval script.lua

Evalsha

1
2
3
4
5
script load "return 'hello world'"
# output "8df13960c2be12fec0947206c9e087cd68b2f1b9"

evalsha "8df13960c2be12fec0947206c9e087cd68b2f1b9" 0
# output "hello world"

IP Access Limitation

1
2
3
4
5
6
7
8
9
10
11
local access = redis.call('incr', KEYS[1])
if tonumber(access) == 1 then
redis.call('expire', KEYS[1], ARGV[1])
else if tonumber(access) > tonumber(ARGV[2])
return 0
else
return 1
end

# 5秒访问10次
# redis-cli --eval "ip-limitation.lua" 192.168.1.1 , 5 10

Endless Loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
eval 'while(true) do end' 0

# 此时 Redis 完全阻塞,如果此时另一个客户端进行操作,则输出
# (error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
# execute command SCRIPT KILL to stop execute script
# relatived configuration is lua-time-limit
# Redis提供了一个lua-time-limit参数,默认是5秒,它是Lua脚本的“超时时间”,但这个超时时间仅仅是当Lua脚本超过lua-time-limit后,向其他命令发送BUSY的信号,但是不会停止掉服务端和客户端的执行脚本。
```

``` bash
eval "redis.call('set', 'username', 'jason') while(true) do end" 0

# execute command SCRIPT KILL to stop execute script
# (error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.
# 注意:如果当前Lua脚本已经执行过写操作,那么script kill将不会生效

Attention

redis.call() 与 redis.pcall() 的区别

两者唯一区别在于它们对错误处理的不同:

  1. 当 redis.call() 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因:

    1
    2
    EVAL "return redis.call('SADD','evalShell','a')" 0
    (error) ERR Error running script (call to f_e17faafbc130014cebb229b71e0148b1f8f52389): @user_script:1: WRONGTYPE Operation against a key holding the wrong kind of value
  2. redis.pcall() 出错时并不引发(raise)错误,而是返回一个带 err 域的 Lua 表(table),用于表示错误(这样与命令行客户端直接操作返回相同):

    1
    2
    EVAL "return redis.pcall('SADD','evalShell','a')" 0
    (error) WRONGTYPE Operation against a key holding the wrong kind of value

Redis 是单线程服务,如果某个客户端发生了阻塞,如何继续接收其他客户端的命令?


Reference