LUA+Reids实现库存秒杀预扣减 记录流水 以及自己的思考

目录

lua脚本

记录流水

记录流水的作用

流水什么时候删除


我们在做库存扣减的时候,显示基于Lua脚本和Redis实现的预扣减

这样可以在秒杀扣减的时候保证操作的原子性和高效性

lua脚本

复制代码
// ... 已有代码 ...

    @Override
    public InventoryResponse decrease(InventoryRequest request) {
        // 创建库存响应对象
        InventoryResponse inventoryResponse = new InventoryResponse();
        // 定义用于减少库存的Lua脚本
        String luaScript = """
                -- 检查哈希表 KEYS[2] 中是否已存在 ARGV[2] 对应的字段
                -- 如果存在,说明该操作已执行过,返回错误信息
                if redis.call('hexists', KEYS[2], ARGV[2]) == 1 then
                    return redis.error_reply('OPERATION_ALREADY_EXECUTED')
                end
                                
                -- 从 Redis 中获取 KEYS[1] 对应的值,即当前库存
                local current = redis.call('get', KEYS[1])
                -- 如果返回值为 false,说明键不存在,返回错误信息
                if current == false then
                    return redis.error_reply('KEY_NOT_FOUND')
                end
                -- 尝试将当前库存值转换为数字,如果转换失败,返回错误信息
                if tonumber(current) == nil then
                    return redis.error_reply('current value is not a number')
                end
                -- 如果当前库存为 0,返回库存为零的错误信息
                if tonumber(current) == 0 then
                    return redis.error_reply('INVENTORY_IS_ZERO')
                end
                -- 如果当前库存小于要减少的数量 ARGV[1],返回库存不足的错误信息
                if tonumber(current) < tonumber(ARGV[1]) then
                    return redis.error_reply('INVENTORY_NOT_ENOUGH')
                end
                                
                -- 计算减少库存后的新库存值
                local new = tonumber(current) - tonumber(ARGV[1])
                -- 将新的库存值存储回 Redis 中
                redis.call('set', KEYS[1], tostring(new))
                                
                -- 获取 Redis 服务器的当前时间(秒和微秒)
                local time = redis.call("time")
                -- 将获取到的时间转换为毫秒级时间戳
                local currentTimeMillis = (time[1] * 1000) + math.floor(time[2] / 1000)
                                
                -- 使用哈希结构存储库存减少操作的日志
                -- 在 KEYS[2] 对应的哈希表中,以 ARGV[2] 为字段名,存储操作日志的 JSON 字符串
                redis.call('hset', KEYS[2], ARGV[2], cjson.encode({
                    action = "decrease",  -- 操作类型为减少库存
                    from = current,       -- 操作前的库存值
                    to = new,             -- 操作后的库存值
                    change = ARGV[1],     -- 减少的库存数量
                    by = ARGV[2],         -- 操作标识
                    timestamp = currentTimeMillis  -- 操作的时间戳
                }))
                                
                -- 返回更新后的库存值
                return new
                """;

        try {
            // ... 已有代码 ...

首先是合法性校验

然后是原子性扣减

最后记录一条库存扣减流水

记录流水

在 lua 脚本中 我们不仅是做了库存的扣减

还在 redis 中用 hash 存储了一条流水

key:买家id + token + 扣减数量

value:本次扣减的变化的库存数,变化前的库存数,变化后的库存数 变化操作的ID 变化的时间戳

记录流水的作用

  1. 幂等

执行lua脚本的时候 查看是否有当前流水

如果有 说明是一个重复请求 直接幂等掉

  1. 对账

很重要

redis库存扣减后 需要持久化到数据库 这边使用的是mq异步处理

如何保证一致性呢

我们用redis里的hash流水和数据库流水进行对账

如果不一致的话

很有可能是mq丢消息了

就要会进行排查了

流水什么时候删除

数据库 与 缓存对账后删除

商品下架后24小时后删除

相关推荐
奔跑吧邓邓子几秒前
从0到1学PHP(三):PHP 流程控制:掌控程序的走向
开发语言·php·流程控制
青衫客363 分钟前
LLM—— 基于 MCP 协议(SSE 模式)的工具调用实践
python·大模型·llm
Ly2020Wj6 分钟前
pytorch入门:利用pytorch进行线性预测
人工智能·pytorch·python
R-G-B10 分钟前
【31】C# WinForm入门到精通 ——保存文件SaveFileDialog 【属性、方法、事件、实例、源码】
开发语言·c#·c# winform·c#保存文件·savefiledialog
典孝赢麻崩乐急19 分钟前
Redis学习------缓存穿透
redis·学习·缓存
啊阿狸不会拉杆19 分钟前
《Java 程序设计》第 11 章 - 泛型与集合
java·开发语言·jvm·数据结构·算法
渡我白衣29 分钟前
Linux网络编程:网络基础概念(下)
linux·网络
懒大王爱吃狼29 分钟前
PyCharm功能及安装使用教程
ide·python·pycharm·python基础·python教程·pycharm安装·pycharm使用
芷栀夏35 分钟前
如何通过IT-Tools与CPolar构建无缝开发通道?
网络·人工智能·python
小白要加油努力1 小时前
C++-STL-String重点知识
开发语言·c++