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 脚本
- 加载 Lua 脚本到 Redis 中
1 | > SCRIPT LOAD "Lua Script" |
Redis 会将字符串的 Lua 脚本存储起来,便于后续执行,并返回脚本的 SHA1
hash 值,当我们想要执行脚本时,通过 EVALSHA
命令,带着这个 hash 值以及 Key 和 其他脚本需要的参数列表
- 执行脚本
1 | > EVALSHA sha1 numkeys key [key ...] arg [arg ...] |
- 直接执行脚本
1 | > EVAL script numkeys key [key ...] arg [arg ...] |
Redis 会自动缓存该脚本,可以直接调用该脚本的 SHA1
hash 值
简单示例
1 | SCRIPT LOAD "return 'hello world!'" |
注意事项
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 脚本, EVAL
和 EVALSHA
以应该被视作一个命令,只不过较为复杂而已。
当执行 Lua 脚本时, Redis 不允许运行其他的读或写命令。如果编写的脚本永远不返回,就会阻塞其他客户端的执行。 Redis 提供了两种方式来停止脚本的运行。
如果脚本只是执行了一系列的读操作,且执行时间已经超过了 Redis 配置的
lua-time-limit
时间, 那么可以执行SCRIPT KILL
命令如果已经执行了写命令,Kill 脚本可能会导致 Redis 处于不一致的状态,想要恢复的化,可以执行
SHUTDOWN NOSAVE
命令, Redis 会丢失上次快照后的所有变更; 或者丢失写到 AOF 文件的操作
记得在部署生产前,一定要严格的测试 Lua 脚本
应用到现实世界
创一个脚本加载器
键值与参数列表
应该尽可能地将要操作地 Key 作为 Keys 列表的一部分,而脚本地其他输入作为参数列表的一部分,这是因为,如果内部实现了多服务器分片,就要对这些 Keys 进行校验,是否存在于同一分片上
转换为使用 Lua 脚本实现之前的功能
应用到项目中例子
1 | local incrres = redis.call('incr', KEYS[1]) |