redis Lua脚本(一)

Redis从2.6版本开始引入对Lua脚本的支持,通过在服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务端原子的执行多个Redis命令。

其中,使用EVAL命令可以直接对输入的脚本进行求值:

redis>EVAL "return 'hello world'" 0
"hello world"

而使用EVALSHA命令则可以根据脚本的SHA1校验和来对脚本进行求值,但这个命令要求校验和对应的脚本必须至少被EVAL命令执行过一次:

后面将对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环境协作组件

除了创建并修改Lua环境之外,Redis服务器还创建了两个用于与Lua环境进行协作的组件,它们分别是负责执行Lua脚本中的Redis命令的伪客户端,以及用于保存Lua脚本的lua_scripts字典。

伪客户端

因为执行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函数会将命令结果作为函数返回值给脚本中的调用者。

下图展示了Lua脚本在调用redis.call函数时,Lua环境、伪客户端、命令执行器三者之间的通信过程(调用redis.pcall)函数时产生的通信过程也是一样的。


lua_scripts字典

除了伪客户端之外,Redis服务器伪Lua环境创建了另一个协作组件是lua_scripts字典,这个字典的键为某个Lua脚本的SHA1校验和(checksum),而字典的值则是SHA1校验和对应的Lua脚本:

EVAL命令的实现

EVAL命令的执行过程可以分为以下三个步骤:

  1. 根据客户端给定的Lua脚本,在Lua环境中定义一个Lua函数。
  2. 将客户端给定的脚本保存到lua_scripts字典,等待将来进一步说明。
  3. 执行刚刚才Lua环境中定义的函数,以此来执行客户端给定的Lua脚本。

后面将以:

redis>EVAL "return 'hello world'" 0

"hello world"

命令为示例,分别介绍EVAL命令的三个执行步骤。

定义脚本函数

当客户端想服务器发送EVAL命令,要求执行某个Lua脚本的时候,服务器首先要做的就是在Lua环境中,为传入的脚本定义一个与这个脚本相对应的Lua函数,其中Lua函数的名字由f_前缀加上脚本的SHA1校验和(四十个字符长)组成,而函数的体(body)则是脚本本身。

将脚本保存到Lua_scripts字典

EVAL命令要做的第二件事就是将客户端传入的脚本保存到服务器的lua_scripts字典里面。举个例子,对于命令:

EVAL “return ‘hello world’” 0

来说,服务器将在lua_scripts字典中添加一个键值对,其中键为Lua脚本的SHA1校验和:

执行脚本函数

在为脚本定义函数,并且将脚本保存到lua_scripts字典之后,服务器还需要进行一些设置钩子,传入参数之类的准备动作,才能正式开始执行脚本。

整个准备和执行脚本的过程如下:

  1. 将EVAL命令中传入的键名(key name)参数和脚本参数分别保存到KEYS数组和ARGV数组,然后将这两个数组作为全局变量传入到Lua环境里面。
  2. 为Lua环境装载超时处理钩子(hook),这个钩子可以在脚本出现超时运行情况时,让客户端通过SCRIPT KILL命令停止脚本,或者通过SHUTDOWN命令直接关闭服务器。
  3. 执行脚本函数。
  4. 移除之前装载的超时钩子。
  5. 将执行脚本函数所得的结果保存到客户端状态的输出缓冲区里面,等待服务器将结果返回给客户端。
  6. 对Lua环境进行垃圾回收操作。

举个例子,对于如下命令:

EVAL "return 'hello world'" 0

服务器将执行如下动作:

  1. 因为这个脚本没有给定任何键名参数或者脚本参数,所以服务器会跳过传值到KEYS数组或ARGV数组这一步。
  2. 为Lua环境装载超时钩子。

执行算是告一段落,之后服务器只要将保存在输出缓冲区里面的执行结果返回给执行EVAL命令的客户端就可以了。

EVALSHA命令的实现

参考文献:《redis设计与实现》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值