lua学习02:redis事务与lua脚本

1.创建并修改Lua环境

步骤:

1)创建一个基础的Lua环境,之后的所有修改都是针对这个环境进行的。
2)载入多个函数库到Lua环境里面,让Lua脚本可以使用这些函数库来进行数据操作。
3)创建全局表格redis,这个表格包含了对Redis进行操作的函数,比如用于在Lua脚本中执行Redis命令的redis.call函数。
4)使用Redis自制的随机函数来替换Lua原有的带有副作用的随机函数,从而避免在脚本中引入副作用。
5)创建排序辅助函数,Lua环境使用这个辅佐函数来对一部分Redis命令的结果进行排序,从而消除这些命令的不确定性。
6)创建redis.pcall函数的错误报告辅助函数,这个函数可以提供更详细的出错信息。
7)对Lua环境中的全局环境进行保护,防止用户在执行Lua脚本的过程中,将额外的全局变量添加到Lua环境中。
8)将完成修改的Lua环境保存到服务器状态的lua属性中,等待执行服务器传来的Lua脚本。接下来的各个小节将分别介绍这些步骤。

2.Lua环境协作组件

伪客户端

  • 作用:

执行Redis命令必须有相应的客户端状态,所以为了执行Lua脚本中包含的Redis命令,Redis服务器专门为Lua环境创建了一个伪客户端,并由这个伪客户端负责处理Lua脚本中包含的所有Redis命令(负责执行redis命令)

  • 步骤:

1)Lua环境将redis.call函数或者redis.pcall函数想要执行的命令传给伪客户端。
2)伪客户端将脚本想要执行的命令传给命令执行器。
3)命令执行器执行伪客户端传给它的命令,并将命令的执行结果返回给伪客户端。
4)伪客户端接收命令执行器返回的命令结果,并将这个命令结果返回给Lua环境。
5)Lua环境在接收到命令结果之后,将该结果返回给redis.call函数或者redis.pcall函数。
6)接收到结果的redis.call函数或者redis.pcall函数会将命令结果作为函数返回值返回给脚本中的调用者。

  • lua环境、伪客户端和命令执行器三者之间的通信过程
    在这里插入图片描述

lua_scripts字典

  • 说明:

1)这个字典的键为某个Lua脚本的SHA1校验和(checksum),而字典的值则是SHA1校验和对应的Lua脚本
2)Redis服务器会将所有被EVAL命令执行过的Lua脚本,以及所有被SCRIPT LOAD命令载入过的Lua脚本都保存到lua_scripts字典里面

struct redisServer{
	//...
	dict* lua_scripts;
	//...
}
  • 流程

1)如果客户端向服务器发送以下命令:
在这里插入图片描述
2)那么服务器的lua_scripts字典将包含被SCRIPT LOAD命令载入的三个Lua脚本,如图20-4所示
在这里插入图片描述

  • 作用:

lua_scripts字典有两个作用,
①一个是实现SCRIPT EXISTS命令
②另一个是实现脚本复制功能

3.EVAL命令的实现

  • 步骤:

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

2)将客户端给定的脚本保存到lua_scripts字典,等待将来进一步使用。
描述:
EVAL命令要做的第二件事是将客户端传入的脚本保存到服务器的lua_scripts字典里面,以lua脚本的SHA1校验和为key,value为lua脚本本身

3)执行刚刚在Lua环境中定义的函数,以此来执行客户端给定的Lua脚本。
①将EVAL命令中传入的键名(key name)参数和脚本参数分别保存到KEYS数组和ARGV数组,然后将这两个数组作为全局变量传入到Lua环境里面。
②为Lua环境装载超时处理钩子(hook),这个钩子可以在脚本出现超时运行情况时,让客户端通过SCRIPT KILL命令停止脚本,或者通过SHUTDOWN命令直接关闭服务器。
③执行脚本函数。
④移除之前装载的超时钩子。⑤将执行脚本函数所得的结果保存到客户端状态的输出缓冲区里面,等待服务器将结果返回给客户端。
⑥对Lua环境执行垃圾回收操作。
⑦执行算是告一段落,之后服务器只要将保存在输出缓冲区里面的执行结果返回给执行EVAL命令的客户端就可以了

4.EVALSHA命令的实现

  • 原理:

只要脚本对应的函数曾经在Lua环境里面定义过,那么即使不知道脚本的内容本身,客户端也可以根据脚本的SHA1校验和来调用脚本对应的函数,从而达到执行脚本的目的,这就是EVALSHA命令的实现原理

  • 举例说明

在这里插入图片描述

5.脚本管理命令的实现

–介绍:
除了EVAL命令和EVALSHA命令之外,Redis中与Lua脚本有关的命令还有四个,它们分别是SCRIPT FLUSH命令、SCRIPTEXISTS命令、SCRIPT LOAD命令、以及SCRIPT KILL命令。

SCRIPT FLUSH

  • 作用:

SCRIPT FLUSH命令用于清除服务器中所有和Lua脚本有关的信息,这个命令会释放并重建lua_scripts字典,关闭现有的Lua环境并重新创建一个新的Lua环境

SCRIPT EXISTS

  • 作用:

SCRIPT EXISTS命令根据输入的SHA1校验和,检查校验和对应的脚本是否存在于服务器中

  • 返回值

返回1表示在redis服务器中,0则表示不在

  • 注意

1)SCRIPT EXISTS命令允许一次传入多个SHA1校验和,不过因为SHA1校验和太长,所以示例里分开多次来进行测试
2)lua_scripts字典既保存脚本的SHA1校验和,又保存脚本本身的原因是为了实现脚本复制功能,详细的情况请看本章稍后对脚本复制功能实现原理的介绍

SCRIPT LOAD

  • 作用:

SCRIPT LOAD命令所做的事情和EVAL命令执行脚本时所做的前两步完全一样:命令首先在Lua环境中为脚本创建相对应的函数,然后再将脚本保存到lua_scripts字典里面,可以根据返回的SHA1校验和再用EVALSHA命令执行被载入的脚本了

  • 返回值

返回的是SHA1校验和

SCRIPT KILL

  • 作用:

1)如果服务器设置了lua-time-limit配置选项,那么在每次执行Lua脚本之前,服务器都会在Lua环境里面设置一个超时处理钩子(hook)。
2)超时处理钩子在脚本运行期间,会定期检查脚本已经运行了多长时间,一旦钩子发现脚本的运行时间已经超过了lua-time-limit选项设置的时长,钩子将定期在脚本运行的间隙中,查看是否有SCRIPT KILL命令或者SHUTDOWN命令到达服务器
3)如果超时运行的脚本未执行过任何写入操作,那么客户端可以通过SCRIPT KILL命令来指示服务器停止执行这个脚本,并向执行该脚本的客户端发送一个错误回复。处理完SCRIPT KILL命令之后,服务器可以继续运行。
4)另一方面,如果脚本已经执行过写入操作,那么客户端只能用SHUTDOWN nosave命令来停止服务器,从而防止不合法的数据被写入数据库中。

6.脚本复制

介绍:
与其他普通Redis命令一样,当服务器运行在复制模式之下时,具有写性质的脚本命令也会被复制到从服务器,这些命令包括EVAL命令、EVALSHA命令、SCRIPT FLUSH命令,以及SCRIPT LOAD命令

复制EVAL命令、SCRIPT FLUSH命令和SCRIPT LOAD命令

1.EVAL

1)那么主服务器在执行这个EVAL命令之后,将向所有从服务器传播这条EVAL命令,从服务器会接收并执行这条EVAL命令,最终结果是,主从服务器双方都会将数据库"msg"键的值设置为"hello world",并且将脚本:
2)“return redis.call(‘SET’,KEYS[1] , ARGV[1])”
3)保存在脚本字典里面。

2.SCRIPT FLUSH

1)如果客户端向主服务器发送SCRIPT FLUSH命令,那么主服务器也会向所有从服务器传播SCRIPT FLUSH命令。
2)最终的结果是,主从服务器双方都会重置自己的Lua环境,并清空自己的脚本字典

3.SCRIPT LOAD

如果客户端使用SCRIPT LOAD命令,向主服务器载入一个Lua脚本,那么主服务器将向所有从服务器传播相同的SCRIPTLOAD命令,使得所有从服务器也会载入相同的Lua脚本

复制EVALSHA命令

背景:
EVALSHA命令是所有与Lua脚本有关的命令中,复制操作最复杂的一个,因为主服务器与从服务器载入Lua脚本的情况可能有所不同,所以主服务器不能像复制EVAL命令、SCRIPT LOAD命令或者SCRIPT FLUSH命令那样,直接将EVALSHA命令传播给从服务器。对于一个在主服务器被成功执行的EVALSHA命令来说,相同的EVALSHA命令在从服务器执行时却可能会出现脚本未找到(not found)错误

1.判断传播EVALSHA命令是否安全的方法
  • 原理:

repl_scriptcache_dict字典的键是一个个Lua脚本的SHA1校验和,而字典的值则全部都是NULL,当一个校验和出现在repl_scriptcache_dict字典时,说明这个校验和对应的Lua脚本已经传播给了所有从服务器,主服务器可以直接向从服务器传播包含这个SHA1校验和的EVALSHA命令,而不必担心从服务器会出现脚本未找到错误。
在这里插入图片描述

2.清空repl_scriptcache_dict字典
  • 清除数据点

每当主服务器添加一个新的从服务器时,主服务器都会清空自己的repl_scriptcache_dict字典,这是因为随着新从服务器的出现,repl_scriptcache_dict字典里面记录的脚本已经不再被所有从服务器载入过,所以主服务器会清空repl_scriptcache_dict字典,强制自己重新向所有从服务器传播脚本,从而确保新的从服务器不会出现脚本未找到错误

3.EVALSHA命令转换成EVAL命令的方法
  • 流程

具体的转换方法如下:
1)根据SHA1校验和sha1,在lua_scripts字典中查找sha1对应的Lua脚本script。
2)将原来的EVALSHA命令请求改写成EVAL命令请求,并且将校验和sha1改成脚本script,至于numkeys、key、arg等参数则保持不变。

4.传播EVALSHA命令的方法
  • 流程

1)如果EVALSHA命令指定的SHA1校验和存在于repl_scriptcache_dict字典,那么主服务器直接向从服务器传播EVALSHA命令。
2)如果EVALSHA命令指定的SHA1校验和不存在于repl_scriptcache_dict字典,那么主服务器会将EVALSHA命令转换成等价的EVAL命令,然后传播这个等价的EVAL命令,并将EVALSHA命令指定的SHA1校验和添加到repl_scriptcache_dict字典里面(重点)

在这里插入图片描述

7.重点回顾

❑Redis服务器在启动时,会对内嵌的Lua环境执行一系列修改操作,从而确保内嵌的Lua环境可以满足Redis在功能性、安全性等方面的需要。
❑Redis服务器专门使用一个伪客户端来执行Lua脚本中包含的Redis命令。❑Redis使用脚本字典来保存所有被EVAL命令执行过,或者被SCRIPT LOAD命令载入过的Lua脚本,这些脚本可以用于实现SCRIPT EXISTS命令,以及实现脚本复制功能。
❑EVAL命令为客户端输入的脚本在Lua环境中定义一个函数,并通过调用这个函数来执行脚本。
❑EVALSHA命令通过直接调用Lua环境中已定义的函数来执行脚本。❑SCRIPT FLUSH命令会清空服务器lua_scripts字典中保存的脚本,并重置Lua环境。
❑SCRIPT EXISTS命令接受一个或多个SHA1校验和为参数,并通过检查lua_scripts字典来确认校验和对应的脚本是否存在。
❑SCRIPT LOAD命令接受一个Lua脚本为参数,为该脚本在Lua环境中创建函数,并将脚本保存到lua_scripts字典中。
❑服务器在执行脚本之前,会为Lua环境设置一个超时处理钩子,当脚本出现超时运行情况时,客户端可以通过向服务器发送SCRIPT KILL命令来让钩子停止正在执行的脚本,或者发送SHUTDOWN nosave命令来让钩子关闭整个服务器。
❑主服务器复制EVAL、SCRIPT FLUSH、SCRIPT LOAD三个命令的方法和复制普通Redis命令一样,只要将相同的命令传播给从服务器就可以了。
❑主服务器在复制EVALSHA命令时,必须确保所有从服务器都已经载入了EVALSHA命令指定的SHA1校验和所对应的Lua脚本,如果不能确保这一点的话,主服务器会将EVALSHA命令转换成等效的EVAL命令,并通过传播EVAL命令来获得相同的脚本执行效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值