01-Redis Lua

基本操作

Redis 中执行 Lua 可以通过两种方式:

  • eval:将 Lua 脚本或命令直接使用 Redis 执行,
  • evalsha:把脚本或命令保存到 Redis 中,然后使用一串 sha 码调用(可以理解为调用函数)

在Redis使用LUA脚本的好处包括:

  1. 减少网络开销,在 Lua 脚本中可以把多个命令放在同一个脚本中运行;

  2. 原子操作,Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心会出现竞态条件;

    • ACID的原子性是指:事务中的命令要么全执行,要么全部不执行;
    • Redis 中执行 Lua 脚本原子性是指:Lua脚 本会作为一个整体执行且不被其他客户端打断,至于 Lua 脚本里面的命令是否必须全部成功,或者全部失败,并不要求。
  3. 复用性,客户端发送的脚本会存储在Redis中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑

eval

语法:

1
2
3
4
5
6
7
eval    脚本内容    key个数   key列表   参数列表

参数:
* 脚本内容:就是要执行的lua脚本内容
* key个数:表示参数中有多个个key,Redis 中的 key 是从 1 开始的,如果没有 key 的参数,就写0
* key列表:作为参数传递给 Lua 语言,Lua 中是用 KEYS[n] 来获取对应的参数
* 参数列表:是传递给 Lua 语言,可填可不填,Lua 中使用 ARGV[n] 来获取对应的参数

例子(在 Redis 中执行):

1
eval 'return "hello" .. KEYS[1] .. ARGV[1]' 1 redis world

输出:

1
"hello redisworld"

这里传入的key个数为1,所以 Redis 是key而world是参数

使用 redis-cli 执行。

1
redis-cli --eval 脚本文件 key列表,参数列表

evalsha

这个操作相当于把脚本加载到 Redis ,得到一个 SHA1 的校验和,然后使用这个 SHA1 码来调用对应的 Lua 脚本,避免每次去发送Lua 脚本。

加载脚本

1
script load

例子:

1
2
redis-cli script load "$(cat del-batch.lua)"
"e812abcb57c0360287ff97f74e444c04144382c9"

使用

执行 evalsha

1
evalsha     脚本sha值  key个数   key列表   参数列表

如:

1
2
127.0.0.1:6379> evalsha e812abcb57c0360287ff97f74e444c04144382c9 1 A*
"del pattern is : A*, count is:0"

Redis 管理脚本的方式

Redis 提供了几个命令来管理脚本

  • script load
  • script exists
  • script flush
  • script kill

script load

用于将 Lua 脚本加载到 Redis 内存中

1
script load [script]

script exists

用于判断 sha1 值是否已经加载到 Redis 内存中

1
script exists sha1...

返回个数

script flush

用于清除 Redis 内存已经加载的所有脚本

1
script flush

script kill

用于杀掉正在执行的Lua脚本

1
script kill

如果 Lua 脚本比较耗时,甚至 Lua 脚本存在问题,那么此时 Lua 脚本的执行会阻塞 Redis ,直到脚本执行完毕或者外部干预将其结束

有一点需要注意,如果Lua脚本正在执行写操作,script kill命令不会生效,这时只能等待脚本执行结束,或使用shutdown save停掉 Redis 服务

使用 lua 的优点

  • Lua脚本在 Redis 中是原子执行的,执行过程中不会插入其他命令
  • Lua脚本可以帮助开发和运维人员创造出自己定制的命令,并可以将这些命令存放在内存中,实现复用的效果
  • Lua脚本可以将多条命令一次性打包,有效减少网络开销

在 lua 脚本中调用 Redis 方法

可参看redis官方文档

有两种方式可以调用

  • redis.call()
  • redis.pcall()

这两种方法都可以调用,区别是 call() 方法是遇到异常就停止执行并直接返回错误,而 pcall() 遇到异常会忽略掉继续执行

1
2
eval "return redis.call('set','foo','bar')" 0
OK

使用 scan 进行批量删除的例子

一个使用 Lua 脚本执行 Redis scan 命令进行批量删除的例子,文件名为del-batch.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 定义游标cur初始值为0
local cur = 0
-- 定义删除个数初始值
local count=0
-- 循环调用
repeat
-- 调用游标
local result = redis.call("scan",cur,"match",KEYS[1])
-- 将下个游标点转化为number
cur = tonumber(result[1])
local arr = result[2]
-- 循环当前游标获取到的值,进行删除
if(arr~=nil and #arr>0) then
for i,k in pairs(arr) do
local key = tostring(k)
-- 或者使用redis.call("unlink",key)
redis.call("del",key)
count = count +1
end
end
-- 当游标点为0时,退出循环
until(cur<=0)
-- 返回执行的结果
return "del pattern is : "..KEYS[1]..", count is:"..count

调用

1
redis-cli --eval del-batch.lua "TEST_KEY*"

执行了之后会删除符合规则 TEST_KEY* 的 key

调用结果

1
"del pattern is : TEST_KEY*, count is:300"

Reference


01-Redis Lua
https://flepeng.github.io/041-Redis-41-核心概念-01-Redis-Lua/
作者
Lepeng
发布于
2021年1月1日
许可协议