文章目录
不同Lua返回值对应泛型对照表
| Lua返回示例 | Java泛型选什么 |
|---|---|
return 0/1/2 数字 |
Long |
return "ok" 字符串 |
String |
return {1,2,3} 数组 |
List<Object> |
return nil |
Object |
seckill.lua
lua
-- 0 成功
-- 1 库存不足
-- 2 用户重复下单
local vouvherId = ARGV[1]
local userId = ARGV[2]
local stockKey = 'hmdp:seckill:stock:' .. vouvherId
local orderKey = 'hmdp:seckill:order:' .. userId
-- 判断库存
if tonumber(redis.call('get', stockKey)) <= 0 then
return 1
end
-- 判断用户是否已经下单
if redis.call('sismember', orderKey, userId) == 1 then
return 2
end
-- 扣库存
redis.call('incrby', stockKey, -1)
redis.call('sadd', orderKey, userId)
return 0
service实现类
- Redis 协议里,
lua脚本返回的整数是64 位有符号整数 ,对应 Java 的long;即使数值很小 0、1、2,底层依然是 64bit,所以 Spring Data Redis 脚本 API 统一使用 Long 接收数字结果。
java
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
static {
SECKILL_SCRIPT = new DefaultRedisScript<>();
SECKILL_SCRIPT.setLocation(new ClassPathResource("seckillJvm.lua"));
SECKILL_SCRIPT.setResultType(Long.class);
}
@Override
public Result seckillVoucherAsync(Long voucherId) {
Long userId = UserHolder.getUser().getId();
"执行 lua 脚本"
Long result = stringRedisTemplate.execute(
SECKILL_SCRIPT,
Collections.emptyList(),
voucherId.toString(), userId.toString()
);
int r = result.intValue();
if(r != 0){
// 1 库存不足;2 用户重复下单
return Result.fail(r == 1 ? "库存不足" : "不能重复下单");
}
}
}
代码逐段拆解
java
Long result = stringRedisTemplate.execute(
SECKILL_SCRIPT,
Collections.emptyList(),
voucherId.toString(), userId.toString()
);
参数签名
java
<T> T execute(
RedisScript<T> script,
List<String> keys,
Object... args
)
-
第一个参数:SECKILL_SCRIPT
就是我们定义的
DefaultRedisScript<Long>,里面加载了你写的lua脚本文件,指定返回类型是Long。 -
第二个参数:
Collections.emptyList()→ KEYS\[\]数组📌 对应lua里
KEYS[1]、KEYS[2]你的 lua 脚本 完全没有使用 KEYS 数组,key都是在lua内部字符串拼接:
lualocal stockKey = 'hmdp:seckill:stock:' .. vouvherId local orderKey = 'hmdp:seckill:order:' .. userId所以这里 keys 传空集合
emptyList()。⚠️ Redis官方最佳实践:尽量把key放到KEYS\[\]参数传入,不要lua内部拼接字符串key ,是为了兼容Redis集群槽位。
集群模式下,lua脚本操作的key必须全部来自KEYS数组,自己拼接会报错
CROSSSLOT Keys in request don't hash to the same slot。 -
后面可变参数:
voucherId.toString(), userId.toString()→ ARGV\[\]对应lua脚本:
lualocal vouvherId = ARGV[1] local userId = ARGV[2]voucherId.toString()→ARGV[1]userId.toString()→ARGV[2]
ARGV 放普通参数,没有集群槽位限制,可以随便传。
返回值 Long result
lua脚本 return 0 / 1 / 2,底层协议返回64位整数,所以Java接收为Long。
java
"使用的时候 转换为 int"
if (result == 0) {
// 成功
} else if (result ==1) {
// 库存不足
} else if (result ==2) {
// 重复下单
}
注意:
result 不会是 null,脚本一定有 return。
🚨 你的脚本集群坑点(重点)
现在写法:key在lua内部拼接
lua
local stockKey = 'hmdp:seckill:stock:' .. vouvherId
local orderKey = 'hmdp:seckill:order:' .. userId
- 单机Redis:正常跑没问题
- Redis集群环境:直接报错跨槽
改写成支持集群的正确版本(把key通过Java传入KEYS数组)
Java调用:
java
String stockKey = "hmdp:seckill:stock:" + voucherId;
String orderKey = "hmdp:seckill:order:" + userId;
Long result = stringRedisTemplate.execute(
SECKILL_SCRIPT,
List.of(stockKey, orderKey), // KEYS[1] stockKey , KEYS[2] orderKey
voucherId.toString(), userId.toString()
);
lua脚本修改:
lua
-- KEYS[1] stockKey hmdp:seckill:stock:xxx
-- KEYS[2] orderKey hmdp:seckill:order:xxx
-- ARGV[1] voucherId
-- ARGV[2] userId
local stockKey = KEYS[1]
local orderKey = KEYS[2]
if tonumber(redis.call('get', stockKey)) <= 0 then
return 1
end
if redis.call('sismember', orderKey, ARGV[2]) == 1 then
return 2
end
redis.call('incrby', stockKey, -1)
redis.call('sadd', orderKey, ARGV[2])
return 0
集群要求:脚本操作的所有 redis key,
必须全部来自KEYS[]参数,不能在lua字符串拼接生成key。