本人详解
作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》
公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题
中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯
转载说明:务必注明来源(注明:作者:王文峰哦)
【如何保证订单支付与库存扣减的一致性】五年经验Java开发者业务场景面试题集【如何避免热点Key失效】【微服务接口幂等性】
学习教程(传送门)
1、掌握 JAVA入门到进阶知识(持续写作中……)
2、学会Oracle数据库用法(创作中……)
3、手把手教你vbs脚本制作(完善中……)
4、牛逼哄哄的 IDEA编程利器(编写中……)
5、吐血整理的 面试技巧(更新中……)
一、秒杀系统设计:如何支撑10万QPS的双11抢购?
题目描述
假设你是某电商平台的Java架构师,需要在双11期间设计一个秒杀系统,支撑10万+ QPS,同时满足:
不超卖(库存1000件,最多卖出1000单)
高可用(系统不能崩溃)
低延迟(用户点击后1秒内响应结果)
请描述完整设计方案。
考察点
分布式系统设计、高并发流量削峰、缓存与数据库一致性、防刷限流、事务控制。
参考答案
流量分层拦截(核心防刷)
前端限流:按钮置灰(未开始)、倒计时(防止重复提交)、验证码(滑动验证/短信验证,降低机器请求)。
网关层拦截:使用Nginx做IP限流(如limit_req_zone限制单IP每秒10次),过滤异常UA(如无Referer、爬虫特征)。
服务层校验:用户登录态校验(JWT令牌)、黑名单过滤(历史恶意用户)、参数校验(商品ID是否合法)。
缓存预热与库存扣减
库存预加载:活动开始前,将数据库库存同步到Redis(set stock:1001 1000),避免直接操作数据库。
Redis原子扣减:用户下单时,通过Lua脚本原子扣减库存(避免并发问题):
if redis.call('get', KEYS[1]) > 0 then
redis.call('decr', KEYS[1])
return 1 -- 扣减成功
else
return 0 – 库存不足
end
防超卖兜底:即使Redis扣减成功,数据库层面仍需用「乐观锁」二次校验(版本号或库存字段):
UPDATE goods SET stock = stock - 1, version = version + 1
WHERE id = #{goodsId} AND version = #{oldVersion} AND stock > 0
异步下单与消息队列削峰
请求异步化:秒杀请求先进入RocketMQ/Kafka队列(削峰填谷),后端消费者按稳定速率(如2000单/秒)处理。
订单生成:消费者从队列拉取请求,校验用户、库存(再次查Redis),通过后生成订单(写入MySQL),并发送短信通知。
高可用保障
集群部署:网关、服务、Redis均集群化(如Redis Cluster),避免单点故障。
熔断降级:Sentinel监控核心服务(如下单接口),若错误率超50%则熔断,返回「稍后再试」。
数据库保护:主库写压力大时,启用从库读(库存查询走从库),但扣减必须主库(避免主从延迟导致超卖)。
关键优化点
库存分段:将1000件库存拆成10个段(每段100件),用户抢购时随机分配段ID,减少热点Key竞争(Redis的stock:1001:0~stock:1001:9)。
本地缓存:网关层用Caffeine缓存少量库存(如每个商品缓存10个),过滤无效请求(用户拿到缓存库存为0时直接拒绝)。