Redis从2.6版本开始引入对Lua脚本的支持,通过在服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务端原子的执行多个Redis命令。
其中,使用EVAL命令可以直接对输入的脚本进行求值:
redis>EVAL "return 'hello world'" 0
"hello world"
后面将对Redis服务器中与Lua脚本相关的各个部分进行介绍。
首先,将介绍Redis服务器初始化Lua环境的整个过程,说明Redis对Lua环境进行了哪些修改,而这些修改又对用户执行Lua脚本产生了什么影响和限制。
接着,将介绍与Lua环境进行协作的两个组件,它们分别是负责执行Lua脚本中包含的Redis命令的伪客户端,以及负责保存传入服务器的Lua脚本的脚本字典。了解伪客户端可以知道脚本中的Redis命令在执行时,服务器与Lua环境的交互过程,而了解脚本字典则有助于理解SCRIPT EXISTS命令和脚本复制功能的实现原理。
在这之后,将介绍EVAL命令和EVALSHA命令的实现原理,说明Lua脚本在Redis服务器中是如何被执行的,并对管理脚本的四个命令——SCRIPT FLUSH命令、SCRIPT EXISTS命令、SCRIPT LOAD命令、SCRIPT KILL命令的实现原理进行介绍。
最后,将以介绍Reids在主从服务器之间复制Lua脚本的方法作为结束。
除了创建并修改Lua环境之外,Redis服务器还创建了两个用于与Lua环境进行协作的组件,它们分别是负责执行Lua脚本中的Redis命令的伪客户端,以及用于保存Lua脚本的lua_scripts字典。
因为执行Redis命令必须有相应的客户端状态,所以为了执行Lua脚本中包含的Redis命令,Redis服务器专门为Lua环境创建了一个伪客户端,并由这个伪客户端负责处理Lua脚本中包含的所有Redis命令。
Lua脚本使用redis.call函数或者redis.pcall函数执行一个Redis命令,需要完成以下步骤:
下图展示了Lua脚本在调用redis.call函数时,Lua环境、伪客户端、命令执行器三者之间的通信过程(调用redis.pcall)函数时产生的通信过程也是一样的。
除了伪客户端之外,Redis服务器伪Lua环境创建了另一个协作组件是lua_scripts字典,这个字典的键为某个Lua脚本的SHA1校验和(checksum),而字典的值则是SHA1校验和对应的Lua脚本:
EVAL命令的执行过程可以分为以下三个步骤:
后面将以:
redis>EVAL "return 'hello world'" 0
"hello world"
命令为示例,分别介绍EVAL命令的三个执行步骤。
当客户端想服务器发送EVAL命令,要求执行某个Lua脚本的时候,服务器首先要做的就是在Lua环境中,为传入的脚本定义一个与这个脚本相对应的Lua函数,其中Lua函数的名字由f_前缀加上脚本的SHA1校验和(四十个字符长)组成,而函数的体(body)则是脚本本身。
EVAL命令要做的第二件事就是将客户端传入的脚本保存到服务器的lua_scripts字典里面。举个例子,对于命令:
EVAL “return ‘hello world’” 0
来说,服务器将在lua_scripts字典中添加一个键值对,其中键为Lua脚本的SHA1校验和:
在为脚本定义函数,并且将脚本保存到lua_scripts字典之后,服务器还需要进行一些设置钩子,传入参数之类的准备动作,才能正式开始执行脚本。
整个准备和执行脚本的过程如下:
举个例子,对于如下命令:
EVAL "return 'hello world'" 0
服务器将执行如下动作:
执行算是告一段落,之后服务器只要将保存在输出缓冲区里面的执行结果返回给执行EVAL命令的客户端就可以了。
参考文献:《redis设计与实现》