Scripting Redis with Lua

Redis 2.6 版本之前,如果想要一些 Redis 不能提供的功能,要么编写较复杂客户端代码实现,要么编辑 Redis 的 C 语言源码。 即使编辑源码并不困难,但是在商业环境中使用这样的版本,或者试图说服经理使用我们自己的 Redis 服务器版本,都是一件极具挑战的事。

Redis 引入了基于 Lua 语言的服务端脚本的支持,可以使得用户在 Redis 中执行更多的操作,简化代码,提升新能。

Lua

由巴西里约热内卢教皇天主教大学 (The Pontifical Catholic University of Rio de Janeiro in Brazil)的一支团队进行设计,实现和维护,高效,轻量,的嵌入式脚本语言,支持过程式编程,面向对象编程,函数式编程,数据驱动编程和数据描述;动态类型,虚拟机字节码解释执行,自动内存管理。

应用广泛: Adobe Photoshop Lightroom, 重点应用于嵌入式系统和游戏,如魔兽世界,愤怒的小鸟等

如何在 Redis 中使用 Lua 脚本

  1. 加载 Lua 脚本到 Redis 中
1
> SCRIPT LOAD "Lua Script"

Redis 会将字符串的 Lua 脚本存储起来,便于后续执行,并返回脚本的 SHA1 hash 值,当我们想要执行脚本时,通过 EVALSHA 命令,带着这个 hash 值以及 Key 和 其他脚本需要的参数列表

  1. 执行脚本
1
> EVALSHA sha1 numkeys key [key ...] arg [arg ...]
  1. 直接执行脚本
1
> EVAL script numkeys key [key ...] arg [arg ...]

Redis 会自动缓存该脚本,可以直接调用该脚本的 SHA1 hash 值

简单示例

1
2
3
SCRIPT LOAD "return 'hello world!'"

EVALSH sha1 0

注意事项

Lua 脚本返回的数据类型

由于 Lua 允许数据传入和传出的方式的限制,Lua 中提供的某些数据类型不允许返回,或者在返回之前被更改。

Lua value Redis return
true 1
false nil
nil nil
1.5 (浮点数) 1
1e30 平台相关的最小整数值
“string” Unchanged
1 Integer is returned unchanged

由于可能产生歧义性,所以应该尽可能地返回字符串,然后手动执行解析

原子性

和单条命令或者 MULTI/EXEC 命令一样,是原子性地 (单条命令一次运行以个;MULTI/EXEC 实现了简单的事务)。执行 Lua 脚本, EVALEVALSHA 以应该被视作一个命令,只不过较为复杂而已。

当执行 Lua 脚本时, Redis 不允许运行其他的读或写命令。如果编写的脚本永远不返回,就会阻塞其他客户端的执行。 Redis 提供了两种方式来停止脚本的运行。

  • 如果脚本只是执行了一系列的读操作,且执行时间已经超过了 Redis 配置的 lua-time-limit 时间, 那么可以执行 SCRIPT KILL 命令

  • 如果已经执行了写命令,Kill 脚本可能会导致 Redis 处于不一致的状态,想要恢复的化,可以执行 SHUTDOWN NOSAVE 命令, Redis 会丢失上次快照后的所有变更; 或者丢失写到 AOF 文件的操作

记得在部署生产前,一定要严格的测试 Lua 脚本

应用到现实世界

创一个脚本加载器

image-20201103222323879

image-20201103222345132

键值与参数列表

应该尽可能地将要操作地 Key 作为 Keys 列表的一部分,而脚本地其他输入作为参数列表的一部分,这是因为,如果内部实现了多服务器分片,就要对这些 Keys 进行校验,是否存在于同一分片上

转换为使用 Lua 脚本实现之前的功能

image-20201103222503976

image-20201103222524180

应用到项目中例子

1
2
3
4
5
6
local incrres = redis.call('incr', KEYS[1])
if (incrres == 1) then
redis.call('expire', KEYS[1], 300)
return "SUCCESS"
end
return "FALSE"