秒杀场景需多层防护:用有界线程池削峰、Semaphore限流、Redis+Lua原子扣库存、异步落库保最终一致性。

秒杀场景下,瞬时高并发请求直接打到数据库,极易引发连接池耗尽、CPU飙升、锁表甚至物理库崩溃。Java中仅靠CountDownLatch无法解决根本问题——它只是同步工具,不控制并发量也不缓冲请求。真正有效的方案是:用线程池做**请求削峰**,用CountDownLatch(或更合适的工具)做**结果聚合/等待**,同时配合缓存、限流、异步落库等手段保护数据库。
用固定大小线程池控制并发写入量
避免所有请求直冲数据库,应将秒杀请求统一提交到有界线程池,让其以可控速率处理:
- 线程数建议设为数据库连接池最大连接数的1.5~2倍(如Druid连接池maxActive=20,则线程池corePoolSize=30)
- 使用
LinkedBlockingQueue并指定容量(如1000),超量请求快速失败或降级,不堆积内存 - 拒绝策略推荐
AbortPolicy(抛异常)或自定义策略返回“秒杀已结束”
用Semaphore替代CountDownLatch做实时并发限流
CountDownLatch适合“等待N个任务完成”,但不适合“限制同时只有M个任务执行”。秒杀库存扣减需精确控制并发数,应改用Semaphore:
- 初始化
Semaphore semaphore = new Semaphore(100),表示最多100人能同时尝试扣库存 - 每个请求先
semaphore.tryAcquire(),获取失败立即返回“系统繁忙” - 成功后在try-finally中确保
semaphore.release(),防止信号量泄漏
库存校验与扣减必须走Redis+Lua原子操作
数据库不是秒杀第一道防线。正确流程是:
立即学习“Java免费学习笔记(深入)”;
- 预热:活动开始前将商品库存写入Redis(如
seckill:stock:1001) - 校验+扣减用Lua脚本一次执行,避免查+改的竞态:
if redis.call("GET", KEYS[1]) >= tonumber(ARGV[1]) then
redis.call("DECRBY", KEYS[1], ARGV[1])
return 1
else
return 0
end - Lua返回1才提交订单,否则拒绝;失败请求不进线程池、不碰DB
异步化落库 + 最终一致性保障
扣减成功后,订单数据不直接INSERT主库,而是:
- 写入消息队列(如RocketMQ/Kafka),由消费者异步分批入库
- 数据库写操作加本地重试+死信队列兜底,避免单点失败导致数据丢失
- 用户端显示“下单成功,正在生成订单”,提升感知速度
- 超时未落库的订单,通过定时任务对账补偿


















