百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

详细介绍一下Redis内部是如何执行Lua脚本的?

liuian 2025-03-20 16:22 5 浏览

在之前的分享介绍中,我们知道,Redis是通过EVAL命令或者是通过EVALSHA命令来调用执行Lua脚本的,当客户端请求执行Lua脚本的时候,脚本会通过EVAL命令传递到Redis服务器中,而EVALSHA命令则是通过已经执行了脚本加载命令SCRIPT LOAD加载到Redis中的Lua脚本的SHA1哈希值来调用执行脚本。

在Redis接收到了Lua脚本之后,会将其传递到Lua虚拟机来进行脚本的加载和编译,在Redis内部主要是通过LuaJIT来执行脚本,LuaJIT是一个高效的Lua脚本即时编译器实现,并且专门是针对Lua脚本进行了优化,可以将Lua脚本编译成高度化的机器编码,这种方式可以有效的提高脚本执行的效率。

LuaJIT的工作原理

在Lua脚本解释器执行Lua脚本代码的时候,首先会将Lua脚本源码编译成为字节码文件,然后通过Lua虚拟机去逐条的解释并且执行这些代码中的字节码,而LuaJIT中执行Lua脚本的时候,也会将Lua脚本先转换成字节码,然后会对这个字节码进行进一步的优化来脚本执行的效率。

只不过与标准Lua脚本解释器不同的是LuaJIT提供的事一种即时编译的机制,也就在运行的时候会将字节码即时编译成机器码,这种编译与传统的编译型语言不同的是这种编译是在程序执行的过程中进行的,而传统的C或者C++就是在编译时就已经生成了字节码文件。通过LuaJIT的即时编译机制使得在Lua脚本在脚本执行的时候达到接近原生代码执行的速度。

在脚本执行过程中,LuaJIT技术会自动检测程序中的热路径,所谓的热路径就是指被频繁执行的代码,然后将这些热路径编译成机器码,这样程序中就会直接执行这些已经编译好的机器码,如果某些逻辑是第一次执行,那么在执行的时候也会被JIT机制编译为机器码。在LuaJIT运行的过程中会对数据类型也进行优化,避免在执行机器码的过程中每次都对数据类型进行检查。

在标准的Lua虚拟机中,会通过栈来存储中间运行结果信息,但是在LuaJIT中则是通过寄存器虚拟机来实现这种机制,其实提到寄存器很多开发者就会明白它的操作效率要比栈的操作效率要更高,这样也是通过这种寄存器模型来提高命令执行的效率,减少栈操作带来的其他系统开销。

在LuaJIT中通过使用内联缓存技术来提高内部函数调用以及查找调用的效率,当LuaJIT发现某个函数或者是方法调用频率较高的时候,那么就会对其地址进行缓存,这样这个方法在后续的调用过程中会直接进行跳转而不需要再次进行寻址查找,从而减少了方法查找和调用带来的系统开销。

既然是虚拟机那么不可或缺的就是垃圾回收机制,在LuaJIT中提供的垃圾回收机制与Lua中提供的垃圾回收机制类似,都是采用了标记清除算法,并且对垃圾的回收做了一定的优化,由于Lua脚本的调用执行效率较高,所以垃圾回收的速度效率也会比较高一点。

Lua脚本的键锁定

在之前的介绍中,我们提到过Lua脚本执行的过程中,Redis会对Lua脚本操作的键值进行加锁,这样是为了保证脚本执行的原子性,也就是说在Lua脚本执行的过程中,其他的客户端无法对操作的键值进行修改。

Redis对键值锁定的方式被称为是键级锁定,这种锁定机制通过一种被叫做分布式锁的机制来实现,确保在操作该键的时候不会被其他的客户端操作所修改。也就是说Redis中将会对所有的涉及到操作的键值进行独立管理,在脚本执行的过程中确保脚本的操作不会被其他的命令操作所打断。

从底层实现来讲其实还是基于了Redis的事务队列实现,在脚本执行的过程中,是按照顺序对脚本命令进行执行,如下所示。

int evalGenericCommand(redisClient *c, int issha) {
    // 获取 Lua 脚本中的键
    unsigned long numkeys = 0;
    robj **keys = luaScriptKeys(c->argv[1], c->argc, &numkeys);  // 获取涉及的键

    // 锁定涉及的键
    lockKeysForScript(keys, numkeys);
    
    // 执行 Lua 脚本
    executeLuaScript(c, keys, numkeys);
    
    // 解锁所有键
    unlockKeys(keys, numkeys);
}

当脚本执行完成之后,也会主动去释放所有被锁定的键值。

Redis中如何执行Lua脚本

在Redis中通过EVAL命令来调用Lua脚本,而在编写Lua脚本的时候,涉及到了redis.call()redis.pcall()这两个函数,这两个函数在脚本执行的过程中都会通过Redis提供的处理机制来与Redis数据库底层进行交互,而我们知道Redis底层命令执行是通过一个统一的命令调度框架来实现,也就是说redis.call()redis.pcall()这两个函数也是基于这个Redis命令调度框架来执行。

redis.call()函数的底层实现

在Redis中解析到了redis.call()函数的时候,这条命令就会被解析成Redis支持的RESP格式,也就是说函数调用会被解析为一条Redis的执行命令,然后进入到到Redis的命令调度机制中,通过命令表server.command_table来查找到对应的命令执行函数进行调用,例如常见的SET命令会被映射到setCommand 函数中。

一旦找到了命令处理函数,那么就会执行该函数进行实际的数据处理操作,并修改Redis内部的数据处理状态,处理完成之后返回对应的命令执行结果。

/* 用于执行 Redis 命令 */
int redisCommand(lua_State *lua) {
    int argc = lua_gettop(lua);   // 获取参数的数量
    // 获取命令名称
    const char *command = lua_tostring(lua, 1);
    // 创建一个 Redis 命令的参数列表
    robj **argv = (robj **) zmalloc(sizeof(robj*) * argc);
    for (int i = 0; i < argc; i++) {
        argv[i] = luaToObject(lua, i + 2);  // 从 Lua 栈中获取参数并转换成 Redis 对象
    }
    
    // 执行 Redis 命令
    int retval = call(command, argc, argv);
    zfree(argv);  // 释放参数列表
    return retval;
}

如果在处理过程中,发现了异常redis.call()函数也会抛出异常并且终止Lua脚本的执行,并且会将错误信息返回到客户端。

由于Redis的命令是在Redis主线程中执行完成的,所以在发现错误之后会立即返回并且剩余的命令将不会被执行,这个机制与Redis事务提供的机制有所不同。

redis.pcall()函数的底层实现

这个函数的底层实现与redis.call()的底层实现机制类似,唯一不同的是redis.pcall() 命令提供了一个内置的错误捕获机制,在命令执行的过程中通过这个错误捕获机制来保护Lua脚本执行的准确性,这种机制会捕获在执行过程中发生的任何错误并且会返回一个包含了所有错误的信息表,并不会直接抛出异常。

/* 用于执行 Redis 命令,并捕获错误 */
int redisPcall(lua_State *lua) {
    int argc = lua_gettop(lua);
    const char *command = lua_tostring(lua, 1);
    robj **argv = (robj **) zmalloc(sizeof(robj*) * argc);
    for (int i = 0; i < argc; i++) {
        argv[i] = luaToObject(lua, i + 2);
    }

    // 使用 pcall 保护性调用 Redis 命令
    int retval = lua_pcall(lua, command, argc, argv);
    zfree(argv);
    return retval;
}

也就是说,脚本执行过程中如果某些脚本执行失败了,redis.pcall() 会封装一个Lua的错误异常表,后续的的命令还会继续执行,并不会受到错误异常处理的影响,当脚本执行完成之后,会返回给最终脚本执行之后的一个错误异常表。这种机制就与Redis提供的事务机制有点类似了。

result = redis.pcall('GET', 'non_existing_key')
if result.err then
    -- 错误处理
    return "Key not found"
else
    return result
end

上面两种机制,主要的差异就体现在对于错误的处理方式上,在实际操作过程中我们可以根据实际需要的业务处理方式来选择合适的调用方式。

总结

Redis中执行Lua脚本的过程包括脚本的加载、编译、键的锁定、执行以及结果返回等过程。通过Lua脚本的调用执行,Redis实现了对多个Redis命令组合的的原子操作,并通过锁定机制确保了脚本执行期间的数据一致性和事务性。Lua脚本的原子性和高效性,使得在复杂的操作场景下,Redis 能够提供更加灵活的解决方案。

相关推荐

GANs为何引爆机器学习?这篇基于TensorFlow的实例教程为你解惑!

「机器人圈导览」:生成对抗网络无疑是机器学习领域近三年来最火爆的研究领域,相关论文层出不求,各种领域的应用层出不穷。那么,GAN到底如何实践?本文编译自Medium,该文作者以一朵玫瑰花为例,详细阐...

高丽大学等机构联合发布StarGAN:可自定义表情和面部特征

原文来源:arXiv、GitHub作者:YunjeyChoi、MinjeChoi、MunyoungKim、Jung-WooHa、SungKim、JaegulChoo「雷克世界」编译:嗯~...

TensorFlow和PyTorch相继发布最新版,有何变化

原文来源:GitHub「机器人圈」编译:嗯~阿童木呀、多啦A亮Tensorflow主要特征和改进在Tensorflow库中添加封装评估量。所添加的评估量列表如下:1.深度神经网络分类器(DNNCl...

「2022 年」崔庆才 Python3 爬虫教程 - 深度学习识别滑动验证码缺口

上一节我们使用OpenCV识别了图形验证码躯壳欧。这时候就有朋友可能会说了,现在深度学习不是对图像识别很准吗?那深度学习可以用在识别滑动验证码缺口位置吗?当然也是可以的,本节我们就来了解下使用深度...

20K star!搞定 LLM 微调的开源利器

LLM(大语言模型)微调一直都是老大难问题,不仅因为微调需要大量的计算资源,而且微调的方法也很多,要去尝试每种方法的效果,需要安装大量的第三方库和依赖,甚至要接入一些框架,可能在还没开始微调就已经因为...

大模型DeepSeek本地部署后如何进行自定义调整?

1.理解模型架构a)查看深度求索官方文档或提供的源代码文件,了解模型的结构、输入输出格式以及支持的功能。模型是否为预训练权重?如果是,可以在预训练的基础上进行微调(Fine-tuning)。是否需要...

因配置不当,约5000个AI模型与数据集在公网暴露

除了可访问机器学习模型外,暴露的数据还可能包括训练数据集、超参数,甚至是用于构建模型的原始数据。前情回顾·人工智能安全动态向ChatGPT植入恶意“长期记忆”,持续窃取用户输入数据多模态大语言模型的致...

基于pytorch的深度学习人员重识别

基于pytorch的深度学习人员重识别Torchreid是一个库。基于pytorch的深度学习人员重识别。特点:支持多GPU训练支持图像的人员重识别与视频的人员重识别端到端的训练与评估简单的re...

DeepSeek本地部署:轻松训练你的AI模型

引言:为什么选择本地部署?在AI技术飞速发展的今天,越来越多的企业和个人希望将AI技术应用于实际场景中。然而,对于一些对数据隐私和计算资源有特殊需求的用户来说,云端部署可能并不是最佳选择。此时,本地部...

谷歌今天又开源了,这次是Sketch-RNN

前不久,谷歌公布了一项最新技术,可以教机器画画。今天,谷歌开源了代码。在我们研究其代码之前,首先先按要求设置Magenta环境。(https://github.com/tensorflow/magen...

Tensorflow 使用预训练模型训练的完整流程

前面已经介绍了深度学习框架Tensorflow的图像的标注和训练数据的准备工作,本文介绍一下使用预训练模型完成训练并导出训练的模型。1.选择预训练模型1.1下载预训练模型首先需要在Tensorf...

30天大模型调优学习计划(30分钟训练大模型)

30天大模型调优学习计划,结合Unsloth和Lora进行大模型微调,掌握大模型基础知识和调优方法,熟练应用。第1周:基础入门目标:了解大模型基础并熟悉Unsloth等工具的基本使用。Day1:大模...

python爬取喜马拉雅音频,json参数解析

一.抓包分析json,获取加密方式1.抓包获取音频界面f12打开抓包工具,播放一个(非vip)视频,点击“媒体”单击打开可以复制URL,发现就是我们要的音频。复制“CKwRIJEEXn-cABa0Tg...

五、JSONPath使用(Python)(json数据python)

1.安装方法pipinstalljsonpath2.jsonpath与Xpath下面表格是jsonpath语法与Xpath的完整概述和比较。Xpathjsonpath概述/$根节点.@当前节点...

Python网络爬虫的时候json=就是让你少写个json.dumps()

大家好,我是皮皮。一、前言前几天在Python白银交流群【空翼】问了一个Python网络爬虫的问题,提问截图如下:登录请求地址是这个:二、实现过程这里【甯同学】给了一个提示,如下所示:估计很多小伙伴和...