一、为什么要使用Lua脚本的好处
1、减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延和请求次数。
2、原子性的操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
3、代码复用:客户端发送的脚步会永久存在redis中,这样,其他客户端可以复用这一脚本来完成相同的逻辑。
4、速度快:见 与其它语言的性能比较, 还有一个 JIT编译器可以显著地提高多数任务的性能; 对于那些仍然对性能不满意的人, 可以把关键部分使用C实现, 然后与其集成, 这样还可以享受其它方面的好处。
5、可以移植:只要是有ANSI C 编译器的平台都可以编译,你可以看到它可以在几乎所有的平台上运行:从 Windows 到Linux,同样Mac平台也没问题, 再到移动平台、游戏主机,甚至浏览器也可以完美使用 (翻译成JavaScript).
6、源码小巧:20000行C代码,可以编译进182K的可执行文件,加载快,运行快。
二、redis本地客户端调用示例
基本介绍
EVAL 和 EVALSHA 命令是从 Redis 2.6.0 版本开始的,使用内置的 Lua 解释器,可以对 Lua 脚本进行求值。
在lua脚本中使用redis.call与redis.pcall调用redis命令。
redis.pcall函数,功能与redis.call相同,唯一的区别是当redis命令执行出错时,redis.pcall会记录错误并继续执行,而redis.call会直接返回错误,不会继续执行。
在脚本中可以使用return语句将值返回给客户端,如果没有执行return语句则默认返回。
redis Eval 命令基本语法如下:
redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...]
参数说明:
- script: 参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数。
- numkeys: 用于指定键名参数的个数。
- key [key ...]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
- arg [arg ...]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。
实例
xxx>eval "return redis.call('set',KEYS[1],ARGV[1])" 1 name ling OK xxx>get name "ling"
在redis中编写lua脚本时,可以通过KEYS与ARGV来传入外部参数,每个KEY都是通过KEYS表指定。ARGV表用来传递参数,这个例子中ARGV用来传入name的值。
关于EVALSHA命令
EVAL
命令要求你在每次执行脚本的时候都发送一次脚本主体(script body)。Redis 有一个内部的缓存机制,因此它不会每次都重新编译脚本,不过在很多场合,付出无谓的带宽来传送脚本主体并不是最佳选择。
为了减少带宽的消耗, Redis 实现了 EVALSHA 命令,它的作用和 EVAL
一样,都用于对脚本求值,但它接受的第一个参数不是脚本,而是脚本的 SHA1 校验和(sum)。
它会根据给定的 sha1 校验码,执行缓存在服务器中的脚本。
三、什么时候使用Lua
三个主要优点:原子性,减少网络io,速度快。
关于WATCH/MULTI/EXEC与lua的区别与选择
Redis支持WATCH/MULTI/EXEC这样的块,能进行一组操作,也能一起提交执行,看起来与Lua有重叠。应该如何进行选择?
MULT块中所有操作独立,但在Lua中,后面的操作能依赖前面操作的执行结果。同时使用Lua脚本还能够避免WATCH使用后竞争条件引起客户端反应变慢的情况。
在RedisGreen(译注:国外一家专门提供Redis主机的服务商),我们看到许多应用使用Lua的同时也使用MULTI/EXEC,但两者但不是替代关系。许多成功的Lua脚本都很小,仅仅实现一个你的应用需要而Redis命令中没有单一的功能。
四、访问库
Redis的Lua解释器加载七个库:base,table,string, math, debug,cjson和cmsgpack。前几个都是标准库,充许你使用任何语言进行基本的操作。后面两个可以让Redis支持JSON和MessagePack—这是非常有用的功能,同时我也很想知道为什么常常看不到这种用法。
Web应用程序常常使用JSON作为api返回数据,你也许也可以把一堆JSON数据存到Redis的key中。当想访问某些JSON数据时,首先需要保存到一个hash中,使用Redis的JSON支持将非常方便:
if redis.call("EXISTS", KEYS[1]) == 1 then local payload = redis.call("GET", KEYS[1]) return cjson.decode(payload)[ARGV[1]] else return nil end
在这里我们检查看key是否存在,如不存在则快速返回nil。如存在则从Redis中获取JSON值,用cjson.decode()进行解析,然后返回请求内容。
redis-cli set apple '{ "color": "red", "type": "fruit" }' => OK redis-cli eval "$(cat json-get.lua)" 1 apple type => "fruit"
加载这段脚本进你的Redis服务器,将JSON数据保存到Redis中,通常是hash。 虽然我们每次访问时都必须解析,但只要你的对象很小,这个操作实际上是非常快的。
如果你的API只是在内部提供,通常需要考虑效率上的问题,MessagePack 是比采用JSON更好的选择,它更小,更快,在Redis(更多场合也是如此),MessagePack是JSON更好的替代品。
if redis.call("EXISTS", KEYS[1]) == 1 then local payload = redis.call("GET", KEYS[1]) return cmsgpack.unpack(payload)[ARGV[1]] else return nil end
五、Redis与lua数据类型转换
当 Lua 通过 call() 或 pcall() 函数执行 Redis 命令的时候,命令的返回值会被转换成 Lua 数据结构。
同样地,当 Lua 脚本在 Redis 内置的解释器里运行时,Lua 脚本的返回值也会被转换成 Redis 协议(protocol),然后由 EVAL 将值返回给客户端。
数据类型之间的转换遵循这样一个设计原则:如果将一个 Redis 值转换成 Lua 值,之后再将转换所得的 Lua 值转换回 Redis 值,那么这个转换所得的 Redis 值应该和最初时的 Redis 值一样。
换句话说, Lua 类型和 Redis 类型之间存在着一一对应的转换关系。
redis返回值类型和Lua数据类型转换规则
redis返回值类型 Lua数据类型
整数回复 数字类型
字符串回复 字符串类型
多行字符串回复 table类型(数组形式)
状态回复 table类型(只有一个ok字段存储状态信息)
错误回复 table类型(只有一个err字段存储错误信息)
Lua数据类型和redis返回值类型转换规则
Lua数据类型 redis返回值类型
数字类型 整数回复(Lua的数字类型会被自动转换成整数)
字符串类型 字符串回复
table类型(数组形式) 多行字符串回复
table类型(只有一个ok字段存储状态信息) 状态回复
table类型(只有一个err字段存储错误信息) 错误回复
Python 示例
pool = redis.ConnectionPool(host='xxx',port=6379, decode_responses=True) conn = redis.Redis(connection_pool=pool) def luatest(): lua1 = """ """ script2 = conn.register_script(lua1) script2(keys=[],args=[])
编程中一些注意的地方:
1.lua脚本中调用redis命令使用call与pcall命令。
2.Wrong number of args calling Redis command From Lua script此报错为使用redis调用命令的使用参数不完整,比如有些操作hash的命令,需要两个key,掉了一个key就会出现此错误。
3.lua脚本中支持多返回值,lua中使用table存储,返回时直接返回此table,python接收为一个list的。
实际示例:使用redis存储聊天数据(lua)
总结:
下面这些都是在Redis中使用Lua时常见的错误:
- 表是Lua中的表达式,与很多流行语言不同。KEYS中的第一个元素是KEYS[1],第二个是KEYS[2](译注:不是0开始)
- nil是表的结束符,[1,2,nil,3]将自动变为[1,2],因此在表中不要使用nil。
- redis.call会触发Lua中的异常,redis.pcall将自动捕获所有能检测到的错误并以表的形式返回错误内容。
- Lua数字都将被转换为整数,发给Redis的小数点会丢失,返回前把它们转换成字符串类型。
- 确保在Lua中使用的所有KEY都在KEY表中,否则在将来的Redis版中你的脚本都有不能被很好支持的危险。
- Lua脚本和其它Redis操作一样,在脚本执行时,其它的一切都不能运行。考虑用脚本来护展Redis服务器能力,但要保持短小和有用。