20 Lua脚本
作用:
通过在服务器嵌入Lua环境,客户端可以使用Lua脚本,直接在服务器端原子性地执行多个Redis命令
EVAL:可以直接对输入的脚本进行求值
EVALSHA:可以根据脚本的SHA1校验和来对脚本进行求职;要求校验和对应的脚本至少被EVAL执行过一次或曾被SCRIPT LOAD 命令载入过
20.1 创建并修改Lua环境
过程:8步
接下来逐一介绍这些步骤
20.1.1 创建Lua环境
调用Lua的C API函数lua_open,创建一个新的Lua环境
20.1.2 载入函数库
20.1.3 创建Redis全局表格
创建一个redis表格(table)并将其设为全局变量,其包含的函数有:
通过redis.call和redis.pcall函数,用户可以直接在Lua脚本中执行Redis命令:
20.1.4 使用Redis自制的随机函数来替换Lua原有的随机函数
原因:防止带有副作用的函数令脚本产生不一致的数据
使用自制函数替换了math库的math.random和math.randomsees,两个替换函数有以下特征:
20.1.5 创建排序辅助函数
原因:对于Lua脚本,除了20.1.4,另一个可能产生不一致数据的地方是那些带有不确定性质的命令
例子:set
在相同数据集上可能会产生不同输出的命令称为”带有不确定性的命令“,如SMEMBERS,还有:
解决:
例子:
20.1.6 创建redis.pcall函数的错误报告辅助函数
内容:
20.1.7 保护Lua的全局环境
内容:
即当脚本试图创建一个全局变量或获取一个不存在的全局变量,都会报错;不过可以修改
20.1.8 将Lua环境保存到服务器状态的Lua属性里面
将Lua环境和服务器状态的lua属性关联起来:
只需要创建一个Lua环境的原因:
20.2 创建Lua环境协作组件
创建两个组件:一个是负责执行Lua脚本中的Redis命令的伪客户端,一个是用于保存Lua脚本的lua_scripts字典
20.2.1 伪客户端
作用:
使用redis.call函数或redis.pcall函数执行一个redis命令的流程如下:
20.2.2 lua_scripts字典
键为某个Lua脚本的SHA1校验和,值为SHA1校验和对应的Lua脚本:
例子:
作用:实现SCRPIT EXISTS命令(键,20.5.2)和脚本复制功能(值,20.6.2.3)
20.3、20.4、20.5与命令实现有关,请看《Redis设计与实现》第二十章总结二:Lua脚本之命令实现
20.6 脚本复制
当服务器运行在复制模式之下时,具有写性质的脚本命令也会被复制到从服务器
命令为EVAL、SCRIPT FLUSH、SCRIPT LOAD、EVALSHA
20.6.1 EVAL、SCRIPT FLUSH、SCRIPT LOAD命令的复制方法
与复制其他普通命令相同,直接传播给从服务器:
- EVAL
效果:主从服务器都会执行相同的Lua脚本
- SCRIPT FLUSH
效果:主从服务器都会重置自己的Lua环境,并清空自己的脚本字典
- SCRIPT LOAD
效果:主从服务器都会载入相同的Lua脚本
20.6.2 EVALSHA命令的复制方法
不能直接传播命令的原因:
1.可能主从服务器的lua_scripts字典不相同,主服务器能够执行的,从服务器可能执行不了,出现脚本未找到错误;出现在从服务器开始复制主服务器之前,主服务器已执行过某些脚本的场景
2.多个从服务器之间载入Lua脚本的情况也可能各有不同,一个从服务器成功,不代表其他的从服务器也能成功;出现在不同从服务器开始复制主服务器的时间不同的场景
解决:涉及lua_scripts字典和repl_scriptcache_dict字典
20.6.2.1 判断传播EVALSHA命令是否安全的方法
主服务器使用redisServer的repl_scriptcache_dict字典来记录自己已经将哪些脚本传播给了所有从服务器:
键为Lua脚本的SHA1校验和,值全为null
当EVALSHA命令的校验和在repl_scriptcache_dict字典中时,主服务器可以向从服务器传播此EVALSHA命令;
如果只存在于lua_scripts字典而不存在于repl_scriptcache_dict字典中时,那么至少会有一个从服务器遇上脚本未找到错误
20.6.2.2 清空repl_scriptcache_dict字典
20.6.2.3 EVALSHA命令转换称EVAL命令的方法
通过EVALSHA指定的SHA1校验和以及lua_scripts字典保存的Lua脚本进行转换:
场景:
之后,针对此校验和,就可以直接使用EVALSHA命令了:
20.6.2.4 传播EVALSHA命令的方法
主服务执行EVALSHA之后,将判断此命令给定的校验和是否存在于repl_scriptcache_dict字典中,来决定传播EVAL还是EVALSHA:若存在,则传播EVALSHA;若不存在,则转换成等价的EVAL再传播,然后将此校验和添加到主服务器的repl_scriptcache_dict字典中