欲求不满之 Redis Lua 脚本的执行原理

Multisim实战:从8选1到32选1数据选择器的完整搭建指南(附常见问题解决) 本文提供了一份详尽的Multisim实战指南,手把手教你从基础的8选1数据选择器开始,逐步级联搭建16选1乃至32选1数据选择器。文章深入剖析了级联的核心逻辑、片选方案以及使用译码器的专业方法,并附带了电源连接、输出冲突、信号毛刺等常见问题的诊断与解决技巧,帮助电子工程学生和爱好者扎实掌握数字电路的系统设计与仿真调试。 阅读详情

Redis 提供了非常丰富的指令集,但是用户依然不满足,希望可以自定义扩充若干指令来完成一些特定领域的问题。Redis 为这样的用户场景提供了 lua 脚本支持,用户可以向服务器发送 lua 脚本来执行自定义动作,获取脚本的响应数据。Redis 服务器会单线程原子性执行 lua 脚本,保证 lua 脚本在处理的过程中不会被任意其它请求打断。

640?wx_fmt=png
图片

比如在《Redis 深度历险》分布式锁小节,我们提到了 del_if_equals 伪指令,它可以将匹配 key 和删除 key 合并在一起原子性执行,Redis 原生没有提供这样功能的指令,它可以使用 lua 脚本来完成。

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> eval 'if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end' 1 foo bar
(integer) 1
127.0.0.1:6379> eval 'if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end' 1 foo bar
(integer) 0

EVAL SCRIPT KEY_NUM KEY1 KEY2 ... KEYN ARG1 ARG2 ....

SCRIPT LOAD 和 EVALSHA 指令

在上面的例子中,脚本的内容很短。如果脚本的内容很长,而且客户端需要频繁执行,那么每次都需要传递冗长的脚本内容势必比较浪费网络流量。所以 Redis 还提供了 SCRIPT LOAD 和 EVALSHA 指令来解决这个问题。

640?wx_fmt=png
图片

SCRIPT LOAD 指令用于将客户端提供的 lua 脚本传递到服务器而不执行,但是会得到脚本的唯一 ID,这个唯一 ID 是用来唯一标识服务器缓存的这段 lua 脚本,它是由 Redis 使用 sha1 算法揉捏脚本内容而得到的一个很长的字符串。有了这个唯一 ID,后面客户端就可以通过 EVALSHA 指令反复执行这个脚本了。我们知道 Redis 有 incrby 指令可以完成整数的自增操作,但是没有提供自乘这样的指令。

incrby key value  ==> $key = $key + value
mulby key value ==> $key = $key * value

local curVal = redis.call("get", KEYS[1])
if curVal == false then
  curVal = 0
else
  curVal = tonumber(curVal)
end
curVal = curVal * tonumber(ARGV[1])
redis.call("set", KEYS[1], curVal)
return curVal

local curVal = redis.call("get", KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal * tonumber(ARGV[1]); redis.call("set", KEYS[1], curVal); return curVal

127.0.0.1:6379> script load 'local curVal = redis.call("get", KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal * tonumber(ARGV[1]); redis.call("set", KEYS[1], curVal); return curVal'
"be4f93d8a5379e5e5b768a74e77c8a4eb0434441"

127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 notexistskey 5
(integer) 0
127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 notexistskey 5
(integer) 0
127.0.0.1:6379> set foo 1
OK
127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 foo 5
(integer) 5
127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 foo 5
(integer) 25

错误处理

上面的脚本参数要求传入的附加参数必须是整数,如果没有传递整数会怎样呢?

127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 foo bar
(error) ERR Error running script (call to f_be4f93d8a5379e5e5b768a74e77c8a4eb0434441): @user_script:1: user_script:1: attempt to perform arithmetic on a nil value

640?wx_fmt=png
图片

如果读者对 lua 语言有所了解就知道 lua 原生没有提供 try catch 语句,那上面提到的异常包裹语句究竟是用什么来实现的呢?lua 的替代方案是内置了 pcall(f) 函数调用。pcall 的意思是 protected call,它会让 f 函数运行在保护模式下,f 如果出现了错误,pcall 调用会返回 false 和错误信息。而普通的 call(f) 调用在遇到错误时只会向上抛出异常。在 Redis 的源码中可以看到 lua 脚本的执行被包裹在 pcall 函数调用中。

// 编译期
int luaCreateFunction(client *c, lua_State *lua, char *funcname, robj *body) {
  ...
  if (lua_pcall(lua,0,0,0)) {
    addReplyErrorFormat(c,"Error running script (new function): %s\n",
            lua_tostring(lua,-1));
    lua_pop(lua,1);
    return C_ERR;
  }
  ...
}

// 运行期
void evalGenericCommand(client *c, int evalsha) {
  ...
  err = lua_pcall(lua,0,1,-2);
  ...
}

错误传递

redis.call 函数调用会产生错误,脚本遇到这种错误会返回怎样的信息呢?我们再看个例子

127.0.0.1:6379> hset foo x 1 y 2
(integer) 2
127.0.0.1:6379> eval 'return redis.call("incr", "foo")' 0
(error) ERR Error running script (call to f_8727c9c34a61783916ca488b366c475cb3a446cc): @user_script:1: WRONGTYPE Operation against a key holding the wrong kind of value

127.0.0.1:6379> eval 'return redis.pcall("incr", "foo")' 0
(error) WRONGTYPE Operation against a key holding the wrong kind of value

脚本死循环怎么办?

Redis 的指令执行是个单线程,这个单线程还要执行来自客户端的 lua 脚本。如果 lua 脚本中来一个死循环,是不是 Redis 就完蛋了?Redis 为了解决这个问题,它提供了 script kill 指令用于动态杀死一个执行时间超时的 lua 脚本。不过 script kill 的执行有一个重要的前提,那就是当前正在执行的脚本没有对 Redis 的内部数据状态进行修改,因为 Redis 不允许 script kill 破坏脚本执行的原子性。比如脚本内部使用了 redis.call("set", key, value) 修改了内部的数据,那么 script kill 执行时服务器会返回错误。下面我们来尝试以下 script kill 指令。

127.0.0.1:6379> eval 'while(true) do print("hello") end' 0

127.0.0.1:6379> script kill
OK
(2.58s)

127.0.0.1:6379> eval 'while(true) do print("hello") end' 0
(error) ERR Error running script (call to f_d395649372f578b1a0d3a1dc1b2389717cadf403): @user_script:1: Script killed by user with SCRIPT KILL...
(6.99s)

Script Kill 的原理

下面我就要开始揭秘 kill 的原理了,lua 脚本引擎功能太强大了,它提供了各式各样的钩子函数,它允许在内部虚拟机执行指令时运行钩子代码。比如每执行 N 条指令执行一次某个钩子函数,Redis 正是使用了这个钩子函数。

640?wx_fmt=png
图片
void evalGenericCommand(client *c, int evalsha) {
  ...
  // lua引擎每执行10w条指令,执行一次钩子函数 luaMaskCountHook
  lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
  ...
}

思考题

在延时队列小节,我们使用 zrangebyscore 和 zdel 两条指令来争抢延时队列中的任务,通过 zdel 的返回值来决定是哪个客户端抢到了任务,这意味着那些没有抢到任务的客户端会有这样一种感受 —— 到了嘴边的肉(任务)最后还被别人抢走了,会很不爽。如果可以使用 lua 脚本来实现争抢逻辑,将 zrangebyscore 和 zdel 指令原子性执行就不会存在这种问题,读者可以尝试一下。

注:如果读者不熟悉 lua,建议先学习 lua 语言,lua 语言简单易学,但是也不是几分钟就可以学会的事,需要再来一本小册的内容。本小册专注 Redis,所以就不开大篇内容来细讲 lua 语言了,有需要的朋友可以搜索相关在线教程。

640?wx_fmt=jpeg

阅读更多深度技术文章,扫一扫上面的二维码关注微信公众号「码洞」

YOLOv11 损失函数进阶:SlideLoss 让小目标检测精度飙升 创建self.loss_fun = loss_fun # 基础损失函数(如BCEWithLogitsLoss)self.loss_coef = loss_coef # 损失系数# 计算滑动权重:根据预测与真实值的差异判断样本难度# 难度越高,权重越大该类在原有损失函数的基础上,通过diff计算样本预测与真实值的差异(差异越大,样本越难),进而生成动态权重,实现对难样本的重点学习。 阅读详情

相关推荐

《供应链金融风控模型设计:多维度数据整合与动态风险预警机制》

注:方案突出供应链金融「三流合一」(资金流、物流、信息流)的风控逻辑,通过多维度数据整合与动态预警,解决传统风控中中小企业信用评估难、链条风险传导监测滞后的问题。◦ 构建供应链拓扑图,计算「核心企业违约对上下游企业的影响系数」(如某电子厂商违约时,其前五大供应商的风险权重自动提升40%)• 区块链应用:将核心企业应付账款上链存证,防止「一票多融」欺诈(如某供应商用同一应收账款在多家银行融资)◦ 财务数据:核心企业审计报告、上下游企业纳税数据(增值税发票频次/金额)

2501_91651383的博客 1549

redis 基本命令

基本命令 # 客户端连接服务器 redis-cli -h <address> -p <port> -a <password> # 列出服务器信息 INFO # 查看服务是否允许 PING key # 判断键的存在; 返回存在的键的数量 EXISTS <key1> <key2> # 删除键 DEL <key1> <ke............

firefly 2745

保姆级CVE-2018-17066漏洞复现 DLink命令注入漏洞(更新完结)

之后按照之前复现的方式一样 我们丢到kali中执行binwalk解析。之后为了模拟 可以在kali里安装qemu-mipsel。在bin目录下可以尝试将goahead的环境搭配好。固件官网可以进行下载。

weixin_51339377的博客 882

redis脚本命令执行(redis.call)

1、redis-cli命令行中执行: # 调用redis命令设置缓存 # 不传参数 eval "return redis.call('set', 'name1', 'Tom')" 0 # 传入1个值参数 eval "return redis.call('set', 'name2', ARGV[1])" 0 "Tom" # 传入1个键名参数和1个值参数 eval "return redis.call('set', KEYS[1], ARGV[1])" 1 "name3" "Tom" 2、linux命

落白 1万+

Matlab:imageDatastore() 高效读取图像数据集的实战技巧

本文详细介绍了Matlab中imageDatastore()函数的高效使用技巧,帮助用户从繁琐的手动读取图像数据集中解放出来。通过核心语法、关键参数(如IncludeSubfolders、LabelSource、ReadSize)的解析,以及数据集分割、打乱等进阶实战操作,指导读者构建稳定、高效的图像数据读取管道,特别适用于大型图像数据集处理和机器学习项目。

hill8的博客 1100

FPGA时钟架构解密:从SRCC/MRCC到全局时钟树的实战指南

本文深入解析Xilinx 7系列FPGA时钟架构,从SRCC/MRCC引脚特性到全局时钟树设计,详细介绍了BUFG、BUFMR等关键组件的应用场景和优化策略。通过对比SRCC与MRCC的驱动范围及适用场景,提供低抖动时钟布线技巧和时钟域交叉(CDC)设计方法,帮助工程师构建高性能FPGA时钟系统。

weixin_29191081的博客 1484

redis 精髓

一、初探 1、简介 Redis是一个由ANSI C语言编写,性能优秀、支持网络、可持久化的内存数据库,并提供多种语言的API,线程安全,常用于做缓存和分布式锁,支持事务、持久化、主从复制、发布订阅、LUA脚本、LRU驱动事件、流水线、多种集群方案。 Redis跟memcached的只支持k-v类型不同,redis常用的类型主要是 String、List、Hash、Set、ZSet 这5种,并...

真是呆子啊的博客 1432

SAP系统中PP模块全配置文档

配置路径IMG>SAP NetWeaver>常规设置>维护日历>工厂日历事务代码SCAL要求传输工厂代码描述日历代码日历描述起始日期星期一星期二星期三星期四星期五星期六星期日有效开始日期有效结束日期1000南微医学工厂MT南微医学工厂日历√√√√√√√201820502.4定义工厂配置路径IMG>生产>物料清单>常规数据>定义BOM状态事务代码OS23要求传输配置路径IMG>生产>主数据>资源>常规数据>标准值>定义参数。

yanzibing99的博客 1892

实战篇Redis(2)

分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路 那么分布式锁他应该满足一些什么样的条件呢?可见性:多个线程都能看到相同的结果,注意:这个地方说的可见性并不是并发编程中指的内存可见性,只是说多个进程之间都能感知到变化的意思互斥:互斥是分布式锁的最基本的条件,使得程序串行执行高可用:程序不易崩溃,时时刻刻都保证较高的可用性高性能:由于加锁本身就让性能降低

wuzj1314的博客 948

TVS 和压敏电阻联合使用进行浪涌保护时,压敏电阻的压敏电压要低于TVS 的钳位电压 VC。

通过先让MOV接管,可以确保在大多数情况下,电压不会迅速上升到TVS二极管的钳位电压,从而保护TVS二极管免受持续高能量冲击的损害。总结来说,将MOV的压敏电压设置得低于TVS二极管的钳位电压,可以提供一种分级的保护机制,优化浪涌保护性能,同时保护敏感的TVS二极管不被过度的能量冲击损坏,并延长其使用寿命。MOV吸收的能量(Wmov):假设浪涌电流为10kA,MOV导通后将电压限制在300V,那么Wmov = Ipp * Vmov = 10kA * 300V = 3MW。例如,Vmov可以设定为275V。

weixin_66642980的博客 2246

12、信号采样与Z变换的全面解析

本文全面解析了信号采样和Z变换的核心概念与应用。内容涵盖采样基础、采样频率与奈奎斯特率的关系、离散时间处理模拟信号的方法、采样率转换技术,以及Z变换的定义、性质、常见变换对和逆变换计算。通过实例分析和流程图展示,帮助读者深入理解数字信号处理的关键步骤,为相关领域的学习与应用打下坚实基础。

Jerry的专栏 118

随手记录4:spring上下文ApplicationContext 两种获取方式和使用获取普通对象和FactoryBean的不同;

随手记录4:spring上下文ApplicationContext 获取和使用;简介关于ApplicationContext 获取关于ApplicationContext 使用1.UserService .java2.FactoryBeanConfig .java结论 简介 项目中基本上好多对象都是交给spring管理的; 但是有些静态的工具类中想获取该对象又不能通过 @Autowired直接注入; 或者有些方法里面需要使用到spring托管的对象; 可以通过ApplicationContext 直接获

weixin_43812522的博客 744

【AI智能体】Dify 搭建个人专属简历美化助手操作详解

Dify 搭建个人专属简历诊断助手实战操作详解

congge 4380

Redis基础面试题:Redis中的Lua脚本执行原理与原子性保证机制

面试官通过今天的讨论,我们深入了解了Redis中Lua脚本的执行原理、原子性保证机制,以及相关的优化与限制。你还有什么补充吗?Victor:补充一点,Lua脚本是Redis中非常强大的功能,但也需要谨慎使用。在实际开发中,应根据业务需求权衡脚本的复杂度与性能,确保既能满足功能需求,又不会对Redis服务造成负面影响。面试官:完全同意。

qq_61302385的博客 4186

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

【Redis】Redis中使用Lua脚本

Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua具体语法参考:https://www.runoob.com/lua/lua-tutorial.html。

morris 3万+

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

【进阶篇】2.4 Redis Lua脚本详解

Redis 其内置了脚本语言Lua,允许用户通过Lua脚本执行一系列操作。本篇博客将深入探讨Redis Lua脚本的执行机制,包括加载脚本、编译脚本和执行脚本的过程,以及脚本的原子性和事务性。我们将通过实例演示的方式,展示如何在Redis中使用Lua脚本,以及Lua脚本在Redis中的应用场景。让我们一起来了解Redis Lua脚本的强大之处吧!Lua是一种轻量级的脚本语言,它具有简洁、灵活、高效的特点。Lua语言最初是为嵌入式系统设计的,但现在已广泛应用于游戏开发、Web开发、嵌入式设备控制等领域。

日拱一卒无有尽,功不唐捐终入海 1万+

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

【实践篇】4.6 Redis Lua脚本执行原理和语法示例

在学习本文之前,我是强烈建议大家了解一下我的其他两篇博客《Redis 从入门到精通【进阶篇】一文学会Lua脚本》《Redis 从入门到精通【进阶篇】之Lua脚本详解》Redis通过嵌入Lua解释器,实现了对Lua脚本的执行。在执行过程中,Redis保证了脚本的原子性和阻塞性,同时通过脚本缓存和访问限制来提高性能和安全性。这使得Lua脚本成为在Redis中实现复杂逻辑的有效手段嵌入式Lua解释器:Redis内部集成了Lua解释器,这使得Redis可以直接执行Lua脚本。

日拱一卒无有尽,功不唐捐终入海 3992

redis之lua实现原理

redis之lua实现原理

SO_zxn的博客 1383

Redis Lua脚本好处、Redis执行Lua的两种方式、Redis缓存Lua脚本、Redis Lua原子性验证、Lua脚本IP限流、Lua脚本自乘

以客户端执行乘法脚本为例,在redis客户端执行Lua语言,拼接了很多命令,如果这样的一长执行串命令发给服务端,那么网络通信就会产生比较大的开销,所以Redis支持在服务端直接缓存一部分脚本的内容(它是用脚本生成了一段摘要,服务端可以根据这个摘要去执行脚本内容)。Lua脚本实现原子性的原理,就是执行一个Lua脚本的时候,里面有多个命令,这个时候会阻塞其它任何命令的执行和其它任何客户端的请求,也就是说,执行Lua的时候,只会执行Lua里的命令,以此来达到Lua里的多条命令成为一个整体来执行。

郝开的博客 2691

【JAVA高级】如何使用Redis加锁和解锁(一)、Lua脚本执行原理及流程

在Redis中加锁和解锁通常是通过Redis的原子性命令来实现的,以保证操作的原子性和线程安全。

qq_39666711的博客 2089

Redis系列——Lua脚本和redis事务的应用

本文主要阐述了Redis中Lua脚本与事务机制的功能,并深入分析了它们的优势与局限性。

庄的博客 1915

Redis篇-5--原理篇4--Lua脚本

Redis 支持使用 Lua 脚本来执行复杂的操作,这为 Redis 提供了更强的灵活性和性能优化能力。通过 Lua 脚本,你可以在服务器端执行一系列命令,而不需要多次往返客户端与服务器之间,从而减少了网络延迟并提高了效率。此外,Lua 脚本是原子性的,这意味着在脚本执行期间,其他客户端的请求不会被处理,确保了数据的一致性。

weisian的博客 3万+

Spring Boot中使用Redis和Lua脚本实现延时队列

通过使用Redis和Lua脚本,我们可以在Spring Boot环境中实现一个高效且可靠的延时队列系统。这种方法利用了Redis的有序集合数据结构和Lua脚本的原子性操作来确保任务的正确性和一致性。通过定期调度任务来处理到期的任务,我们可以实现各种需要延迟执行的操作,如发送提醒、执行定时任务等。

码到三十五 2万+

Redis 3.0 Lua脚本终极指南:从源码解析原子性执行原理

Redis 3.0 引入了强大的 Lua 脚本功能,彻底改变了我们使用 Redis 的方式。通过 EVAL 和 EVALSHA 命令,开发者可以在 Redis 服务器端执行复杂的多命令操作,同时保证**原子性执行**和**高性能**。本文将深入剖析带有详细注释的 Redis 3.0 源码,揭示 Lua 脚本在 Redis 中的工作机制。 ## 🔍 什么是 Redis Lua 脚本? Redi

gitblog_00454的博客 853
上一篇: 自己动手实现 Shell多进程套套符
下一篇: 短小精悍之 Redis 命令行工具有趣的罕见用法
码洞
博客等级 码龄10年 41粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值