Lua是一种编程语言,它的基本语法大家可以参考网站:Lua 教程 | 菜鸟教程
1.什么是Lua
LUA 是一种轻量级、可扩展的脚本编程语言,以其小巧、快速、灵活和易于嵌入到宿主应用程序中的特性而闻名。
轻量级:小巧的解释器,占用资源少,适用于嵌入式环境和资源受限系统。 动态类型:支持动态类型,变量无需事先声明类型,运行时自动识别。 可扩展:通过 C API 易于与宿主程序集成,提供丰富的扩展机制,便于构建定制化功能。 面向过程与函数式:支持函数作为第一类值,具备高阶函数、闭包等特性,适合函数式编程风格。 协程(Coroutine):内置协程支持,实现非阻塞的并发编程,适用于异步任务与事件处理。 简洁易学:语法简单清晰,学习曲线平缓,代码易于阅读和编写。 高效执行:解释器经过优化,执行速度快,尤其在脚本语言中表现出色。 跨平台:用 C 语言编写,高度可移植,能在多种操作系统和硬件架构上运行。 广泛应用:广泛应用于游戏开发、配置脚本、科学计算、网络服务等领域,作为嵌入式脚本语言尤为出色。
我们在这里用它是因为Lua脚本执行Redis是因为它是原子性的,可以保证一整段脚本是一个事务,然后如果有需求它也可以很好的实现异步任务
2.准备工作
-
IDEA安装插件
3.使用
-
3.1 编写Lua脚本
放在resources目录下
只是个例子,具体脚本可以根据需求和参考网址编写你自己的业务脚本
--- --- Generated by Luanalysis --- Created by orz. --- DateTime: 2024/4/23 20:25 --- -- 1.参数列表 -- 1.1.优惠券id local voucherId = ARGV[1] -- 1.2.用户id local userId = ARGV[2] -- 1.3.订单id local orderId = ARGV[3] -- 2.数据key -- 2.1.库存key local stockKey = 'seckill:stock:' .. voucherId -- 2.2.订单key local orderKey = 'seckill:order:' .. voucherId -- 3.脚本业务 -- 3.1.判断库存是否充足 get stockKey if(tonumber(redis.call('get', stockKey)) <= 0) then -- 3.2.库存不足,返回1 return 1 end -- 3.2.判断用户是否下单 SISMEMBER orderKey userId if(redis.call('sismember', orderKey, userId) == 1) then -- 3.3.存在,说明是重复下单,返回2 return 2 end -- 3.4.扣库存 incrby stockKey -1 redis.call('incrby', stockKey, -1) -- 3.5.下单(保存用户)sadd orderKey userId redis.call('sadd', orderKey, userId) -- 3.6.发送消息到队列中, XADD stream.orders * k1 v1 k2 v2 ... redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId) return 0
-
3.2 ServiceImpl业务中使用
//DefaultRedisScript 是 Spring Data Redis 框架提供的一个类,用于简化在 Spring 应用程序中使用 Redis 脚本(如Lua脚本)的过程 private static final DefaultRedisScript<Long> SECKILL_SCRIPT; //脚本静态代码块初始化 static { SECKILL_SCRIPT = new DefaultRedisScript<>(); //在resources目录下导入我们的脚本文件 SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua")); SECKILL_SCRIPT.setResultType(Long.class); }
执行脚本的方法
@Override public Result seckillVoucher(Long voucherId) { //获取用户 Long userId = UserHolder.getUser().getId(); long orderId = redisIdWorker.nextId("order"); // 1.执行lua脚本(要传三部分参数) //stringRedisTemplate.execute(),该方法就是用来执行Redis脚本 //第一部分:RedisScript: 包含待执行脚本内容及返回值类型的对象。 //第二部分:keys: 与脚本关联的键列表,可在脚本中通过 KEYS 访问。 //第三部分:args: 传递给脚本的额外参数数组,可在脚本中通过 ARGV 访问。 Long result = stringRedisTemplate.execute( SECKILL_SCRIPT,//这里就是我们上面静态代码块引入的脚步 Collections.emptyList(), //这里要传key类型的参数,因为我上面写入redis缓存的key是用下面的第三部分参数args来拼出来的, //所以脚本是不需要key的,所以这里用方法传一个空集合,注意不要传null voucherId.toString(), userId.toString(), String.valueOf(orderId) //上面第三部分要传的参数取决于我们脚本的ARGV[]数量 ); //根据脚本的返回值result.intValue()来进行下一步的逻辑 int r = result.intValue(); // 2.判断结果是否为0 if (r != 0) { // 2.1.不为0 ,代表没有购买资格 return Result.fail(r == 1 ? "库存不足" : "不能重复下单"); } //TODO 保存阻塞队列 // 3.返回订单id return Result.ok(orderId); }