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文件:

1 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!"
  • 程序中调用。
 1 <?php
 2 
 3 $redis = new Redis();
 4 $redis->connect('127.0.0.1', 6379);
 5 
 6 // Lua脚本
 7 $command = <<<EOD
 8     return "Hello, Redis & Lua!"
 9 EOD;
10 
11 $res = $redis->eval($command, [], 0);
12 
13 var_dump($res);
14 
15 $redis->close();

执行:

php hello.php

输出:

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

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

假设如下:

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

实现如下:

 1 local oks = {}
 2 local okn = {}
 3 local kn  = 0
 4 
 5 -- check stock
 6 for i, k in ipairs(KEYS) do
 7     local res = redis.call('GET', k)
 8     if tonumber(res) < tonumber(ARGV[i]) then
 9         kn = kn + 1
10         oks[kn] = k
11         okn[kn] = res
12     end
13 end
14 if 0 < #oks then
15     return {oks, okn}
16 end
17 
18 -- everything is ok
19 for i, k in ipairs(KEYS) do
20     redis.call('INCRBY', k, - ARGV[i])
21 end
22 
23 return true

备份数据

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

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

实现如下:

1 local res = redis.call('RPOP', KEYS[1])
2 if res then
3     redis.call('LPUSH', KEYS[1] .. '_bak', res)
4 end
5 
6 return res

执行:

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

总结

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