Java中级项目如何处理并发扣减库存_Java并发控制实战解析

乐观锁通过version字段校验防止超卖,Redis+Lua实现原子扣减,分布式锁兜底,库存分段设计提升并发吞吐。

用数据库乐观锁防止超卖

库存扣减最常见问题是多个请求同时读到相同剩余量,都判断“够用”后执行更新,结果实际扣多了。乐观锁是轻量级方案:在商品表加version字段,更新时带上版本号校验。

  • SQL写法:UPDATE item SET stock = stock - 1, version = version + 1 WHERE id = ? AND stock >= 1 AND version = ?
  • Java中用MyBatis执行后检查update影响行数是否为1;为0说明被其他线程抢先修改,需重试或抛异常
  • 适合读多写少、冲突不频繁的场景,避免数据库行锁阻塞,吞吐较高

Redis+Lua脚本实现原子扣减

当库存变化频繁、并发极高(如秒杀),单靠数据库压力大。把库存放到Redis里,用Lua脚本保证“读-判-减”三步不可分割。

  • 脚本示例:if redis.call("GET", KEYS[1]) >= tonumber(ARGV[1]) then redis.call("DECRBY", KEYS[1], ARGV[1]); return 1 else return 0 end
  • Java调用:redisTemplate.execute(script, Collections.singletonList("item:1001"), "1")
  • 注意:需配合定时任务或MQ,将Redis扣减结果异步落库,保证最终一致性

分布式锁兜底防极端竞争

乐观锁和Redis脚本能覆盖大部分情况,但遇到网络延迟、脚本执行失败等边界问题,仍可能漏控。此时用Redis分布式锁做二次保障。

  • 推荐使用Redisson的RLock,支持自动续期、可重入、锁失效保护
  • 锁粒度建议按商品ID划分,避免全局锁拖慢整体性能
  • 务必设置合理超时时间(如3~5秒),并确保业务逻辑在锁内快速完成,防止死锁或长等待

库存预热与分段设计提升吞吐

纯靠锁或脚本还不够,架构上可进一步优化:把一个商品库存拆成N个逻辑子库存(如按用户ID哈希取模),各子库存独立扣减,互不影响。

  • 例如:商品A总库存10000,拆为100个槽位,每个槽位初始100;扣减时先算slot = userId % 100,再操作对应key
  • 配合Redis集群部署,天然分散热点,QPS可线性提升
  • 需额外维护“总库存”视图(可用HyperLogLog估算或定时聚合),用于前端展示和超卖拦截