大数据 -43 Redis Lua 脚本实战全解析 eval redis.call redis.pcall

点一下关注吧!!!非常感谢!!持续更新!!!
🚀 AI 篇持续更新中!(长期更新)
AI 炼丹日志-30-新发布【1T 万亿】参数量大模型!Kimi‑K2 开源大模型解读与实践,持续打造实用 AI 工具指南!📐🤖
💻 Java 篇正式开启!(300 篇)
目前 2025 年 07 月 16 日更新到:Java-74 深入浅出 RPC Dubbo Admin 可视化管理 安装使用 源码编译、Docker 启动 MyBatis 已完结,Spring 已完结,Nginx 已完结,Tomcat 已完结,分布式服务正在更新!深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300 篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT 案例 详解
Lua 是一门轻量级、高性能、易嵌入的脚本语言,被广泛应用于游戏开发、嵌入式系统和数据处理领域。而在 Redis 中,Lua 脚本因其原子性与灵活性成为复杂操作和事务逻辑的首选工具。本文首先介绍了 Lua 的背景、核心特性与典型应用场景,随后深入讲解了 Redis 中 EVAL/EVALSHA 的语法、参数机制、执行模型,并结合多个实用案例(如原子计数器、CAS 更新、批量插入、哈希批量设置等)展开详细剖析。此外,还对 redis.call 与 redis.pcall 的差异、脚本缓存机制以及脚本调试方法进行了全面梳理,适合所有希望系统掌握 Redis Lua 脚本能力的开发者阅读收藏。

章节内容
上一节我们完成了如下的内容:
Redis 功能扩展
Redis 发布/订阅模式
Redis 事务相关
Redis 为什么是弱事务
等等
背景介绍
这里是三台公网云服务器,每台 2C4G,搭建一个大数据的学习环境,供我学习。
2C4G 编号 h121
2C4G 编号 h122
2C2G 编号 h123

Lua 介绍

简介一下
Lua 是一个轻量级、高效率的脚本语言,由巴西里约热内卢天主教大学(PUC-Rio)的 Tecgraf 实验室于 1993 年开发。它采用标准的 ANSI C 编写,具有跨平台特性,可以在大多数操作系统上运行,包括 Windows、Linux、macOS 等。Lua 的设计目标之一是保持核心精简(整个解释器仅约 200KB),同时通过灵活的扩展机制提供强大的功能。其源代码遵循 MIT 许可协议开放,允许自由使用和修改。
Lua 的主要特点包括:
动态类型系统
自动内存管理
一流的函数支持
简洁清晰的语法
高效的字节码解释器
简易的 C API 接口
典型应用场景包括:
游戏开发 Lua 因其轻量级和高性能的特点,被广泛用于游戏开发中作为脚本引擎。例如:
《魔兽世界》使用 Lua 编写插件和 UI 定制
《愤怒的小鸟》系列游戏使用 Lua 实现游戏逻辑
知名游戏引擎如 Cocos2d-x、Unity 等都支持 Lua 脚本
独立应用脚本
Adobe Lightroom 使用 Lua 实现插件系统
Wireshark 网络分析工具使用 Lua 编写协议解析器
VLC 媒体播放器通过 Lua 扩展功能
Web 应用脚本
作为 Nginx 的脚本扩展(OpenResty 项目)
用于实现 Web 应用的业务逻辑(如 Lapis 框架)
轻量级 API 网关和服务编排
数据库插件
Redis 支持 Lua 脚本实现复杂原子操作
PostgreSQL 可通过 PL/Lua 编写存储过程
MongoDB 支持使用 Lua 编写 MapReduce 任务
此外,Lua 还被应用于:
嵌入式系统(如路由器固件)
科学计算和数据分析
自动化测试脚本
网络安全工具开发
工业控制领域
Lua 的扩展库生态系统(LuaRocks)提供了丰富的第三方模块,涵盖网络编程、GUI 开发、数据结构等各个方面,进一步扩展了其应用范围。
下载安装
Redis EVAL 命令详解
命令语法
参数详细说明
script
一段 Lua 5.1 脚本程序,这段脚本会在 Redis 服务器的上下文中执行。脚本可以包含任意有效的 Lua 代码,并能够调用 Redis 命令。例如:
numkeys
指定后续参数中有多少个是键名(key)。这个数值必须是非负整数,用于帮助 Redis 区分键名参数和普通参数。
key
从命令的第三个参数开始,numkeys 个参数会被视为键名。这些键名可以在 Lua 脚本中通过全局变量 KEYS 数组访问,索引从 1 开始(KEYS[1], KEYS[2]等)。
arg
在键名参数之后的参数会被视为普通参数。这些参数可以在 Lua 脚本中通过全局变量 ARGV 数组访问,同样索引从 1 开始(ARGV[1], ARGV[2]等)。
使用示例
基本用法
以下命令展示了如何同时传递键名和参数给 Lua 脚本:
输出结果将会是:
实际应用场景
原子性操作
实现一个原子性的 get-and-set 操作:
条件更新
只有在旧值匹配时才更新:
复杂计算
使用 Lua 进行复杂计算:
这将返回 60(10+20+30 的和)
注意事项
脚本中的 Redis 命令调用必须使用
redis.call()
或redis.pcall()
函数Lua 数组索引从 1 开始,而不是 0
在集群模式下,所有键必须在同一个哈希槽中
脚本执行是原子性的,执行期间不会执行其他命令
Redis Lua 脚本执行相关命令详解
redis.call 与 redis.pcall 的区别
redis.call
返回值就是 Redis 命令执行的返回值
例如:
local res = redis.call('GET', 'key')
,res 将存储 GET 命令的返回值如果执行过程中出错,会立即返回错误信息,并且不再继续执行后续脚本代码
示例:当尝试获取不存在的 key 时,
redis.call('HGET', 'non_hash_key', 'field')
会立即抛出错误适合在需要严格错误处理的场景中使用
redis.pcall
返回值同样是 Redis 命令执行的返回值
与 redis.call 的返回值处理方式相同
如果执行过程中出错,会记录错误信息但不会中断脚本执行
示例:
local res = redis.pcall('HGET', 'non_hash_key', 'field')
会返回错误对象,但脚本会继续执行错误信息可以通过检查返回值类型来判断(通常返回一个 Lua 表包含 err 字段)
适合在需要容错处理、不希望因为单个命令失败而中断整个脚本的场景中使用
EVALSHA 命令详解
EVALSHA 的背景
常规的 EVAL 命令要求每次执行时都要发送完整的脚本主体
示例:
EVAL "return redis.call('GET', KEYS[1])" 1 mykey
这会在网络传输中造成不必要的带宽消耗,特别是对于频繁执行的复杂脚本
EVALSHA 的工作原理
接收脚本的 SHA1 校验值作为第一个参数,而不是完整的脚本内容
示例:
EVALSHA a42059b356c875f0717db19a51f0aaca2ae5d991 1 mykey
Redis 服务器会维护一个脚本缓存(Script Cache),存储最近执行过的脚本
当使用 EVALSHA 时,Redis 会:
检查本地缓存中是否存在该 SHA1 对应的脚本
如果存在,直接执行缓存的脚本
如果不存在,返回错误(可使用 SCRIPT LOAD 预先加载)
使用建议
客户端可以先使用
SCRIPT LOAD
命令预加载脚本,获取 SHA1 值示例:
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
对于可能未缓存的情况,客户端应实现 fallback 机制:
先尝试 EVALSHA
如果失败(返回 NOSCRIPT 错误),再使用 EVAL 并重新加载脚本
脚本缓存是易失性的,在 Redis 重启后会丢失,需要客户端重新加载
Script 命令
Script Flush 清除所有脚本缓存
Script Exists 根据给定的脚本校验和,检查指定脚本是否存在于缓存脚本中
Script Load 将一个脚本装入脚本缓存 返回 SHA1 摘要 但并不立即运行
Script Kill 杀死当前正在运行的脚本
脚本测试 1
编写一个脚本
写入如下内容
保存后,执行 Shell 命令

脚本测试 2
编写脚本
写入如下内容
保存后,执行 Shell 命令
执行的结果如下图:

案例 1:原子计数器 - 详细解析与实现
功能说明
该 Lua 脚本实现了一个原子化的计数器功能,能够安全地对 Redis 中的键值进行数值递增操作,并返回更新后的值。主要解决多个客户端并发操作时可能出现的竞态条件问题。
脚本详细解析
典型应用场景
页面访问计数器:
每次页面访问时执行脚本
示例调用:
EVAL "脚本内容" 1 page:views 1
限流器实现:
记录某操作在时间窗口内的调用次数
示例:限制每分钟最多 100 次 API 调用
库存扣减:
商品库存的原子性扣减
示例:
EVAL "脚本内容" 1 product:123:stock -1
用户积分系统:
用户积分的增减操作
示例:
EVAL "脚本内容" 1 user:456:points 10
使用注意事项
键不存在时会自动初始化为 0
确保传入的 ARGV[1]是可以转换为数字的字符串
对于大数值操作,注意 Redis 的数值范围限制
在集群环境下,确保操作的 key 都在同一个 slot 上
案例 2:检查并设置值(CAS 操作实现)
这个 Lua 脚本实现了一个原子性的"检查并设置"(Compare-And-Swap,CAS)操作,常用于 Redis 中的乐观锁机制。以下是详细说明:
功能描述
脚本会检查指定键的当前值是否等于给定的旧值(old_value),如果相等则将其更新为新值(new_value),整个过程是原子性的。
参数说明
KEYS[1]
: 需要操作的 Redis 键名ARGV[1]
: 期望的旧值(old_value),用于比较ARGV[2]
: 要设置的新值(new_value)
执行流程
首先获取键名
KEYS[1]
的当前值将当前值与参数
ARGV[1]
进行比较如果两者相等:
使用
SET
命令将键的值更新为ARGV[2]
返回 1 表示设置成功
如果不相等:
不执行任何修改
返回 0 表示设置失败
返回值
1:表示检查通过并成功设置了新值
0:表示当前值与期望的旧值不匹配,未执行设置操作
应用场景
乐观锁控制:在多客户端并发修改时确保数据一致性
配置更新:确保只在特定状态下更新配置
资源分配:确保资源未被其他客户端占用
注意事项
该操作是原子性的,在并发环境下非常安全
如果键不存在,
GET
会返回 nil,此时与任何值比较都会失败对于复杂数据类型(如 Hash,List),需要相应调整比较逻辑
案例 3:列表的批量插入
案例 3:获取并删除键值对
案例 4:哈希表字段的批量设置
版权声明: 本文为 InfoQ 作者【武子康】的原创文章。
原文链接:【http://xie.infoq.cn/article/c9a347d04ae57d2608ee99619】。文章转载请联系作者。
评论