文章目录
一、Redis-Lua脚本简介
Redis服务器内置了Lua解释器用于执行指定的Lua脚本,Lua脚本可以直接调用Redis命令,并使用Lua语言及其内置的函数库处理命令结果。Lua脚本的出现为用户提供了一种标准的的方法来扩展Redis服务器的功能。
Redis服务器以原子方式执行Lua脚本,在执行完整个Lua脚本及其包含的Redis命令之前,Redis服务器不会执行其他客户端发送的命令或脚本。除此之外,在Lua脚本中一次可执行多个Redis命令,对于需要在客户端和服务器之间往返通信多次的程序来说,使用Lua脚本可以有效地提升程序的执行效率。
二、在Redis中使用Lua脚本
1、执行Lua脚本
- 通过EVAL命令可以执行指定的Lua脚本;
语法格式:EVAL script key_numbers key1 key2 … arg1 arg2 …
其中,script即脚本,key_numbers参数用于指定脚本需要处理的键数量,而后面的任意多个key即是需要处理的键,这些键可以在脚本中通过KEYS参数进行访问(在Lua中,KEYS数组的索引是1开始,因此访问KEYS[1]取得的是第一个key),再其后的任意多个arg参数用于指定传递给脚本的附件参数,这些参数可以在脚本中通过ARGV数组进行访问,与KEYS参数一样,ARGV数组的索引也是1开始的。
如
# 将return 'hello world'传递给了Lua环境执行,
# 其中return用于将给定值返回给脚本调用者,此处返回的就是hello world,
# 而其后的key参数为0,说明这个脚本不需要对Redis的数据库键进行处理,
# 也没有给定任何arg参数,说明这个脚本也不需要任何附加参数。
EVAL "return 'hello world'" 0
- 通过redis-cli执行脚本文件
除了使用EVAL命令执行给定的脚本之外,还可以通过redis-cli客户端的eval选项,以命令行方式执行给定的脚本文件,此种方式不需要像执行EVAL命令那样传入键的数量,只需要在传入键和附加参数之间使用逗号进行分割即可。如
// test.lua
redis.call("SET", KEYS[1], ARGV[1])
return redis.call("GET", KEYS[1])
# 执行 test.lua
redis-cli --eval test.lua 'msg','hello'
2、使用脚本执行Redis命令
在Lua脚本中,可通过**redis.call()函数或者redis.pcall()**函数来调用Redis命令,这两个函数接收的第一个参数都是被执行的Redis命令的名字,而后面跟着的是任意多个命令参数。在Lua脚本中执行Redis命令所使用的格式与在redis-cli客户端中执行Redis命令所使用的格式是完全一样的。如
# 使用Lua脚本设置一个字符串键及其值
EVAL "return redis.call('SET',KEYS[1],ARGV[1])" 1 "message" "hello world"
redis.call()函数和redis.pcall()函数都可以用于执行Redis命令,唯一的区别就是处理错误的方式,redis.call()函数在执行命令出错时会引发一个Lua错误,迫使EVAL命令向调用者返回一个错误,而redis.pcall()函数会将错误包裹起来,并返回一个表示错误的Lua表格。
3、值转换
Redis服务器中存在着Redis命令执行环境以及Lua脚本执行环境两种执行环境,因此在这两种环境之间传递值会有相应的转换操作。当Lua脚本通过redis.call()函数或者redis.pcall()函数执行Redis命令时,传入的Lua值将被转换为Redis命令协议值,而当Redis命令执行完后,Redis命令执行结果又将转换为Lua值,Lua脚本执行完后,Lua值将被转换为Redis协议值。
4、缓存脚本
Redis提供了缓存Lua脚本的功能,允许将给定的Lua脚本缓存在服务器中,然后根据Lua脚本的SHA1校验和直接调用脚本,以避免每次执行同样的脚本都需要重新发送一次,浪费网络带宽。
通过命令SCRIPT LOAD 可以将指定的脚本缓存在服务器中,并返回脚本对应的SHA1校验和作为结果;在此之后,就可以通过EVALSHA命令来执行已被缓存的脚本。EVALSHA命令除了第一个参数接收的是SHA1校验和而不是脚本本身之外,其他参数与EVAL命令的参数相同。
语法格式:
SCRIPT LOAD script
EVALSHA sha1 numkeys key1 key2 … arg1 arg2 …
# 缓存脚本,返回结果为5332031c6b470dc5a0dd9b4bf2030dea6d65de91
SCRIPT LOAD "redis.call('SET',KEYS[1],ARGV[1]);return redis.call('GET',KEYS[1])"
# 执行脚本
EVALSHA "5332031c6b470dc5a0dd9b4bf2030dea6d65de91" 1 "name" "jack"
检查脚本是否已被缓存
可以通过SCRIPT EXSITS命令检查脚本是否已被缓存,该命令接收一个或多个SHA1校验和作为参数;
语法格式:SCRIPT EXSITS sha1,如
SCRIPT EXSITS "5332031c6b470dc5a0dd9b4bf2030dea6d65de91"
移除所有已缓存的脚本
执行SCRIPT FLUSH命令将移除服务器已缓存的所有脚本,在成功移除所有已缓存脚本之后会返回OK作为回复。
5、强制停止正在运行的脚本
配置项lua-time-limit可定义Lua脚本可以不受限制运行的时长,默认值为5000,单位毫秒。当脚本的运行时间低于lua-time-limit指定的时长时,其他客户端发送的命令请求将被阻塞,超过该时长时,向服务器发送请求的客户端将得到一个错误回复,提示用户可以通过SCRIPT KILL或者SHUTDOWN NOSAVE命令来终止或者直接关闭服务器。也就是说当脚本运行超过lua-time-limit指定时长时,服务器并不会结束该脚本,而是由客户端指定是否结束。
SCRIPT KILL命令如果用于尚未执行过任何写命令的Lua脚本,那么服务器将终止该脚本,然后回到正常状态,继续处理客户端的命令请求;如果正在运行的Lua脚本已经执行过写命令,并且该脚本尚未执行完毕,为了防止这些可能是不完整或者错误的脏数据保存到数据库,服务器会让其执行完毕,此时如果仍要终止脚本运行,需使用SHUTDOWN NOSAVE命令,在不执行持久化操作的情况下关闭服务器,然后通过手动重启服务器来让它回到正常状态。
三、内置函数库
Redis在Lua环境中内置了一些函数库,通过这些函数库可以对Redis服务器进行操作,或者对给定的数据进行处理。
1、Redis包
Redis包中的常用函数有
redis.call()
redis.pcall()
redis.log()
redis.sha1hex()
redis.error_reply()
redis.status_reply()
redis.breakpoint
redis.debug()
redis.call()和redis.pcall()在前面“使用脚本执行Redis命令”部分中已介绍了。
-
redis.log()函数
redis.log()函数用于在脚本中向Redis服务器写入日志,它接收一个日志等级和一条消息作为参数;
语法格式:redis.log(level, message)
其中level的值可以是4个日志等级中的一个:reids.LOG_DEBUG、redis.LOG_VERBOSE、redis.LOG_NOTICE、redis.LOG_WARNING,只有当给定的日志等级大于等于服务器当前设置的日志等级时,Redis才会把给定的消息写入日志中。 -
redis.sha1hex()函数
redis.sha1hex()函数用于计算出给定字符串的SHA1校验和;
语法格式:redis.sha1hex(string) -
redis.error_reply()函数和redis.status_reply()函数
redis.error_reply()函数和redis.status_reply()函数分别用于返回redis的错误回复以及状态回复;
语法格式:
redis.error_reply(error_message)
redis.status_reply(status_message)
其中redis.error_reply()函数会返回一个只包含err字段的Lua表格(如,{err=‘something wrong’}),而err字段的值则是给定的错误消息;redis.status_reply()函数返回的则是一个只包含OK字段的Lua表格,而OK字段的值即给定的状态消息。
- redis.breakponit()和redis.debug()函数
Redis允许在脚本中通过调用redis.breakponit()函数来添加动态端点,当调试器执行至redis.breakponit()调用所在的行时,调试器就会暂停执行过程并等待用户的指示。
redis.debug()函数能够直接把给定的值输出到调试客户端,可以用来方便的得知变量或表达式的值。
2、cjson包
cjson包能够为Lua脚本提供快速的JSON编码和解码操作,这个包中最常用的就是将Lua值编码为JSON数据的编码函数encode(),以及将JSON数据解码为Lua值的解码函数decode()。