黑马点评秒杀系统总结:从数据库锁到Redis分布式锁的完整演进思路
本文总结了黑马点评秒杀系统的优化演进过程。从最初数据库乐观锁解决超卖问题,到单体环境使用悲观锁解决一人一单,再到引入Redis分布式锁解决跨JVM并发问题。针对分布式锁的误删和原子性问题,采用UUID+线程ID校验和Lua脚本解决。最终引入Redisson提供生产级锁功能,并通过消息队列实现秒杀削峰。整个演进路线清晰展示了从基础到高级的秒杀系统优化思路,为解决高并发场景下的库存和订单问题提供了完整
·
一、背景说明:为什么要做秒杀优化?
在高并发秒杀场景中,系统主要面临两个核心问题:
- 超卖问题:库存被并发扣减,出现负数。
- 一人一单问题:同一用户在极短时间内多次下单成功。
黑马点评项目围绕这两个问题,逐步引出了多种并发控制方案,非常适合作为后端学习Redis与高并发的入门实战。
二、第一阶段:数据库层 —— 乐观锁解决超卖问题
在最初的实现中,库存扣减直接依赖数据库:
UPDATE voucher SET stock = stock - 1 WHERE id = ? AND stock > 0;
优点:
- 实现简单
- 不引入额外中间件
问题:
- 高并发下数据库压力极大
- 只能解决超卖,无法解决一人一单
- 频繁失败重试,性能差
➡️ 结论:数据库并不是并发控制的理想位置。
三、第二阶段:单体环境 —— 悲观锁解决一人一单
在单体部署环境下,可以通过synchronized或ReentrantLock实现线程隔离:
synchronized (userId.toString().intern()) {
// 判断是否下过单
// 扣库存
// 创建订单
}
优点:
- 能保证单JVM内的一人一单
- 逻辑直观
问题:
- 锁只在JVM内生效
- 多实例部署时彻底失效
四、第三阶段:Redis分布式锁 —— SETNX解决跨JVM并发问题
黑马点评使用Redis的SETNX(set if not exists)实现分布式锁:
SET lock:order:userId value NX EX 10
作用:
- 跨JVM保证一人一单
- Redis性能远高于数据库
五、分布式锁的隐藏问题与解决方案
1️⃣ 锁误删问题
如果锁过期后被其他线程获取,原线程再执行DEL,会误删别人的锁。
解决方案:
- 锁value = UUID + 线程ID
- 删除时先校验是否为当前线程持有
2️⃣ 校验 + 删除的原子性问题
"判断锁是谁 + 删除锁"不是原子操作。
解决方案:Lua脚本
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0
六、SETNX的局限性 & Redisson的引入
原生setnx实现的分布式锁仍存在问题:
- ❌ 不可重入
- ❌ 不支持阻塞重试
- ❌ 锁续期需要自行实现
黑马点评后续通过Redisson提供的分布式锁解决这些问题:
- 可重入锁
- 看门狗自动续约
- 支持公平锁、读写锁等
➡️ 更接近生产级分布式锁方案。
七、最终优化:引入消息队列进行秒杀削峰
在Redis校验通过后,不直接创建订单,而是:
- Redis中完成资格校验与库存预扣
- 将下单请求写入消息队列
- 由异步消费者完成订单落库
优势:
- 削峰填谷
- 解耦秒杀入口与下单逻辑
- 提升系统整体吞吐能力
八、总结:黑马点评秒杀的核心设计思路
如果用一句话来总结黑马点评秒杀的演进路线的话,我觉得可以这样表述:
从数据库乐观锁防超卖 → JVM悲观锁防并发 → Redis分布式锁解决一人一单 → Redisson提供生产级锁能力 → 消息队列完成最终秒杀优化。
更多推荐


所有评论(0)