代码环境
- 仓库:https://github.com/springvortex/spring-cloud-alibaba.git
- 分支:
release/v1.0.0- JDK:
21- Spring Boot:
4.1.0
商品购买是这套项目里第一个真正需要跨实例互斥的业务。Provider 多实例部署后,同一个商品的扣库存请求可能被 LoadBalancer 分到不同 JVM,本地锁失效,库存就可能超卖。
这次实现的链路是:
Consumer -> GoodsFeignApi -> Provider
-> 按商品 ID 获取 Redisson 锁
-> 事务内校验商品
-> 数据库条件更新扣库存
-> 保存订单主表和明细
-> 提交后清理商品详情缓存
锁按商品维度拆分:
redissonClient.getLock("zjc:provider:goods:purchase:lock:" + goodsId);
不同商品不互相等待,同一商品在集群内串行执行。这个版本的锁等待为 10 秒,超时返回 503 和“当前购买人数过多”;后续压测提交又把等待调整到 20 秒。
只靠锁还不够。数据库层使用条件更新:
UPDATE t_goods
SET stock = stock - #{quantity}
WHERE goods_id = #{goodsId}
AND status = 1
AND is_deleted = 0
AND stock >= #{quantity}
影响行数不是 1 就说明商品不可购买或库存不足,事务直接失败回滚。这样即使 Redis 锁异常失效、看门狗续期异常或锁被误释放,数据库条件也能保证库存不会被扣成负数。
订单主表和明细使用已有的 saveWithDetails 同事务保存,明细保留商品名称、单价和数量快照。事务提交后再清理 provider:goods:id 详情缓存;缓存清理失败不影响购买结果,但会记录 ERROR,提示旧库存可能保留到 TTL 到期。
购买请求本身也有边界:用户 ID 必须有效,购买数量限制在 1 到 100。Consumer 侧只是 Feign 入口,不本地兜底下单,Provider 不可用时统一返回业务繁忙。
经验总结
分布式锁负责减少并发冲突,数据库条件更新负责守住最后一致性边界。两层能力解决的是不同问题,购买链路里都需要存在。
评论
评论由 GitHub Discussions 承载,需要 GitHub 账号登录。