Redis从2.6版本开始引入对Lua脚本的支持,通过在服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务端原子的执行多个Redis命令。
lua脚本的好处:
减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延
原子操作。redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
复用。客户端发送的脚步会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。
1.在redis里使用EVAL和EVALSHA
可以使用EVAL命令对输入的脚本进行接受结果
如: Eval "return 1+1" 0
——>(integer)2
而使用evalsha 命令则可以根据脚本的SHA1校验和对接受脚本的结果,但这个命令要求校验和对应的脚本必须至少执行过一次或者这个校验和对应的脚本被scriptload加载过。
如:
script load "return 1+2" // 会返回一个sha校验
->"e13c398af9f2658ef7050acf3b266f87cXXXXXX"
->evalsha "e13c398af9f2658ef7050acf3b266f87cXXXXXX" 0
->(integer) 3
在脚本比较长的情况下,如果每次调用脚本都需要将整个脚本传给Redis会占用较多的带宽。为了解决这个问题,Redis提供了EVALSHA命令,允许开发者通过脚本内容的SHA1摘要来执行脚本,该命令的用法和EVAL一样,只不过是将脚本内容替换成脚本内容的SHA1摘要。
2.伪客户端
因为执行Redis命令必须有相应的客户端状态,所以为了执行Lua脚本中包含的Redis命令,Redis服务器专门为Lua环境创建了一个伪客户端,并由这个伪客户端负责处理Lua脚本中包含的所有Redis命令。
Lua脚本使用redis.call函数或者redis.pcall函数执行一个Redis命令,需要完成以下步骤:
(1).Lua环境将redis.call函数或者redis.pcall函数想要执行的命令传给伪客户端。
(2).伪客户端将脚本想要执行的命令传给命令执行器。
(3).命令执行器执行伪客户端传给它的命令,并将命令的执行结果返回给伪客户端。
(4).伪客户端接收命令执行器返回的命令结果,并将这个命令结果返回给Lua环境。
(5).Lua环境在接收到命令结果以后,将该结果返回给redis.call函数或者redis.pcall函数。
(6).接收到结果的redis.call函数或者redis.pcall函数会将命令结果作为函数返回值给脚本中的调用者。
3.lua_scripts字典
除了伪客户端之外,Redis服务器伪Lua环境创建了另一个协作组件是lua_scripts字典,这个字典的键为某个Lua脚本的SHA1校验和(checksum),而字典的值则是SHA1校验和对应的Lua脚本:
如:
script load "retrun 1+1"
-> "e13c398af9f2658ef7050acf3b266f87cXXXXXX"
这个返回值和载入的脚本是一一对应关系,这个校验值会存在script字典里。服务器会将所有被eval命令执行过的lua脚本,以及被scriptload载入过的脚本保存在字典里。
lua_script字典的两个作用
(1).一个是实现script exists命令
(2).实现脚本复制功能。
4.EVAL命令的实现
EVAL命令的执行过程可以分为以下三个步骤:
(1)根据客户端给定的Lua脚本,在Lua环境中定义一个Lua函数。
(2)将客户端给定的脚本保存到lua_scripts字典,等待将来进一步说明。
(3)执行刚刚才Lua环境中定义的函数,以此来执行客户端给定的Lua脚本。
5.Redis管理Lua脚本
(1).script load
此命令用于将Lua脚本加载到Redis内存中
(2).script exists
scripts exists sha1 [sha1 …]
此命令用于判断sha1是否已经加载到Redis内存中
(3).script flush
此命令用于清除Redis内存已经加载的所有Lua脚本,在执行script flush后,sha1不复存在。
(4).script kill
此命令用于杀掉正在执行的Lua脚本。
6.redis.call和pcall
6.redsi编写lua脚本
语法:
$ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...
--eval,告诉redis-cli读取并运行后面的lua脚本
path/to/redis.lua,是lua脚本的位置
KEYS[1] KEYS[2],是要操作的键,可以指定多个,在lua脚本中通过KEYS[1], KEYS[2]获取
ARGV[1] ARGV[2],参数,在lua脚本中通过ARGV[1], ARGV[2]获取。
注意:
KEYS和ARGV中间的 ',' 两边的空格,不能省略。
如:
redis.call('set', 'foo', 'bar')
local value=redis.call('get', 'foo') --value的值为bar
redis.pcall函数,功能与redis.call相同,唯一的区别是当命令执行出错时,redis.pcall会记录错误并继续执行,而redis.call会直接返回错误,不会继续执行。