• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Redis(源码剖析):69---Lua脚本之创建并修改Lua环境(lua_open函数、call函数、pcall函 ...

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

创建并修改Luab环境

  • 为了在Redis服务器中执行Lua脚本,Redis在服务器内嵌了一个Lua环境(environment),并对这个Lua环境进行了一系列修改,从而确保这个Lua环境可以满足Redis服务器 的需要
  • Redis服务器创建并修改Lua环境的整个过程由以下步骤组成:
    • ①创建一个基础的Lua环境,之后的所有修改都是针对这个环境进行的
    • ②载入多个函数库到Lua环境里面,让Lua脚本可以使用这些函数库来进行数据操作
    • ③创建全局表格redis,这个表格包含了对Redis进行操作的函数,比如用于在Lua脚本中 执行Redis命令的redis.call函数
    • ④使用Redis自制的随机函数来替换Lua原有的带有副作用的随机函数,从而避免在脚本 中引入副作用
    • ⑤创建排序辅助函数,Lua环境使用这个辅佐函数来对一部分Redis命令的结果进行排 序,从而消除这些命令的不确定性
    • ⑥创建redis.pcall函数的错误报告辅助函数,这个函数可以提供更详细的出错信息
    • ⑦对Lua环境中的全局环境进行保护,防止用户在执行Lua脚本的过程中,将额外的全 局变量添加到Lua环境中
    • ⑧将完成修改的Lua环境保存到服务器状态的lua属性中,等待执行服务器传来的Lua脚本

一、创建Lua环境

  • 在最开始的这一步,服务器首先调用Lua的C API函数lua_open,创建一个新的Lua环境
  • 因为lua_open函数创建的只是一个基本的Lua环境,为了让这个Lua环境可以满足Redis的操作要求,接下来服务器将对这个Lua环境进行一系列修改

二、载入函数库

  • Redis修改Lua环境的第一步,就是将以下函数库载入到Lua环境里面:
    • 基础库(base library):这个库包含Lua的核心(core)函数,比如assert、error、 pairs、tostring、pcall等。另外,为了防止用户从外部文件中引入不安全的代码,库中的 loadfile函数会被删除。
    • 表格库(table library):这个库包含用于处理表格的通用函数,比如table.concat、 table.insert、table.remove、table.sort等。
    • 字符串库(string library):这个库包含用于处理字符串的通用函数,比如用于对字符 串进行查找的string.find函数,对字符串进行格式化的string.format函数,查看字符串长度的 string.len函数,对字符串进行翻转的string.reverse函数等。
    • 数学库(math library):这个库是标准C语言数学库的接口,它包括计算绝对值的 math.abs函数,返回多个数中的最大值和最小值的math.max函数和math.min函数,计算二次 方根的math.sqrt函数,计算对数的math.log函数等。
    • 调试库(debug library):这个库提供了对程序进行调试所需的函数,比如对程序设置 钩子和取得钩子的debug.sethook函数和debug.gethook函数,返回给定函数相关信息的 debug.getinfo函数,为对象设置元数据的debug.setmetatable函数,获取对象元数据的 debug.getmetatable函数等。
    • Lua CJSON库http://www.kyne.com.au/~mark/software/lua-cjson.php):这个库用于处 理UTF-8编码的JSON格式,其中cjson.decode函数将一个JSON格式的字符串转换为一个Lua 值,而cjson.encode函数将一个Lua值序列化为JSON格式的字符串。
    • Struct库http://www.inf.puc-rio.br/~roberto/struct/):这个库用于在Lua值和C结构 (struct)之间进行转换,函数struct.pack将多个Lua值打包成一个类结构(struct-like)字符 串,而函数struct.unpack则从一个类结构字符串中解包出多个Lua值
    • Lua cmsgpack库https://github.com/antirez/lua-cmsgpack):这个库用于处理 MessagePack格式的数据,其中cmsgpack.pack函数将Lua值转换为MessagePack数据,而 cmsgpack.unpack函数则将MessagePack数据转换为Lua值
  • 通过使用这些功能强大的函数库,Lua脚本可以直接对执行Redis命令获得的数据进行复杂的操作

三、创建redis全局表格

  • 在这一步,服务器将在Lua环境中创建一个redis表格(table),并将它设为全局变量
  • 这个redis表格包含以下函数:
    • 用于执行Redis命令的redis.call和redis.pcall函数
    • 用于记录Redis日志(log)的redis.log函数,以及相应的日志级别(level)常量: redis.LOG_DEBUG,redis.LOG_VERBOSE,redis.LOG_NOTICE,以及 redis.LOG_WARNING
    • 用于计算SHA1校验和的redis.sha1hex函数
    • 用于返回错误信息的redis.error_reply函数和redis.status_reply函数
  • 在这些函数里面,最常用也最重要的要数redis.call函数和redis.pcall函数,通过这两个函数,用户可以直接在Lua脚本中执行Redis命令:

四、使用Redis自制的随机函数来替换Lua原有的随机函数

  • 为了保证相同的脚本可以在不同的机器上产生相同的结果,Redis要求所有传入服务器的Lua脚本,以及Lua环境中的所有函数,都必须是无副作用(side effect)的纯函数(pure function)
  • 但是,在之前载入Lua环境的math函数库中,用于生成随机数的math.random函数和math.randomseed函数都是带有副作用的,它们不符合Redis对Lua环境的无副作用要求
  • 因为上面的原因,Redis使用自制的函数替换了math库中原有的math.random函数和 math.randomseed函数,替换之后的两个函数有以下特征:
    • 对于相同的seed来说,math.random总产生相同的随机数序列,这个函数是一个纯函数
    • 除非在脚本中使用math.randomseed显式地修改seed,否则每次运行脚本时,Lua环境都使用固定的math.randomseed(0)语句来初始化seed

演示案例

  • 例如,使用以下脚本,我们可以打印seed值为0时,math.random对于输入10至1所产生的随机序列:

  • 无论执行这个脚本多少次,产生的值都是相同的:

  • 但是,如果我们在另一个脚本里面,调用math.randomseed将seed修改为10086:

  • 那么这个脚本生成的随机数序列将和使用默认seed值0时生成的随机序列不同:

五、创建排序辅助函数(__redis__compare_helper()

  • 上一个小节说到,为了防止带有副作用的函数令脚本产生不一致的数据,Redis对math库的math.random函数和math.randomseed函数进行了替换
  • 对于Lua脚本来说,另一个可能产生不一致数据的地方是那些带有不确定性质的命令。 比如对于一个集合键来说,因为集合元素的排列是无序的,所以即使两个集合的元素完全相 同,它们的输出结果也可能并不相同

带有不确定性的命令

  • 考虑下面这个集合例子,这个例子中的fruit集合和another-fruit集合包含的元素是完全相同的,只是因为集合添加 元素的顺序不同,SMEMBERS命令的输出就产生了不同的结果:

  • Redis将SMEMBERS这种在相同数据集上可能会产生不同输出的命令称为“带有不确定性 的命令”,这些命令包括:

  • 为了消除这些命令带来的不确定性,服务器会为Lua环境创建一个排序辅助函数 __redis__compare_helper,当Lua脚本执行完一个带有不确定性的命令之后,程序会使用 __redis__compare_helper作为对比函数,自动调用table.sort函数对命令的返回值做一次排 序,以此来保证相同的数据集总是产生相同的输出
  • 举个例子,如果我们在Lua脚本中对fruit集合和another-fruit集合执行SMEMBERS命令, 那么两个脚本将得出相同的结果,因为脚本已经对SMEMBERS命令的输出进行过排序了:

六、创建redis.pcall函数的错误报告辅助函数(__redis__err__handler()

  • 在这一步,服务器将为Lua环境创建一个名为__redis__err__handler的错误处理函数,当 脚本调用redis.pcall函数执行Redis命令,并且被执行的命令出现错误时, __redis__err__handler就会打印出错代码的来源和发生错误的行数,为程序的调试提供方便
  • 举个例子,如果客户端要求服务器执行以下Lua脚本:

  • 那么服务器将向客户端返回一个错误,其中@user_script说明这是一个用户定义的函数,而之后的4则说明出错的代码位于Lua 脚本的第四行:

七、保护Lua的全局环境

  • 在这一步,服务器将对Lua环境中的全局环境进行保护,确保传入服务器的脚本不会因为忘记使用local关键字而将额外的全局变量添加到Lua环境里面
  • 因为全局变量保护的原因,当一个脚本试图创建一个全局变量时,服务器将报告一个错误:

  • 除此之外,试图获取一个不存在的全局变量也会引发一个错误:

  • 不过Redis并未禁止用户修改已存在的全局变量,所以在执行Lua脚本的时候,必须非常小心,以免错误地修改了已存在的全局变量:

八、将Lua环境保存到服务器状态的lua属性里面

  • 经过以上的一系列修改,Redis服务器对Lua环境的修改工作到此就结束了,在最后的这 一步,服务器会将Lua环境和服务器状态的lua属性关联起来,如下图所示:

  • 因为Redis使用串行化的方式来执行Redis命令,所以在任何特定时间里,最多都只会有一个脚本能够被放进Lua环境里面运行,因此,整个Redis服务器只需要创建一个Lua环境即可

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
OpenResty + Lua访问Redis,实现高并发访问时的毫秒级响应打回发布时间:2022-07-22
下一篇:
lua工具类(二)发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap