Redis中Lua脚本的使用
Redis中Lua脚本的使用
参考资料:cnblogs.com、Redis
Lua 脚本介绍
Redis 从 2.6 版本开始内置 Lua 脚本执行引擎,用于在服务器端组合多个 Redis 命令为一个原子操作,从而避免客户端多次往返网络、保证复杂逻辑的原子性执行。Lua 是一种轻量级、可嵌入的脚本语言,非常适合嵌入 Redis 这种高性能内存数据库中。
Lua 在 Redis 中执行时无需额外配置,只需通过命令将脚本发送到 Redis。脚本由 Redis Server 内的 Lua 5.1 解释器运行。Redis 脚本将命令组合在一起,避免多次网络往返,并在执行期间阻塞其他客户端命令,确保原子性。脚本执行结果可返回值给客户端。
Lua 脚本基本语法
Lua 语法简单,适合嵌入式脚本应用。基本数据类型包括:nil(空)、boolean(布尔值)、number(数字)、string(字符串)和 table(表)。table 是 Lua 的核心数据结构,它既可表现为数组也可表现为字典。
变量声明:
1 | name = 'example' -- 全局变量 |
表(类似数组、字典):
1 | local arr = {'a', 'b', 1} -- 数组样 |
控制结构类似其他语言:
1 | if x < 10 then |
循环遍历表:
1 | for i,v in ipairs(arr) do |
返回值可以使用 return,Lua 支持返回多个值,但在 Redis 脚本中推荐返回一个表以避免客户端解析混乱。
Lua 脚本在 Redis 中的使用方式
在 Redis 中执行 Lua 脚本核心命令是 EVAL:
1 | EVAL <script> <numkeys> <key1> ... <keyN> <arg1> ... <argM> |
<script>:Lua 脚本内容<numkeys>:脚本使用的 Redis 键数量- 接下来是
<key1> ... keyN,脚本中通过KEYS[1]等访问 - 余下是参数通过
ARGV[1]等访问。
键与参数分离的目的是让 Redis 能够基于 numkeys 提前分析访问的键,有助于集群模式下正确路由。
1 | 127.0.0.1:6379> EVAL "return redis.call('GET', KEYS[1])" 1 mykey |
Redis 还提供了脚本缓存机制,通过 SCRIPT LOAD 加载脚本并返回 SHA1 摘要,然后可用 EVALSHA 执行,避免每次发送完整脚本字符串,提高性能。
1 | SCRIPT LOAD "return 'hello'" |
Redis 中 Lua 脚本的特性与原理
Lua 脚本在 Redis 内部执行时具有以下关键特性:
- 原子性:整个脚本在执行期间不会被打断,所有命令作为一个原子事务运行。
- 阻塞执行:执行期间 Redis 不处理其他客户端命令,因此脚本应保持短小以避免阻塞客户端。
- 数据类型转换:Lua 与 Redis 之间数据互传需进行类型转换,例如 Lua 返回的小数可能被转换成整数。
- 错误处理:
redis.call()在命令错误时会抛出错误,中断脚本;redis.pcall()则返回错误表,可在脚本内部捕获处理。
Redis 中的最佳实践
脚本设计
Lua 脚本应尽量保持短、逻辑简单。避免大型循环或长耗时计算,因为它们会阻塞 Redis 线程。
参数与键分离
使用 KEYS 和 ARGV 明确区分 Redis 键和参数。键用于 Redis 访问,参数用于业务逻辑。Redis 集群模式下所有键应在同一槽位以避免错误脚本行为。
缓存机制
尽可能通过 SCRIPT LOAD 和 EVALSHA 重用脚本,减少网络传输和重复编译开销。
错误与测试
在上线前全面测试脚本逻辑。脚本执行失败不会自动回滚之前的修改。需要在业务层处理失败后的状态一致性。
避免复杂 Lua 特性
Lua 有许多语言特性如协程、复杂函数定义等,但在 Redis 脚本中应避免使用,聚焦于简单逻辑以提高可维护性。
以下内容在 Redis + Lua 的工程语境下展开,强调为什么用、怎么用、避免什么。
Redis 使用实例
库存扣减(高并发原子性)
场景
秒杀、抢购、优惠券库存扣减。
要求:不能超卖、不能多扣、必须原子执行。
问题本质
以下逻辑如果拆散为多条命令,会产生竞态条件:
- 查询库存
- 判断是否大于 0
- 扣减库存
Lua 脚本实现
1 | local stock = tonumber(redis.call('GET', KEYS[1])) |
Redis 调用
1 | EVAL "<lua-script>" 1 stock:sku:1001 |
价值
- 单线程原子执行
- 无需 Redis 事务(MULTI/EXEC)
- 避免客户端 CAS 自旋
- 秒杀场景标准解法
分布式限流(滑动窗口)
场景
接口防刷、短信验证码、登录频控。
限流规则示例
- 每个用户
- 10 秒内最多 5 次
Lua 脚本实现(ZSET)
1 | local key = KEYS[1] |
特点
- 精确滑动窗口
- 清理 + 判断 + 写入一次完成
- 避免多命令时间漂移
价值
- 高并发下限流结果一致
- Lua 是唯一正确解法
防重复提交 / 幂等控制
场景
- 表单重复提交
- 订单重复创建
- MQ 消费幂等
典型逻辑
- key 不存在 → 执行业务
- key 存在 → 拒绝
Lua 脚本
1 | if redis.call('EXISTS', KEYS[1]) == 1 then |
优点
- SETNX + EXPIRE 原子化
- 不依赖 Redis 版本是否支持 SET key value NX EX
Java 中调用 Redis Lua 脚本
StringRedisTemplate 调用方式
适用场景
- key / value 都是字符串
- 绝大多数业务场景首选
示例
1 | String lua = |
关键点
KEYS→ List 传入ARGV→ execute 后的可变参数- 返回值类型必须显式声明
RedisTemplate 调用方式
适用场景
- value 是对象、JSON、复杂结构
- 使用序列化器
示例
1 | DefaultRedisScript<Long> script = new DefaultRedisScript<>(); |
总结
Redis 内置 Lua 脚本功能用于将一系列命令组合为一个原子操作,提高性能、减少网络往返、保证逻辑一致性。Lua 语法简单,适合轻量级编写 Redis 逻辑。通过 EVAL、EVALSHA、SCRIPT LOAD 等命令执行和管理脚本,结合良好设计和测试策略,可显著提升 Redis 的开发效率与运行稳定性。





