秒杀系统设计
🔥 大规模秒杀架构
系统架构设计
Q1: 设计一个能支持千万级用户同时抢购的秒杀系统?如何处理高并发和库存一致性?
难度: ⭐⭐⭐⭐⭐
答案: 秒杀系统需要在极短时间内处理海量请求,核心挑战是高并发、库存一致性和系统稳定性。
1. 整体架构设计:
mermaid
graph TB
A[用户浏览器] --> B[CDN内容分发网络]
B --> C[负载均衡器 Nginx/HAProxy]
C --> D[API网关 Kong/Zuul]
D --> E[秒杀服务集群]
D --> F[用户服务]
D --> G[订单服务]
E --> H[Redis集群 - 库存缓存]
E --> I[消息队列 Kafka]
I --> J[订单处理服务]
I --> K[库存扣减服务]
I --> L[支付服务]
J --> M[(MySQL主库)]
K --> M
L --> N[(MySQL从库)]
E --> O[预热系统]
E --> P[限流熔断]2. 核心技术方案:
前端优化:
javascript
// 前端防重复点击和请求优化
class SeckillClient {
constructor() {
this.isSubmitting = false;
this.token = null;
this.retryCount = 0;
this.maxRetries = 3;
}
async initSeckill(productId) {
try {
// 获取秒杀token
this.token = await this.getSeckillToken(productId);
// 启动心跳检测
this.startHeartbeat();
// 开启实时库存监控
this.watchStock(productId);
} catch (error) {
console.error('秒杀初始化失败:', error);
}
}
async submitSeckill(productId) {
// 防重复提交
if (this.isSubmitting) {
return { success: false, message: '请勿重复点击' };
}
this.isSubmitting = true;
const startTime = Date.now();
try {
// 客户端限流
if (!this.canSubmit()) {
throw new Error('提交过于频繁,请稍后再试');
}
const result = await this.callSeckillAPI(productId);
// 请求统计
this.recordMetric('seckill_submit', Date.now() - startTime);
return result;
} catch (error) {
// 智能重试机制
if (this.retryCount < this.maxRetries && this.isRetryableError(error)) {
this.retryCount++;
await this.delay(Math.pow(2, this.retryCount) * 100); // 指数退避
return this.submitSeckill(productId);
}
throw error;
} finally {
this.isSubmitting = false;
}
}
canSubmit() {
const now = Date.now();
const lastSubmit = localStorage.getItem('lastSeckillSubmit');
// 1秒内只能提交一次
return !lastSubmit || (now - lastSubmit) > 1000;
}
async watchStock(productId) {
// WebSocket实时库存监控
const ws = new WebSocket(`ws://api.seckill.com/stock/${productId}`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.updateStockDisplay(data.stock);
// 库存售罄自动禁用按钮
if (data.stock <= 0) {
this.disableSeckillButton();
}
};
}
}3. 后端核心服务设计:
秒杀服务核心逻辑:
java
@Service
public class SeckillService {
@Autowired private RedisTemplate redisTemplate;
@Autowired private KafkaTemplate kafkaTemplate;
@Autowired private BloomFilter bloomFilter;
private static final String STOCK_KEY = "seckill:stock:";
private static final String USER_KEY = "seckill:user:";
public SeckillResult doSeckill(Long productId, Long userId) {
// 1. 参数校验和预检查
if (!preCheck(productId, userId)) {
return SeckillResult.fail("参数校验失败");
}
// 2. 用户重复购买检查
if (isDuplicatePurchase(productId, userId)) {
return SeckillResult.fail("您已经参与过该商品的秒杀");
}
// 3. 库存预检查(布隆过滤器)
if (!bloomFilter.mightContain(productId.toString())) {
return SeckillResult.fail("商品不存在或已售罄");
}
// 4. 分布式锁 + 库存扣减
String lockKey = "seckill:lock:" + productId;
return distributedLock.execute(lockKey, 100, TimeUnit.MILLISECONDS, () -> {
// 双重检查库存
Long currentStock = getCurrentStock(productId);
if (currentStock <= 0) {
return SeckillResult.fail("商品已售罄");
}
// 原子性库存扣减
Long remainStock = decrementStock(productId);
if (remainStock < 0) {
// 回滚库存
incrementStock(productId);
return SeckillResult.fail("商品已售罄");
}
// 标记用户已参与
markUserParticipated(productId, userId);
// 异步创建订单
createOrderAsync(productId, userId, remainStock);
return SeckillResult.success("秒杀成功", remainStock);
});
}
private Long decrementStock(Long productId) {
String stockKey = STOCK_KEY + productId;
// Lua脚本保证原子性
String luaScript =
"local stock = redis.call('GET', KEYS[1]) " +
"if stock and tonumber(stock) > 0 then " +
" return redis.call('DECR', KEYS[1]) " +
"else " +
" return -1 " +
"end";
return redisTemplate.execute(
RedisScript.of(luaScript, Long.class),
Collections.singletonList(stockKey)
);
}
private void createOrderAsync(Long productId, Long userId, Long remainStock) {
SeckillOrderEvent event = SeckillOrderEvent.builder()
.productId(productId)
.userId(userId)
.timestamp(System.currentTimeMillis())
.remainStock(remainStock)
.build();
// 发送到消息队列异步处理
kafkaTemplate.send("seckill-order", event);
// 发送库存变化通知
kafkaTemplate.send("stock-change", new StockChangeEvent(productId, remainStock));
}
}4. 库存管理策略:
库存预热和分层扣减:
java
@Component
public class StockManager {
// 多级库存管理
@Autowired private RedisCluster redisCluster;
@Autowired private DatabaseService databaseService;
public void initSeckillStock(Long productId, Long totalStock) {
// 1. 数据库中设置总库存
databaseService.setTotalStock(productId, totalStock);
// 2. Redis集群分片存储
int shardCount = 10; // 分10个分片
Long stockPerShard = totalStock / shardCount;
for (int i = 0; i < shardCount; i++) {
String shardKey = "seckill:stock:" + productId + ":shard:" + i;
redisCluster.set(shardKey, stockPerShard.toString());
}
// 3. 布隆过滤器标记商品存在
bloomFilter.put(productId.toString());
// 4. 预热本地缓存
localCache.put("stock:" + productId, totalStock);
}
public Long decrementStockOptimized(Long productId) {
// 随机选择分片,减少热点
int shardIndex = ThreadLocalRandom.current().nextInt(10);
String shardKey = "seckill:stock:" + productId + ":shard:" + shardIndex;
// Lua脚本原子操作
String luaScript =
"local shard_stock = redis.call('GET', KEYS[1]) " +
"if shard_stock and tonumber(shard_stock) > 0 then " +
" local remain = redis.call('DECR', KEYS[1]) " +
" -- 更新总库存 " +
" redis.call('DECR', KEYS[2]) " +
" return remain " +
"else " +
" return -1 " +
"end";
List<String> keys = Arrays.asList(shardKey, "seckill:stock:" + productId);
return redisCluster.eval(luaScript, keys, Collections.emptyList());
}
// 库存回补机制
@Scheduled(fixedRate = 1000) // 每秒执行一次
public void stockReplenishment() {
// 检查支付超时的订单
List<TimeoutOrder> timeoutOrders = orderService.getTimeoutOrders();
for (TimeoutOrder order : timeoutOrders) {
// 回补库存
incrementStock(order.getProductId());
// 取消订单
orderService.cancelOrder(order.getOrderId());
}
}
}5. 消息队列异步处理:
订单处理服务:
java
@KafkaListener(topics = "seckill-order", groupId = "order-group")
public class OrderProcessor {
@Autowired private OrderService orderService;
@Autowired private UserService userService;
@Autowired private RedisTemplate redisTemplate;
public void processOrder(SeckillOrderEvent event) {
try {
// 1. 用户信息验证
User user = userService.getUser(event.getUserId());
if (user == null || !user.isActive()) {
handleOrderFailure(event, "用户信息异常");
return;
}
// 2. 商品信息验证
Product product = productService.getProduct(event.getProductId());
if (product == null || !product.isOnSale()) {
handleOrderFailure(event, "商品信息异常");
return;
}
// 3. 创建预订单
Order order = Order.builder()
.productId(event.getProductId())
.userId(event.getUserId())
.quantity(1)
.price(product.getSeckillPrice())
.status(OrderStatus.PENDING_PAYMENT)
.createTime(new Date(event.getTimestamp()))
.expireTime(new Date(event.getTimestamp() + 15 * 60 * 1000)) // 15分钟支付
.build();
orderService.createOrder(order);
// 4. 发送支付通知
PaymentEvent paymentEvent = PaymentEvent.builder()
.orderId(order.getId())
.userId(event.getUserId())
.amount(product.getSeckillPrice())
.expireTime(order.getExpireTime())
.build();
kafkaTemplate.send("payment-notify", paymentEvent);
// 5. 更新用户状态缓存
updateUserOrderStatus(event.getUserId(), event.getProductId(), order.getId());
} catch (Exception e) {
log.error("订单处理失败", e);
handleOrderFailure(event, e.getMessage());
}
}
private void handleOrderFailure(SeckillOrderEvent event, String reason) {
// 1. 回补库存
stockManager.incrementStock(event.getProductId());
// 2. 清除用户参与标记
redisTemplate.delete("seckill:user:" + event.getProductId() + ":" + event.getUserId());
// 3. 记录失败日志
log.warn("订单创建失败: productId={}, userId={}, reason={}",
event.getProductId(), event.getUserId(), reason);
}
}6. 限流和熔断机制:
多层限流策略:
java
@Component
public class SeckillRateLimiter {
// 1. 接入层限流 - Nginx限流
/*
nginx.conf:
http {
limit_req_zone $binary_remote_addr zone=seckill:10m rate=10r/s;
server {
location /api/seckill {
limit_req zone=seckill burst=20 nodelay;
proxy_pass http://backend;
}
}
}
*/
// 2. 应用层限流 - 基于用户
@Autowired private RedisTemplate redisTemplate;
public boolean checkUserRateLimit(Long userId) {
String key = "rate_limit:user:" + userId;
String luaScript =
"local key = KEYS[1] " +
"local limit = tonumber(ARGV[1]) " +
"local window = tonumber(ARGV[2]) " +
"local current = redis.call('INCR', key) " +
"if current == 1 then " +
" redis.call('EXPIRE', key, window) " +
"end " +
"return current <= limit";
Boolean allowed = redisTemplate.execute(
RedisScript.of(luaScript, Boolean.class),
Collections.singletonList(key),
"3", "60" // 1分钟内最多3次请求
);
return allowed != null && allowed;
}
// 3. 服务层限流 - 令牌桶
private final RateLimiter serviceLimiter = RateLimiter.create(1000.0); // 每秒1000个令牌
public boolean tryAcquire() {
return serviceLimiter.tryAcquire(100, TimeUnit.MILLISECONDS);
}
// 4. 熔断器
@Autowired private CircuitBreaker circuitBreaker;
public SeckillResult doSeckillWithCircuitBreaker(Long productId, Long userId) {
return circuitBreaker.executeSupplier(() -> {
return seckillService.doSeckill(productId, userId);
});
}
}7. 监控和告警系统:
java
@Component
public class SeckillMonitor {
private final MeterRegistry meterRegistry;
private final Counter seckillRequestCounter;
private final Counter seckillSuccessCounter;
private final Timer seckillLatencyTimer;
public SeckillMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.seckillRequestCounter = Counter.builder("seckill.requests.total")
.description("Total seckill requests")
.register(meterRegistry);
this.seckillSuccessCounter = Counter.builder("seckill.requests.success")
.description("Successful seckill requests")
.register(meterRegistry);
this.seckillLatencyTimer = Timer.builder("seckill.requests.latency")
.description("Seckill request latency")
.register(meterRegistry);
}
public void recordSeckillMetrics(SeckillResult result, long latency) {
seckillRequestCounter.increment();
if (result.isSuccess()) {
seckillSuccessCounter.increment();
}
seckillLatencyTimer.record(latency, TimeUnit.MILLISECONDS);
// 实时告警检查
checkAlerts();
}
private void checkAlerts() {
// 检查成功率
double successRate = seckillSuccessCounter.count() / seckillRequestCounter.count();
if (successRate < 0.8) {
alertService.sendAlert("秒杀成功率低于80%: " + successRate);
}
// 检查平均响应时间
double avgLatency = seckillLatencyTimer.mean(TimeUnit.MILLISECONDS);
if (avgLatency > 500) {
alertService.sendAlert("秒杀平均响应时间超过500ms: " + avgLatency);
}
}
}面试要点:
- 理解秒杀系统的核心挑战:高并发、库存一致性、系统稳定性
- 掌握多层防护:前端限流 + 后端限流 + 分布式锁 + 异步处理
- 了解库存管理的分片策略和原子操作
- 掌握消息队列在订单处理中的作用和可靠性保证
这个秒杀系统设计涵盖了从前端到后端的完整方案,包括库存管理、限流熔断、异步处理等核心技术点,适合高级工程师和架构师岗位的技术评估。
