核心思路是架构跟着业务量迭代,不做过度设计
| 演进阶段 | 业务规模 / 峰值 QPS | 核心架构形态 | 核心痛点 | 关键解决技术 |
|---|---|---|---|---|
| 🟢 初创单体期 | 日活数千 / 峰值数百 | 单 Java 应用 + 单库单表 | DB 直接扛读写,超卖风险,单点故障 | 数据库乐观锁扣库存、JVM 本地缓存 |
| 🟡 成长分布式期 | 日活数万 / 峰值数千~万级 | 服务垂直拆分 + Redis 缓存 | DB 写压力大,服务耦合扩容难 | 库存预热 Redis 预扣减、Nginx 负载均衡、服务垂直拆分 |
| 🔴 爆发微服务期 | 大促场景 / 峰值十万级 | 微服务集群 + 全链路中间件 | 流量洪峰冲垮链路、缓存雪崩、服务雪崩 | 多级缓存体系、MQ 削峰、全链路限流降级、Lua 原子扣库存 |
| 🚀 成熟云原生期 | 平台级大促 / 峰值百万级 | K8s 容器化 + 云原生全家桶 | 资源利用率低、扩容不及时、容灾难度高 | HPA 弹性扩缩容、Serverless 峰值承接、异地多活、可观测体系 |
关于这个问题的底层原理和更多实战细节,我整理了一份《大厂面试手册》,包含大厂高频面试题、源码解析和性能调优案例。
关注公众号【Rain的Java大神之路】,回复“Java”即可免费领取,持续更新中。
update goods set stock = stock - 1 where id = ? and stock > 0的乐观锁思路,从数据库层面杜绝超卖DECR 原子扣减,杜绝超卖。limit_req_zone + 应用层令牌桶(Guava RateLimiter),把无效流量挡在门外。⚠️ 但这个阶段本质还是单体,流量再大一点,Redis 热 Key、应用 CPU 瓶颈马上暴露。
| 挑战 | 解法 | 关键技术点 |
|---|---|---|
| 超卖 | Redis 原子扣减 + 最终一致性校验 | Lua 脚本保证 DECR 后库存≥0 |
| 流量洪峰 | 消息队列异步削峰 | RocketMQ 事务消息,下游按数据库真实库存终裁 |
| 数据库压力 | 分库分表 + 读写分离 | ShardingSphere,按用户 ID 分片 |
| 热 Key | 多级缓存 + 本地缓存 | Redis Cluster + Caffeine,Tair 热点探测自动复制 |
| 读流量 | 商品详情多层缓存 | CDN → Nginx 本地 → Redis → DB |
🔧 这种架构能扛 十万级 QPS,但运维成本爆炸——集群、中间件、配置中心、链路追踪……人都麻了 😅。
从“堆机器”变成“面向不可变基础设施、自动伸缩”,架构升维:
KEDA 甚至能做到基于消息队列积压数动态扩容消费者 Pod,比 HPA 更实时。秒杀场景最核心的无锁化扣库存实现,性能远超分布式锁方案
-- 秒杀扣库存Lua脚本:利用Redis单线程特性,保证「库存查询+扣减」原子执行
-- KEYS[1] = 商品库存Key ARGV[1] = 扣减数量
local stock = tonumber(redis.call('get', KEYS[1]))
local deductNum = tonumber(ARGV[1])
-- 库存充足则扣减,返回1;库存不足返回0
if stock >= deductNum then
redis.call('decrby', KEYS[1], deductNum)
return 1
end
return 0
/**
* Java侧调用Lua脚本实现原子扣库存
*/
public boolean deductStock(Long skuId, Integer num) {
String stockKey = "seckill:stock:" + skuId;
// 单次网络往返完成全逻辑,避免多次Redis命令的并发问题
Long result = redisTemplate.execute(
new DefaultRedisScript<>(DEDUCT_STOCK_LUA, Long.class),
Collections.singletonList(stockKey),
num
);
return result != null && result == 1;
}
把瞬时洪峰流量转化为平稳消费,彻底保护底层数据库
// 生产者:秒杀校验通过后仅发消息,立即返回「排队中」,核心链路耗时<10ms
public void sendOrderMsg(Long userId, Long skuId) {
String orderNo = generateUniqueOrderNo(userId, skuId);
SeckillOrderDTO orderDTO = SeckillOrderDTO.builder()
.orderNo(orderNo).userId(userId).skuId(skuId).build();
rocketMQTemplate.convertAndSend("seckill-order-topic", orderDTO);
}
// 消费者:平滑拉取消息,异步落库,消费速度可控
@Component
@RocketMQMessageListener(topic = "seckill-order-topic", consumerGroup = "seckill-order-group")
public class SeckillOrderConsumer implements RocketMQListener<SeckillOrderDTO> {
@Override
public void onMessage(SeckillOrderDTO orderDTO) {
// 幂等校验:订单号唯一索引兜底,避免重复消费生成多笔订单
if (orderService.isOrderExist(orderDTO.getOrderNo())) {
return;
}
// 数据库最终扣减库存 + 生成订单
orderService.createSeckillOrder(orderDTO);
}
}
细粒度到单个商品的限流,避免单个爆品打垮整个服务
/**
* 秒杀核心接口:针对商品ID做热点参数限流
*/
@SentinelResource(value = "seckill:doSeckill", blockHandler = "seckillBlockHandler")
public Result doSeckill(Long userId, Long skuId) {
// 秒杀核心业务逻辑
return Result.success("排队中,请稍后查询结果");
}
// 初始化热点限流规则:单个商品每秒最多放行1000个请求
@PostConstruct
public void initHotParamRule() {
ParamFlowRule rule = new ParamFlowRule("seckill:doSeckill")
.setParamIdx(1) // 对第2个参数skuId做限流
.setCount(1000) // 单商品每秒QPS阈值
.setGrade(RuleConstant.FLOW_GRADE_QPS);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
| 技术难点 | 问题本质 | 落地方案 |
|---|---|---|
| 商品超卖 | 并发场景下「库存查询 + 扣减」非原子操作,导致库存扣为负数 | 1. 前置拦截:Redis Lua 脚本原子预扣库存,拦截 99% 超卖请求2. 兜底校验:数据库乐观锁 update ... where stock>0 做最终校验3. 双重保障实现零超卖 |
| 脉冲式流量洪峰打垮系统 | 秒杀流量是突发式的,几秒内几十万请求涌入,远超系统日常承载能力 | 1. 多层拦截:CDN→Nginx→网关→服务→DB,逐层过滤无效流量2. 削峰填谷:MQ 异步下单,把瞬时洪峰摊平为平稳消费3. 全链路限流:入口总限流 + 服务级限流 + 热点参数限流 |
| 缓存击穿 / 雪崩 | 热点商品缓存过期,瞬间大量请求直接穿透到数据库,导致 DB 宕机 | 1. 热点商品缓存永不过期,后台异步更新库存数值2. 互斥锁重构缓存,避免同时大量请求回源数据库3. 多级缓存体系:CDN + 本地缓存 + 分布式缓存,分散压力 |
| 服务级联雪崩 | 某一下游服务宕机,导致整条链路请求阻塞、线程耗尽,引发级联故障 | 1. 熔断降级:非核心服务(积分、短信、推荐)直接降级,保核心下单链路2. 线程池隔离:核心接口单独分配线程池,故障互不影响3. 超时控制:所有 RPC 调用设置合理超时,快速失败避免阻塞 |
| 库存数据一致性 | Redis 预扣库存与数据库最终库存不一致,出现少卖 / 超卖 | 1. 最终一致性:MQ 异步同步库存,失败重试 + 死信队列兜底2. 定时对账:定期校准 Redis 与 DB 库存,修复异常数据3. 库存回滚:下单超时 / 支付失败自动回补 Redis 库存 |
| 热点商品性能瓶颈 | 单个爆品流量集中,单 Redis 分片、单 DB 行锁成为性能瓶颈 | 1. 库存分片:单个商品库存拆分为多个 Redis Key,分散压力2. 分库分表:订单表按商品 ID 哈希拆分,分散数据库行锁压力3. JVM 本地缓存:热点商品数据放堆内缓存,减少 Redis 访问 |
| 接口幂等性失效 | 用户重复点击、网络重传、MQ 重复消费,导致生成多笔订单 | 1. 前端防控:秒杀按钮点击后置灰,防止重复提交2. 接口层:秒杀令牌机制,一个用户一个商品仅能获取一次有效令牌3. 落地兜底:订单号唯一索引,数据库层面强制幂等 |
单体 → 分布式 → 云原生
“快糙猛” → “可扩展” → “弹性自愈极致成本”
如果本文对你有帮助,欢迎关注我的公众号【Rain的Java大神之路】。
专注 Java 面试、源码、高并发实战,回复“Java”领取《大厂面试手册》,持续更新。