本文基于淘宝秒杀系统实战经验,结合SpringBoot技术栈,深度解析高并发秒杀系统的架构设计与实现方案。
"王工!秒杀活动开始3秒,服务器全挂了!"凌晨2点接到这个电话时,我后背瞬间湿透。那是2019年双11前夕,我们团队负责的某品牌手机秒杀活动,因并发设计缺陷导致系统雪崩,直接损失预估10亿。正是这次惨痛教训,让我彻底掌握了高并发系统的核心要义。
真实业务场景:某品牌新款手机发售,10万台手机,100万人同时抢购。系统要在100毫秒内完成:
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.7</version>
</dependency>
</dependencies>
/**
* 分布式限流控制器 - 基于Redis+Lua实现精准限流
*/
@Component
public class DistributedRateLimiter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String RATE_LIMIT_SCRIPT =
"local key = KEYS[1]\n" +
"local limit = tonumber(ARGV[1])\n" +
"local window = tonumber(ARGV[2])\n" +
"local current = redis.call('get', key)\n" +
"if current and tonumber(current) > limit then\n" +
" return 0\n" +
"end\n" +
"current = redis.call('incr', key)\n" +
"if tonumber(current) == 1 then\n" +
" redis.call('expire', key, window)\n" +
"end\n" +
"return 1";
/**
* 滑动窗口限流
*/
public boolean tryAcquire(String key, int limit, int windowSeconds) {
List<String> keys = Collections.singletonList("rate_limit:" + key);
Object result = redisTemplate.execute(
new DefaultRedisScript<>(RATE_LIMIT_SCRIPT, Long.class),
keys,
String.valueOf(limit),
String.valueOf(windowSeconds)
);
return result != null && (Long) result == 1;
}
}
/**
* 秒杀库存服务 - 优化版库存扣减
*/
@Service
public class SeckillInventoryService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 优化后的LUA脚本 - 单次原子操作
private static final String STOCK_DEDUCTION_SCRIPT =
"local stockKey = KEYS[1]\n" +
"local stockHistoryKey = KEYS[2]\n" +
"local userId = ARGV[1]\n" +
"local quantity = tonumber(ARGV[2])\n" +
"local limit = tonumber(ARGV[3])\n" +
"\n" +
"-- 检查用户是否已经购买过\n" +
"local userPurchased = redis.call('hget', stockHistoryKey, userId)\n" +
"if userPurchased and tonumber(userPurchased) >= limit then\n" +
" return -1 -- 超过限购数量\n" +
"end\n" +
"\n" +
"-- 原子扣减库存\n" +
"local stock = tonumber(redis.call('get', stockKey))\n" +
"if not stock or stock < quantity then\n" +
" return 0 -- 库存不足\n" +
"end\n" +
"\n" +
"-- 执行扣减\n" +
"local result = redis.call('decrby', stockKey, quantity)\n" +
"if result >= 0 then\n" +
" -- 记录用户购买历史\n" +
" redis.call('hincrby', stockHistoryKey, userId, quantity)\n" +
" redis.call('expire', stockHistoryKey, 3600) -- 1小时过期\n" +
" return 1 -- 成功\n" +
"else\n" +
" -- 回滚库存\n" +
" redis.call('incrby', stockKey, quantity)\n" +
" return 0 -- 失败\n" +
"end";
/**
* 安全的库存扣减方法
*/
public DeductResult deductStockWithLimit(Long itemId, Long userId, Integer quantity, Integer limit) {
String stockKey = "seckill:stock:" + itemId;
String historyKey = "seckill:history:" + itemId;
List<String> keys = Arrays.asList(stockKey, historyKey);
Object result = redisTemplate.execute(
new DefaultRedisScript<>(STOCK_DEDUCTION_SCRIPT, Long.class),
keys,
userId.toString(),
quantity.toString(),
limit.toString()
);
return parseDeductResult(result);
}
/**
* 库存预热
*/
public void preheatInventory(SeckillItem item) {
String stockKey = "seckill:stock:" + item.getId();
String itemKey = "seckill:item:" + item.getId();
// 设置库存
redisTemplate.opsForValue().set(stockKey, item.getStock().toString());
// 设置商品信息
Map<String, String> itemInfo = new HashMap<>();
itemInfo.put("name", item.getName());
itemInfo.put("price", item.getPrice().toString());
itemInfo.put("startTime", String.valueOf(item.getStartTime().getTime()));
itemInfo.put("endTime", String.valueOf(item.getEndTime().getTime()));
redisTemplate.opsForHash().putAll(itemKey, itemInfo);
redisTemplate.expire(itemKey, Duration.ofHours(2));
}
}
/**
* 订单服务 - 基于消息队列的异步处理
*/
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private OrderMapper orderMapper;
/**
* 创建订单 - 异步处理
*/
public OrderResult createOrderAsync(SeckillOrderRequest request) {
// 1. 快速验证
if (!validateRequest(request)) {
return OrderResult.fail("请求参数异常");
}
// 2. 发送订单创建消息
String messageId = sendOrderMessage(request);
// 3. 立即返回处理中状态
return OrderResult.processing(messageId);
}
/**
* 订单消息消费者
*/
@RabbitListener(queues = "order.create.queue")
public void handleOrderMessage(OrderMessage message) {
try {
// 1. 重复检查
if (orderMapper.existsByMessageId(message.getMessageId())) {
return;
}
// 2. 创建订单
Order order = buildOrder(message);
orderMapper.insert(order);
// 3. 发送订单创建成功事件
sendOrderCreatedEvent(order);
} catch (Exception e) {
// 4. 失败重试
handleOrderFailure(message, e);
}
}
/**
* 订单状态查询
*/
public OrderStatus queryOrderStatus(String orderId) {
// 多级缓存查询
OrderStatus status = getFromLocalCache(orderId);
if (status == null) {
status = getFromRedis(orderId);
}
if (status == null) {
status = getFromDatabase(orderId);
}
return status;
}
}
/**
* 多级缓存服务
*/
@Service
public class MultiLevelCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 本地缓存
private final Cache<String, Object> localCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.SECONDS) // 短时间缓存
.build();
/**
* 获取商品信息 - 多级缓存策略
*/
public SeckillItem getItemWithCache(Long itemId) {
String cacheKey = "item:" + itemId;
// 1. 查询本地缓存
SeckillItem item = (SeckillItem) localCache.getIfPresent(cacheKey);
if (item != null) {
return item;
}
// 2. 查询Redis缓存
item = (SeckillItem) redisTemplate.opsForValue().get(cacheKey);
if (item != null) {
// 回填本地缓存
localCache.put(cacheKey, item);
return item;
}
// 3. 查询数据库
item = getItemFromDB(itemId);
if (item != null) {
// 异步更新缓存
asyncUpdateCache(cacheKey, item);
}
return item;
}
/**
* 缓存预热
*/
@Async
public void preheatCache(List<Long> itemIds) {
itemIds.parallelStream().forEach(itemId -> {
SeckillItem item = getItemFromDB(itemId);
if (item != null) {
String cacheKey = "item:" + itemId;
redisTemplate.opsForValue().set(cacheKey, item, Duration.ofMinutes(30));
}
});
}
}
/**
* 秒杀API控制器
*/
@RestController
@RequestMapping("/api/seckill")
@Slf4j
public class SeckillController {
@Autowired
private SeckillService seckillService;
@Autowired
private DistributedRateLimiter rateLimiter;
/**
* 秒杀入口
*/
@PostMapping("/{itemId}")
public ResponseEntity<SeckillResponse> seckill(
@PathVariable Long itemId,
@RequestHeader("userId") Long userId,
@RequestBody SeckillRequest request) {
// 1. 基础限流
if (!rateLimiter.tryAcquire("seckill:" + itemId, 1000, 1)) {
return ResponseEntity.status(429).body(SeckillResponse.fail("系统繁忙,请稍后重试"));
}
// 2. 用户级限流
if (!rateLimiter.tryAcquire("user:" + userId, 5, 60)) {
return ResponseEntity.status(429).body(SeckillResponse.fail("操作过于频繁"));
}
try {
// 3. 执行秒杀
SeckillResult result = seckillService.executeSeckill(itemId, userId, request);
return ResponseEntity.ok(SeckillResponse.success(result));
} catch (SeckillException e) {
log.warn("秒杀业务异常: userId={}, itemId={}", userId, itemId, e);
return ResponseEntity.badRequest().body(SeckillResponse.fail(e.getMessage()));
} catch (Exception e) {
log.error("秒杀系统异常: userId={}, itemId={}", userId, itemId, e);
return ResponseEntity.status(500).body(SeckillResponse.fail("系统异常"));
}
}
/**
* 查询秒杀结果
*/
@GetMapping("/result/{itemId}")
public SeckillResult getResult(@PathVariable Long itemId,
@RequestHeader("userId") Long userId) {
return seckillService.getSeckillResult(itemId, userId);
}
}
/**
* 秒杀核心服务
*/
@Service
@Slf4j
public class SeckillService {
@Autowired
private SeckillInventoryService inventoryService;
@Autowired
private OrderService orderService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 执行秒杀流程
*/
@Transactional(rollbackFor = Exception.class)
public SeckillResult executeSeckill(Long itemId, Long userId, SeckillRequest request) {
// 1. 验证秒杀活动状态
validateSeckillStatus(itemId);
// 2. 库存扣减
DeductResult deductResult = inventoryService.deductStockWithLimit(
itemId, userId, request.getQuantity(), request.getLimit());
if (!deductResult.isSuccess()) {
return SeckillResult.fail(deductResult.getMessage());
}
// 3. 创建订单
OrderResult orderResult = orderService.createOrderAsync(
buildOrderRequest(itemId, userId, request));
// 4. 记录秒杀结果
cacheSeckillResult(itemId, userId, orderResult);
return SeckillResult.success(orderResult.getOrderId());
}
/**
* 验证秒杀活动状态
*/
private void validateSeckillStatus(Long itemId) {
SeckillItem item = getSeckillItem(itemId);
long now = System.currentTimeMillis();
long startTime = item.getStartTime().getTime();
long endTime = item.getEndTime().getTime();
if (now < startTime) {
throw new SeckillException("秒杀尚未开始");
}
if (now > endTime) {
throw new SeckillException("秒杀已结束");
}
}
}
/**
* 分布式锁服务 - 防止重复秒杀
*/
@Component
public class DistributedLockService {
@Autowired
private RedissonClient redisson;
/**
* 执行带锁的秒杀操作
*/
public SeckillResult executeWithLock(String lockKey, long waitTime,
long leaseTime, Supplier<SeckillResult> supplier) {
RLock lock = redisson.getLock(lockKey);
try {
// 尝试获取锁
if (lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS)) {
try {
return supplier.get();
} finally {
lock.unlock();
}
} else {
return SeckillResult.fail("系统繁忙,请重试");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return SeckillResult.fail("系统异常");
}
}
}
/**
* 熔断降级服务
*/
@Component
public class CircuitBreakerService {
private final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("seckillService");
/**
* 带熔断保护的秒杀操作
*/
public SeckillResult executeWithCircuitBreaker(Supplier<SeckillResult> supplier) {
return CircuitBreaker.decorateSupplier(circuitBreaker, () -> {
try {
return supplier.get();
} catch (Exception e) {
log.error("秒杀服务异常", e);
return SeckillResult.fail("服务暂时不可用");
}
}).get();
}
}
# application.yml
server:
port: 8080
tomcat:
max-threads: 1000
min-spare-threads: 100
max-connections: 10000
spring:
redis:
host: localhost
port: 6379
lettuce:
pool:
max-active: 100
max-wait: 1000ms
rabbitmq:
host: localhost
port: 5672
listener:
simple:
prefetch: 10 # 控制并发消费
# 秒杀配置
seckill:
rate-limit:
global: 10000 # 全局QPS限制
user: 5 # 用户级QPS限制
inventory:
preheat-time: 30 # 库存预热时间(分钟)
/**
* 秒杀监控服务
*/
@Component
@Slf4j
public class SeckillMetricsService {
private final MeterRegistry meterRegistry;
// 关键指标
private final Counter successCounter;
private final Counter failureCounter;
private final Timer seckillTimer;
public SeckillMetricsService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.successCounter = Counter.builder("seckill.success")
.description("秒杀成功次数")
.register(meterRegistry);
this.failureCounter = Counter.builder("seckill.failure")
.description("秒杀失败次数")
.register(meterRegistry);
this.seckillTimer = Timer.builder("seckill.duration")
.description("秒杀耗时")
.register(meterRegistry);
}
public void recordSeckillSuccess(long duration) {
successCounter.increment();
seckillTimer.record(duration, TimeUnit.MILLISECONDS);
}
public void recordSeckillFailure(String reason) {
failureCounter.increment();
log.warn("秒杀失败: {}", reason);
}
}
<!-- 秒杀压测计划 -->
<ThreadGroup>
<numThreads>1000</numThreads>
<rampUp>10</rampUp>
<loopCount>100</loopCount>
</ThreadGroup>
<HTTPRequest>
<method>POST</method>
<path>/api/seckill/123</path>
<header name="userId" value="${__Random(1,100000)}"/>
</HTTPRequest>
经过优化后的系统可实现:
技术标签:#高并发 #秒杀系统 #SpringBoot #Redis #分布式系统 #性能优化
通过本文的完整实现方案,您可以构建出能够应对百万级并发的秒杀系统。记住,高并发系统不仅是技术的堆砌,更是对业务深刻理解后的架构艺术。