Redis高级功能-Lua脚本实现原理

7 篇文章 0 订阅

概览

Lua 是一个简洁、轻量、可扩展的脚本语言,它有着相对简单的API 因此很容易嵌入应用中,很多应用程序使用Lua作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。

Redis 从 2.6 版本开始支持 Lua 脚本,客户端通过 Lua 脚本,可以将多个 Redis 命令组合成一个原子性操作在服务器上执行。

例如使用 Redis 实现分布式锁时,为了保证操作原子性,通常也会使用 Lua 脚本封装锁相关的操作,以下为 Redisson 的加锁脚本实现:

Lua 脚本有以下优点:

  • 保证操作原子性
  • 减少网络开销,将多个指令组合到一个脚本中,与服务器的交互从多次变为一次
  • 可重复使用,在初次载入脚本之后,服务器会为脚本生成缓存,后续执行脚本时可直接使用缓存

基础命令

EVAL 命令:执行指定脚本;


SCRIPT LOAD 命令:加载指定脚本,执行成功后会返回脚本的SHA1校验和;

SCRIPT EXISTS 命令:检查脚本是否存在于服务器,1 表示存在;0 表示不存在;

EVALSHA 命令:指定SHA1校验和,执行对应脚本,这种方式在脚本体量较大时,能很大程度节省网络带宽

实现原理

redis 的 lua 功能的实现包含以下几个核心点:

  • lua 环境初始化
  • lua 脚本执行
  • lua 脚本管理
  • lua 脚本复制

下图展示了 lua 环境的初始化,以及 lua 环境与 redis 命令执行器的交互过程。

lua 环境初始化

lua 环境的构建主要包含三部分:基础函数库加载,redis表格加载,定制化函数加载。

基础函数库加载:加载基础函数库,例如字符串库,数学库等,支持 lua 脚本中的一些复杂操作。

redis表格加载:redis表格中包含 redis.call,redis.pcall 等函数,支持 lua 脚本中进行 redis 指令调用。

定制化函数加载:

  • 自定义随机函数,为了保证相同脚本在不同机器上执行能产生相同的结果,而原始的math.random,math.randomseed函数均不满足要求,故 redis 自定义实现了这两个随机函数
  • 排序辅助函数,另一个可能产生不一致数据的地方是那些带有不确定性的指令。例如集合中元素是无序排列的,即使两个集合中元素完全相同,最终输出结果的顺序也可能不一致,因此针对那些带有不确定性的指令,redis 会通过排序辅助函数对结果进行排序
  • 错误报告辅助函数,用于打印脚本执行过程中的错误信息,方便调试
脚本执行

脚本执行的过程主要包含以下三个步骤:

  • 加载脚本,生成对应的SHA1校验和,并生成脚本缓存
  • 在 lua 环境中生成脚本函数
  • 执行脚本函数

例如执行如下脚本:

1.首先会生成该脚本的 SHA1校验和:e17faafbc130014cebb229b71e0148b1f8f52389

2.生成对应的脚本函数,函数命名为 f_SHA1 的形式:f_e17faafbc130014cebb229b71e0148b1f8f52389

后续直接可以直接通过SHA1校验和定位并执行对应的脚本:

另外如果脚本中存在 redis 指令调用,lua 环境会通过一个伪客户端与命令执行器进行交互。

脚本管理

除了脚本执行以外,redis 还提供了几个脚本管理的指令:

  • SCRIPT FLUSH,用于清除服务器中所有和Lua脚本有关的信息
  • SCRIPT EXISTS,根据输入的SHA1校验和,检查校验和对应的脚本是否存在于服务器中
  • SCRIPT LOAD,手动载入脚本,生成对应的SHA1校验和
  • SCRIPT KILL,停止脚本执行

SCRIPT KILL 指令的实现相对更复杂一些,流程如下:

  • 脚本运行时间超出预先设置的 lua-time-limit 时,才允许被 SCRIPT KILL 强行停止脚本
  • 如果脚本中已经执行过写入操作,则不允许使用 SCRIPT KILL,只能通过 SHUTDOWN nosave 强行停止脚本,从而预防脏数据的写入

脚本复制

脚本复制是指当 Redis 运行在主从模式下时,主机执行了 Lua 脚本命令后,需要把命令及时同步到从机。

主要涉及上面提到的 EVAL,EVALSHA,SCRIPT FLUSH,SCRIPT LOAD 命令。

EVAL,SCRIPT FLUSH,SCRIPT LOAD 这三个命令的复制过程不复杂,与复制普通 Redis 命令的方法一样,当主机执行完这三个命令之后,直接将命令扩散给从机,从机重新执行一遍命令即可。

重点需要关注的是 EVALSHA 命令的复制,因为同样是执行 EVALSHA 命令,在从机上可能出现脚本未找到的错误,如下图所示:

当主机执行完 EVALSHA 0xxx1 命令后,将该命令复制到从机,但从机因为之前没有加载过 0xxx1 对应的脚本,因此执行失败了。

Redis 如何解决 EVALSHA 命令的复制问题呢?

首先 Redis 中维护了两个脚本相关的字典表:

  • lua_scripts,维护当前机器已加载的脚本,键保存的是脚本的SHA1校验和,值指向脚本本身
  • repl_scriptcache_dict,维护当前机器已同步给从机的脚本,键是脚本的SHA1校验和,值为空

复制过程如下:

  • 在复制 EVALSHA 命令前,会先检查脚本是否已同步至所有从机,通过 repl_scriptcache_dict 字典查询同步记录
  • 如果脚本已同步完成,直接复制 EVALSHA 命令,没有问题
  • 如果脚本未同步完成,则将 EVALSHA 命令转换成 EVAL 命令,重新触发一次从机的脚本同步

另外值得注意的是当有新的从机上线,主机会将 repl_scriptcache_dict 字典清空,为的是重新触发对所有从机的脚本同步。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值