Spring Boot集成Redis + Lua脚本实现原子性操作:小白入门指南
一、为什么需要Lua脚本?
在分布式系统中,多个Redis命令的组合操作(如先查询后修改)可能因网络延迟、并发竞争导致数据不一致。Lua脚本可以将多个命令封装为一个原子操作,确保所有命令要么全部成功,要么全部失败,避免中间状态
优势:
- 原子性:Redis单线程执行Lua脚本,执行期间不会被其他操作打断
- 减少网络开销:多个命令合并为一次请求
2. Lua脚本中 redis.call
和 redis.pcall
的区别
redis.call
:在执行 Redis 命令时,如果发生错误会抛出异常并终止脚本执行。redis.pcall
:在执行 Redis 命令时,即使发生错误也不会抛出异常,而是返回一个包含错误信息的表,脚本会继续执行
3. 在不同数据类型中的应用
1. String 类型
操作 | Lua 脚本示例 | 参数说明 | 返回值 |
---|
设置值 | redis.call('SET', KEYS[1], ARGV[1]) | KEYS[1]: key | “OK” |
获取值 | redis.call('GET', KEYS[1]) | KEYS[1]: key | String 或 nil (不存在时) |
自增 | redis.call('INCR', KEYS[1]) | KEYS[1]: key | 新整数(若 key 不存在则初始化为 1) |
带过期时间设置 | redis.call('SETEX', KEYS[1], ARGV[1], ARGV[2]) | ARGV[1]: 秒,ARGV[2]: 值 | “OK” |
2. Hash 类型
操作 | Lua 脚本示例 | 参数说明 | 返回值 |
---|
设置字段值 | redis.call('HSET', KEYS[1], ARGV[1], ARGV[2]) | ARGV[1]: 字段名 | 1(新增)或 0(更新) |
获取字段值 | redis.call('HGET', KEYS[1], ARGV[1]) | ARGV[1]: 字段名 | String 或 nil |
获取所有字段 | redis.call('HGETALL', KEYS[1]) | KEYS[1]: key | Table(交替字段名和值) |
删除字段 | redis.call('HDEL', KEYS[1], ARGV[1]) | ARGV[1]: 字段名 | 删除的字段数量 |
3. List 类型
操作 | Lua 脚本示例 | 参数说明 | 返回值 |
---|
左/右插入元素 | redis.call('LPUSH', KEYS[1], ARGV[1]) | ARGV[1]: 元素值 | 列表长度 |
获取范围元素 | redis.call('LRANGE', KEYS[1], ARGV[1], ARGV[2]) | ARGV[1]: 起始索引(0-based) | Table(元素列表) |
弹出元素 | redis.call('LPOP', KEYS[1]) | - | 元素值或 nil (空列表时) |
4. Set 类型
操作 | Lua 脚本示例 | 参数说明 | 返回值 |
---|
添加元素 | redis.call('SADD', KEYS[1], ARGV[1]) | ARGV[1]: 元素值 | 新增元素数量 |
判断元素存在 | redis.call('SISMEMBER', KEYS[1], ARGV[1]) | ARGV[1]: 元素值 | 1(存在)或 0(不存在) |
获取所有元素 | redis.call('SMEMBERS', KEYS[1]) | KEYS[1]: key | Table(无序元素列表) |
5. ZSet(有序集合)
操作 | Lua 脚本示例 | 参数说明 | 返回值 |
---|
添加带分数元素 | redis.call('ZADD', KEYS[1], ARGV[1], ARGV[2]) | ARGV[1]: 分数,ARGV[2]: 元素 | 新增元素数量 |
获取分数范围元素 | redis.call('ZRANGEBYSCORE', KEYS[1], ARGV[1], ARGV[2]) | ARGV[1]: 最小分数,ARGV[2]: 最大分数 | Table(元素列表) |
获取排名 | redis.call('ZRANK', KEYS[1], ARGV[1]) | ARGV[1]: 元素值 | 排名(从 0 开始)或 nil |
6. 通用操作
操作 | Lua 脚本示例 | 参数说明 | 返回值 |
---|
判断 Key 是否存在 | redis.call('EXISTS', KEYS[1]) | KEYS[1]: key | 1(存在)或 0(不存在) |
设置过期时间 | redis.call('EXPIRE', KEYS[1], ARGV[1]) | ARGV[1]: 秒数 | 1(成功)或 0(Key 不存在) |
删除 Key | redis.call('DEL', KEYS[1]) | KEYS[1]: key | 删除的 Key 数量 |