redis的lua环境实现流程
- 服务器调用C的API创建一个Lua环境
- 将一些基础函数库导入到Lua环境中,并删除能载入外部文件的函数loadfile
- 创建redis函数的全局表格。函数包含了redis.call和redis.pcall、redis计算has值的函数,redis日志函数和返回错误的函数
- 使用redis自己的随机函数替代Lua原有随机函数,这是因为Lua原有随即函数有副作用,不符合redis对Lua函数的要求(无副作用,纯函数),redis自制随机函数,对于一样的seed,总生成相同的随机序列,如果不修改seed,则seed都是0
- 创建排序辅助函数:redis有一些“带有不确定性的命令”,相同的输入,返回的结果顺序可能不同,比如:SINTER、SUNION、SDIFF、SMEMBERS、HKEYS、HVALS、KEYS,它们不保证返回顺序每次一致,所以需要有辅助函数,在每次返回前对其排序
- 创建错误报告函数,提供出错更详细的信息
- 保护Lua的全局环境,确保不会滥用全局变量
- 将Lua环境保存到redisServer的lua属性中
- 创建lua环境协作组件:lua伪客户端和lua_script字典。其中伪客户端将执行脚本中所有redis命令并将结果再返回给脚本;而lua_script字典将记录所有被EVAL执行过和SCRIPT LOAD命令执行过的脚本,键为脚本SHA值,值为脚本内容
EVAL执行原理
- 通过EVAL执行的脚本将保存到服务器lua_script字典中,并且创建一个”f_”开头,后接sha值的函数,脚本内容将会称为函数体,如果脚本通过redis.call或redis.pcall调用了redis命令,命令会发到lua伪客户端执行,然后将结果返回给脚本
- 在脚本执行之前,服务器还需要设定一些钩子和传入参数等准备工作:将eval传入的建明和脚本参数保存到KEYS和ARGV数组中,传入lua环境、设置超时钩子
命令
EVAL "script_content"
:执行传入的脚本内容EVALSHA <sha>
:根据传入的sha值,查找是否有对应的函数,并执行函数,没有找到则返回错误SCRIPT FLUSH
:清空所有脚本,会清空并重建lua_script字典和lua环境SCRIPT EXISTS <sha>
:根据传入的sha值,判断对应的脚本是否存在在服务器中,通过检查lua_script字典SCRIPT LOAD "script_content"
:将传入的脚本存入服务器,会在lua环境创建一个函数,并将脚本内容存入lua_scriptSCRIPT KILL
和SHUTDOWN NOSAVE
:脚本执行超过配置lua-time-limit
的时长,如果没有写入操作,则传入SCRIPT KILL
可以停止脚本执行,如果有写入操作,则需要命令SHATDOWN NOSAVE
停止服务器以避免非法数据的写入
对于脚本的复制
- EVAL命令的复制:从服务器执行EVAL命令即可
- SCRIPT FLUSH:从服务器清空并重建lua环境和lua脚本字典
- SCRIPT LOAD:从服务器也执行相同的命令
- EVALSHA命令:因为主服务器和从服务器可能脚本环境有差别,通过sha查找函数并不一定能成功执行,所以,redisServer保存一个字典
repl_scriptcache_dict
,键是sha,值是null,当一个脚本被同步到了所有从服务器后,脚本的sha将会存入该字典。每当有新的从服务器连接,则该字典清空。
当需要执行EVALSHA的复制的时候,如果脚本sha同时在lua_script和repl_scriptcache_dict两个字典中,则说明可以对从服务器使用相同命令复制,如果repl_scriptcache_dict字典没有对应sha,则EVALSHA命令将会转化成EVAL命令执行(通过lua_script字典拿到脚本内容)