Redis 支持直接运行 Lua 脚本,通过编写 Lua 脚本,可以扩展出更多强大的功能,这是一般的 KV 缓存引擎不具备的。

通过 Lua 脚本扩展 Redis 如下:

  • 通过 EVAL 和 EVALSHA 两个接口支持 Lua,功能扩展可以根据业务定制。
    一般说来,只需要修改应用的代码即可,不需要修改 Redis 代码,修改 Redis 代码要升级 Redis 也麻烦。
  • 可以缓存编译后的 Lua 代码,一次编译,多次执行。
    Redis 可以把每个执行过的 Lua 脚本编译后缓存下来,再次执行同样的代码时,会根据 Lua 代码的 SHA1 取出缓存程序直接执行,基本上免去了编译 Lua 代码的环节,性能上接近于 Redis 原生性能。

Hello

  • 直接运行 Lua 脚本文件。

运行方式如下:

redis-cli[ -h host][ -p port] --eval [script.lua][ KEY1[ KEY2[ ...]]][ , ARGV1[ ARGV2[ ...]]]

KEYS 和 ARGV 之间需要有,

创建hello.lua文件:

return "Hello, Redis & Lua!"

执行:

redis-cli -h 127.0.0.1 -p 6379 --eval hello.lua 0

输出:

Hello, Redis & Lua!
  • 直接在 redis-cli 里执行 Lua 代码。

连接 Redis 服务:

redis-cli -h 127.0.0.1 -p 6379

执行:

eval 'return "Hello, Redis & Lua!"' 0

输出:

"Hello, Redis & Lua!"
  • 程序中调用。
<?php

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// Lua脚本
$command = <<<EOD
    return "Hello, Redis & Lua!"
EOD;

$res = $redis->eval($command, [], 0);

var_dump($res);

$redis->close();

执行:

php hello.php

输出:

/home/laijinman/data/dev/redis/hello.php:13:
string(19) "Hello, Redis & Lua!"

Lua脚本同时操作写入多个Keys

假设如下:

所有的商品库存保存到 Redis。下单时,要先判断订单所有商品是否有足够库存。如果库存足够,扣减相应库存,如果有其中一件商品缺货,要返回缺货的商品及剩余库存。

实现如下:

local oks = {}
local okn = {}
local kn  = 0

-- check stock
for i, k in ipairs(KEYS) do
    local res = redis.call('GET', k)
    if tonumber(res) < tonumber(ARGV[i]) then
        kn = kn + 1
        oks[kn] = k
        okn[kn] = res
    end
end
if 0 < #oks then
    return {oks, okn}
end

-- everything is ok
for i, k in ipairs(KEYS) do
    redis.call('INCRBY', k, - ARGV[i])
end

return true

备份数据

假如有一个 List,当我们 lpop 或 rpop 时,一个数据单元就从这个 List 删除了,但如果我们程序中途挂掉,那数据有可能会丢失。

可以用 Lua 脚本去 lpop 或 rpop 一个 List 时,顺便把这个数据保存到另外一个队列,这样即使程序中断,也可以确保可以找回数据。

实现如下:

local res = redis.call('RPOP', KEYS[1])
if res then
    redis.call('LPUSH', KEYS[1] .. '_bak', res)
end

return res

执行:

redis-cli --eval rpop-and-backup.lua lt_test

总结

利用 Lua 可以很方便地扩展 Redis 功能,很多时候可以用来对一些性能要求较高的服务降低 TCP 通讯次数。但由于 Redis 是单线程写入,所以 Lua 服务实质上也是单线程执行,这意味着 Lua 脚本必须高效,请求快进快出,快速完成,复杂且耗时的操作不适用于 Lua 来做,否则会阻塞 Redis 并导致并发能力下降。