:::tip
Redis在2.6版本开始引入了对Lua脚本的支持,通过在服务器嵌入Lua环境,Redis客户端可以使用Lua脚本直接在服务器端原子地执行多个Redis命令
:::
关于Lua的命令
执行lua脚本
EVAL script numkeys key [key …] arg [arg …]
通过lua脚本的sha1校验和执行lua脚本
EVALSHA sha1 numkeys key [key …] arg [arg …]
加载lua脚本到redis内存中
SCRIPT LOAD script
通过sha1校验和检查脚本是否存在于redis内存中
SCRIPT EXISTS sha1
清空redis内存中缓存的lua脚本
SCRIPT FLUSH
停止lua的执行
SCRIPT KILL
内部工作原理
为了在Redis服务器中执行Lua脚本,Redis服务器内嵌了一个Lua环境
因为Redis使用串行化的方式来执行Redis命令,所以在任何时间里,最多只有一个脚本能被放进Lua环境里执行,因此,只需要创建一个Lua环境即可
伪客户端
因为执行Redis命令必须有相应的客户端,所以Redis服务器专门为Lua环境创建看一个伪客户端,由这个伪客户端负责处理Lua脚本中包含的所有Redis命令
下图是通过lua脚本执行redis命令时的流程
lua_scripts字典
除了伪客户端之外,Redis服务器还创建了一个lua_scripts字典,键为某个lua脚本的SHA1校验和,值为对应的lua脚本内容
Redis服务器会将所有被EVAL命令执行过的Lua脚本,以及所有被SCRIPT LOAD命令载入过的Lua脚本都保存到lua_scripts字典里
举个
客户端执行EVAL ‘return hi’ 命令
Redis服务器首先计算Lua脚本内容的sha1校验和,然后判断是否已存在,然后存进lua_scripts字典中
接着在Lua环境中创建一个函数,函数名为f_加上Lua脚本的sha1校验和,函数内容为Lua脚本的内容
然后调用Lua环境中刚刚创建的函数,即完成了EVAL指令对Lua脚本的执行
后续用户可以通过EVALSHA命令通过sha1校验和执行Lua脚本,实际上也就是通过sha1校验和找到Lua环境中对应的函数然后执行
lua_scripts的作用
1、 后续lua的执行可以通过EVALSHA 传递 sha1校验和的方式执行,避免每次执行时都需要将完整的Lua脚本内容发送给Redis服务器
2、 主从模式下实现脚本复制
脚本复制
通过上面的描述后发现lua_scripts字典存的lua脚本的内容好像没有用到,其实它的用途是用在主从复制时脚本的复制上
跟其他普通的Redis命令一样,当服务器运行在复制模式下时,具有写性质的脚本命令也会被复制到从节点,包括
1、 EVAL
2、 EVALSHA
3、 SCRIPT LOAD
4、 SCRIPT FLUSH
其中EVAL,SCRIPT LOAD和SCRIPT FLUSH可以直接复制给从节点
但是EVALSHA命令的复制却复杂一点
因为EVALSHA的执行需要Redis服务器提前将Lua脚本缓存到内存中,否则会执行失败
如果直接将EVALSHA命令复制给从节点,可能会出现以下两种情况而执行失败
所以为了解决这个问题,主节点需要
1、 判断哪些Lua脚本在所有从节点已经加载过
2、 如果没有加载过,需要通过把Lua脚本加载到从节点中
所以Redis服务器中维护了一个repl_scriptcache_dict的字典,键为某个Lua脚本的sha1校验和,值为null
当一个sha1校验和出现在这个字典时,说明这个校验和对应的Lua脚本已经传播给所有从节点,主节点可以直接向从节点传播EVALSHA命令,而不必担心从节点会出现脚本未找到的错误
当一个sha1校验和没有出现在这个字典时,说明直接复制EVALSHA命令会出错,所以需要将EVALSHA命令转换成EVAL命令,然后再复制给从节点执行。EVALSHA命令转换成EVAL命令就是通过sha1校验和从lua_scripts字典中找到Lua脚本的内容来实现的
当一个新的从节点加入进来的时候,需要将repl_scriptcache_dict全部清空,因为随着新的从节点的加入,字典里面记录的脚本已经不再被所有从服务器加载过了