今日日报:Redis Lua 原子脚本与三段式库存模型——从商品表拆出独立库存微服务
拆库存服务:库存不是商品的附属品 某天盯着商品表的字段列表,发现 quantity、remain_quantity、sale_count 这三个东西怎么看怎么和 name、price、cover_url 不是一家人。name 改了不频繁,库存每秒都在扣——高频写和低频读挤在同一行,互相锁着玩。 决定拆。新建了一个 mall-inventory 微服务,独立数据库 cloud_mall_inventory,三张表:inventory(主库存)、inventory_batch(批次追踪)、inventory_log(变动流水)。 最头疼的问题:扣库存的一致性 库存扣减最怕两个事:超卖和半截崩溃。 第一个做法是两条 Redis 命令: redisUtil.increment(key, -quantity); // 扣 available redisUtil.increment(frozenKey, quantity); // 加 frozen 问题很明显——第一条执行完、第二条还没跑的时候,机器崩了怎么办?available 扣了但 frozen 没加,库存"凭空消失"了。 解法是 Lua 脚本,把两条操作打包发给 Redis: local qty = -tonumber(ARGV[1]) local avail = redis.call('INCRBY', KEYS[1], qty) if avail < 0 then redis.call('INCRBY', KEYS[1], -qty) return -1 end redis.call('INCRBY', KEYS[2], -qty) return avail - qty Redis 内部保证整个脚本一次性原子执行,不存在中间状态。Spring Data Redis 的 StringRedisTemplate.execute(script, keys, args) 直接调用就行。 三段式库存模型 完整链路改成了"冻结 → 确定 → 释放": 下单 → frozen +1, available -1(冻结) ├─ 支付成功 → frozen -1, sale_count +1(确认扣减) └─ 超时/取消 → frozen -1, available +1(释放) 之前是下单直接扣 remain_quantity,30 分钟后超时取消还要回滚。但回滚依赖 MQ 消息,MQ 挂了库存就永远不恢复了。新模型不存在这个问题——「可用库存」只负责「卖」,frozen 只负责「锁」,职责拆开,逻辑自洽。 ...