秒杀系统架构设计与核心问题解决方案
一、系统架构设计
1. 分层架构
整体采用分层过滤策略,请求流向如下:
客户端层 → 接入层 → 业务服务层 → 数据层
限流 缓存 队列 数据库
2. 具体组件
- 客户端:静态资源 CDN 加速、倒计时校准、防重复提交。
- 接入层:Nginx+Lua/OpenResty,负责第一层限流和缓存。
- 业务层:秒杀服务集群(无状态)、消息队列(Kafka/RocketMQ)、缓存集群(Redis Cluster)。
- 数据层:主从数据库(读写分离)、分库分表(按商品/时间)。
二、核心问题解决方案
1. 超卖问题
解决库存扣减的并发安全是重中之重,主要有三种思路。
Redis 原子操作
利用 Lua 脚本保证检查与扣减的原子性,避免多线程竞争。
-- 使用 Redis 的 DECR 原子操作扣减库存
def deduct_stock(product_id, user_id):
stock_key = f"stock:{product_id}"
lua_script = """
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock and stock > 0 then
redis.call('DECR', KEYS[1])
return 1
end
return 0
"""
result = redis.eval(lua_script, 1, stock_key)
return result == 1
数据库乐观锁
作为兜底方案,在数据库层面增加版本号控制。
UPDATE products SET stock = stock - 1, version = version + 1
WHERE id = ? AND stock > 0 AND version = ?
预扣库存
先预扣 Redis 库存,再异步同步到 DB,提升响应速度。
// 先预扣 Redis 库存,再异步同步到 DB
public boolean preDeductStock(String productId, int count) {
String key = "seckill:stock:" + productId;
Long remaining = redisTemplate.opsForValue().decrement(key, count);
if (remaining >= 0) {
// 发送 MQ 消息异步扣减数据库
sendStockDeductMessage(productId, count);
return true;
} else {
// 库存不足,回滚
redisTemplate.opsForValue().increment(key, count);
return false;
}
}
2. 高并发请求处理
流量削峰
通过消息队列缓冲突发请求,将同步调用转为异步处理。
// 使用消息队列缓冲请求
@Component
public class SeckillService {
@Autowired
private RocketMQTemplate mqTemplate;
public SeckillResult seckill(SeckillRequest request) {
// 1. 校验用户和商品状态
if (!validate(request)) {
return SeckillResult.fail("校验失败");
}
// 2. 生成唯一请求 ID
String requestId = generateRequestId(request);
// 3. 请求入队,立即返回
mqTemplate.sendOneWay("seckill-topic", MessageBuilder.withPayload(request).build());
// 4. 返回排队中状态,前端轮询结果
return SeckillResult.processing(requestId);
}
}
分层过滤
多层拦截能有效减少无效流量到达后端核心逻辑。
所有请求 → 合法性校验 → 库存校验 → 频率控制 → 实际下单
100 万 → 50 万 → 10 万 → 5 万 → 1 万
3. 系统性能优化
缓存策略
采用多级缓存配置,平衡速度与一致性。
# 多级缓存配置
缓存层级:
一级:JVM 本地缓存 (Caffeine) - 热点商品
二级:Redis 集群 - 库存信息
三级:数据库 - 最终一致性
读多写少优化
活动开始前进行缓存预热,减少冷启动压力。
// 商品信息缓存预热
@Service
public class CacheWarmUpService {
@PostConstruct
public void warmUpSeckillProducts() {
List<Product> hotProducts = loadHotProducts();
for (Product product : hotProducts) {
// 库存信息
redisTemplate.opsForValue().set(
"stock:" + product.getId(), product.getStock()
);
// 商品详情
redisTemplate.opsForValue().set(
"product:" + product.getId(), JSON.toJSONString(product)
);
// 使用布隆过滤器存储可售商品 ID
bloomFilter.add(product.getId());
}
}
}
4. 详细实现方案
秒杀流程
完整的链路涉及风险拦截、库存判断及订单落盘。
class SeckillSystem:
def process_seckill(self, user_id, product_id):
# 1. 恶意请求拦截
if not self.check_risk(user_id):
return {"code": 403, "msg": "访问过于频繁"}
# 2. 布隆过滤器快速判断
if not bloom_filter.contains(product_id):
return {"code": 404, "msg": "商品不存在"}
# 3. 内存标记(已售罄的商品直接返回)
if sold_out_flags.get(product_id):
return {"code": 400, "msg": "已售罄"}
# 4. Redis 原子扣减库存
if not self.deduct_stock_in_redis(product_id):
sold_out_flags[product_id] = True
return {"code": 400, "msg": "库存不足"}
# 5. 生成订单 ID(雪花算法)
order_id = snowflake.generate()
# 6. 订单信息入队
mq.send({
"order_id": order_id,
"user_id": user_id,
"product_id": product_id,
"time": time.time()
})
# 7. 返回排队中
return {
"code": 200, "msg": "排队中",
"order_id": order_id,
"queue_position": get_queue_position(order_id)
}
库存同步方案
后台服务消费消息,异步更新数据库并处理对账。
@Component
@Slf4j
public class StockSyncService {
// 数据库最终扣减
@Transactional
public void syncStockToDB(String productId, int count) {
try {
// 数据库扣减(带重试机制)
boolean success = productDAO.deductStock(productId, count);
if (success) {
// 更新 Redis 中的最终库存状态
redisTemplate.opsForValue().set(
"stock_final:" + productId, getDBStock(productId)
);
// 删除售罄标记
soldOutCache.remove(productId);
}
} catch (Exception e) {
log.error("库存同步失败", e);
// 记录异常,人工介入处理
alertService.sendAlert(e);
}
}
// 库存对账任务
@Scheduled(cron = "0 */5 * * * ?")
public void stockReconciliation() {
List<Product> products = productDAO.getAllSeckillProducts();
for (Product product : products) {
Integer redisStock = getRedisStock(product.getId());
Integer dbStock = product.getStock();
if (!Objects.equals(redisStock, dbStock)) {
log.warn("库存不一致:productId={}, redis={}, db={}",
product.getId(), redisStock, dbStock);
// 自动修复或报警
fixStockInconsistency(product.getId(), dbStock);
}
}
}
}
三、高可用保障
1. 限流降级策略
多维度限流规则保护系统不被瞬间流量冲垮。
# 多维度限流配置
限流规则:
用户维度:每个用户 10 次/分钟
IP 维度:每个 IP 1000 次/分钟
商品维度:每个商品 10000 次/分钟
总 QPS: 系统最大承受 50000 QPS
2. 熔断降级
当依赖服务不可用时,快速失败防止雪崩。
@RestController
@Slf4j
public class SeckillController {
@GetMapping("/seckill/{productId}")
@HystrixCommand(
fallbackMethod = "seckillFallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20")
}
)
public Response seckill(@PathVariable String productId, @RequestParam String userId) {
return seckillService.process(userId, productId);
}
// 降级方法
public Response seckillFallback(String productId, String userId) {
return Response.error("系统繁忙,请稍后重试");
}
}
四、监控与告警
1. 关键监控指标
- 系统层面:QPS、RT、错误率、CPU/内存使用率。
- 应用层面:库存扣减成功率、消息堆积量。
- 业务层面:抢购成功率、用户排队时长。
2. 监控实现
使用 Micrometer 等工具埋点记录关键行为。
@Component
public class SeckillMonitor {
private final MeterRegistry meterRegistry;
// 记录关键指标
public void recordSeckill(String productId, boolean success, long cost) {
// QPS 监控
meterRegistry.counter("seckill.requests.total").increment();
if (success) {
meterRegistry.counter("seckill.success.total").increment();
} else {
meterRegistry.counter("seckill.fail.total").increment();
}
// 耗时分布
meterRegistry.timer("seckill.process.time")
.record(cost, TimeUnit.MILLISECONDS);
// 库存变化
meterRegistry.gauge("seckill.stock." + productId, getCurrentStock(productId));
}
}
五、部署与扩展
1. 弹性扩展策略
- 水平扩展:无状态服务可快速扩容。
- 自动伸缩:基于 CPU 使用率或 QPS 自动扩缩容。
- 异地多活:重要业务支持多机房部署。
2. 压测方案
模拟真实场景验证系统极限。
压测场景:
场景 1: 库存预热,10 万用户同时抢 1 万商品
场景 2: 持续高压,5 万 QPS 持续 5 分钟
场景 3: 峰值冲击,瞬间 20 万 QPS
压测目标:
成功率:>99.9%
平均 RT: <100ms
错误率:<0.1%
六、安全考虑
- 防刷机制:验证码(峰值时降级)、设备指纹、行为分析。
- 数据安全:关键数据加密、操作日志记录、防篡改校验。
工程实践建议
在实际落地中,架构设计的核心在于动静分离与分层削峰。静态资源如图片、描述页应提前推送到 CDN,网关层需配合 Sentinel 等组件做恶意请求拦截和总流量限制。秒杀的核心逻辑——'下单扣库存'绝不放在前台实时处理,而是进入消息队列平滑流量,后端服务按能力消费,实现削峰填谷。
针对超卖和库存扣减,推荐采用 Redis 原子操作配合 Lua 脚本,确保检查与扣减的原子性。数据库层则作为最终一致性保障,通过乐观锁兜底。对于高并发场景,除了限流,还需注意热点 Key 的处理,可通过 Key 散列分散压力至多个 Redis 节点。
细节方面,前端加入计算型验证码防止机器人,下单前校验用户资格。服务端返回排队 ID,前端轮询结果,避免长时间阻塞。同时建立定时对账任务,核对 Redis、数据库库存和订单状态,确保极端情况下的数据修复能力。若 Redis 或数据库访问慢,要有熔断机制,防止服务被拖垮,必要时可降级为静态页面提示售罄。

