秒杀系统设计与实现
# 秒杀系统设计与实现
# 系统概述
秒杀系统是电商平台高并发场景下的典型应用,需要在短时间内处理大量用户请求,同时保证数据一致性和系统稳定性。本文档详细介绍秒杀系统的架构设计、核心技术方案以及性能优化策略。
# 系统架构设计
# 整体架构
秒杀系统架构
├── 前端层
│ ├── CDN加速
│ ├── 静态资源缓存
│ └── 防刷机制
├── 接入层
│ ├── 负载均衡
│ ├── 限流控制
│ └── 黑名单过滤
├── 应用层
│ ├── 秒杀活动管理
│ ├── 库存管理
│ ├── 订单处理
│ └── 支付处理
├── 缓存层
│ ├── Redis集群
│ ├── 本地缓存
│ └── 分布式锁
└── 数据层
├── 主从数据库
├── 分库分表
└── 消息队列
# 核心实体设计
# 秒杀活动实体
@Entity
@Table(name = "flash_sale_activity")
public class FlashSaleActivity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "activity_name")
private String activityName;
@Column(name = "activity_desc")
private String activityDesc;
@Column(name = "start_time")
private LocalDateTime startTime;
@Column(name = "end_time")
private LocalDateTime endTime;
@Enumerated(EnumType.STRING)
@Column(name = "status")
private ActivityStatus status;
@Column(name = "create_time")
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
// getters and setters
}
// 活动状态枚举
public enum ActivityStatus {
PENDING("待开始"),
RUNNING("进行中"),
ENDED("已结束"),
CANCELLED("已取消");
private final String description;
ActivityStatus(String description) {
this.description = description;
}
}
# 秒杀商品实体
@Entity
@Table(name = "flash_sale_item")
public class FlashSaleItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "activity_id")
private Long activityId;
@Column(name = "product_id")
private Long productId;
@Column(name = "product_name")
private String productName;
@Column(name = "original_price")
private BigDecimal originalPrice;
@Column(name = "flash_sale_price")
private BigDecimal flashSalePrice;
@Column(name = "total_stock")
private Integer totalStock;
@Column(name = "sold_count")
private Integer soldCount;
@Column(name = "limit_per_user")
private Integer limitPerUser;
@Column(name = "start_time")
private LocalDateTime startTime;
@Column(name = "end_time")
private LocalDateTime endTime;
@Enumerated(EnumType.STRING)
@Column(name = "status")
private ItemStatus status;
// getters and setters
}
// 商品状态枚举
public enum ItemStatus {
PENDING("待开始"),
RUNNING("秒杀中"),
SOLD_OUT("已售罄"),
ENDED("已结束");
private final String description;
ItemStatus(String description) {
this.description = description;
}
}
# 秒杀订单实体
@Entity
@Table(name = "flash_sale_order")
public class FlashSaleOrder {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_no")
private String orderNo;
@Column(name = "user_id")
private Long userId;
@Column(name = "activity_id")
private Long activityId;
@Column(name = "item_id")
private Long itemId;
@Column(name = "product_id")
private Long productId;
@Column(name = "quantity")
private Integer quantity;
@Column(name = "unit_price")
private BigDecimal unitPrice;
@Column(name = "total_amount")
private BigDecimal totalAmount;
@Enumerated(EnumType.STRING)
@Column(name = "status")
private OrderStatus status;
@Column(name = "create_time")
private LocalDateTime createTime;
@Column(name = "pay_time")
private LocalDateTime payTime;
@Column(name = "expire_time")
private LocalDateTime expireTime;
// getters and setters
}
// 订单状态枚举
public enum OrderStatus {
PENDING_PAYMENT("待支付"),
PAID("已支付"),
CANCELLED("已取消"),
EXPIRED("已过期");
private final String description;
OrderStatus(String description) {
this.description = description;
}
}
# 核心服务实现
# 秒杀活动服务
@Service
@Transactional
public class FlashSaleActivityService {
@Autowired
private FlashSaleActivityRepository activityRepository;
@Autowired
private FlashSaleItemRepository itemRepository;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 创建秒杀活动
*/
public FlashSaleActivity createActivity(FlashSaleActivityCreateRequest request) {
validateActivityRequest(request);
FlashSaleActivity activity = new FlashSaleActivity();
BeanUtils.copyProperties(request, activity);
activity.setStatus(ActivityStatus.PENDING);
activity.setCreateTime(LocalDateTime.now());
return activityRepository.save(activity);
}
/**
* 预热秒杀活动
*/
@Async
public void warmUpActivity(Long activityId) {
FlashSaleActivity activity = getActivityById(activityId);
List<FlashSaleItem> items = itemRepository.findByActivityId(activityId);
// 预热活动信息到Redis
String activityKey = "flash_sale:activity:" + activityId;
redisTemplate.opsForValue().set(activityKey, activity, Duration.ofHours(24));
// 预热商品信息和库存到Redis
for (FlashSaleItem item : items) {
String itemKey = "flash_sale:item:" + item.getId();
redisTemplate.opsForValue().set(itemKey, item, Duration.ofHours(24));
String stockKey = "flash_sale:stock:" + item.getId();
redisTemplate.opsForValue().set(stockKey, item.getTotalStock());
}
log.info("秒杀活动预热完成, activityId: {}", activityId);
}
/**
* 启动秒杀活动
*/
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void startPendingActivities() {
LocalDateTime now = LocalDateTime.now();
List<FlashSaleActivity> pendingActivities = activityRepository
.findByStatusAndStartTimeLessThanEqual(ActivityStatus.PENDING, now);
for (FlashSaleActivity activity : pendingActivities) {
activity.setStatus(ActivityStatus.RUNNING);
activityRepository.save(activity);
// 更新商品状态
List<FlashSaleItem> items = itemRepository.findByActivityId(activity.getId());
items.forEach(item -> {
item.setStatus(ItemStatus.RUNNING);
itemRepository.save(item);
});
log.info("秒杀活动已启动, activityId: {}", activity.getId());
}
}
/**
* 结束秒杀活动
*/
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void endRunningActivities() {
LocalDateTime now = LocalDateTime.now();
List<FlashSaleActivity> runningActivities = activityRepository
.findByStatusAndEndTimeLessThanEqual(ActivityStatus.RUNNING, now);
for (FlashSaleActivity activity : runningActivities) {
activity.setStatus(ActivityStatus.ENDED);
activityRepository.save(activity);
// 更新商品状态
List<FlashSaleItem> items = itemRepository.findByActivityId(activity.getId());
items.forEach(item -> {
if (item.getStatus() == ItemStatus.RUNNING) {
item.setStatus(ItemStatus.ENDED);
itemRepository.save(item);
}
});
log.info("秒杀活动已结束, activityId: {}", activity.getId());
}
}
}
# 秒杀服务核心实现
@Service
public class FlashSaleService {
@Autowired
private FlashSaleItemRepository itemRepository;
@Autowired
private FlashSaleOrderRepository orderRepository;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RedissonClient redissonClient;
/**
* 秒杀抢购
*/
public FlashSaleResult flashSale(FlashSaleRequest request) {
Long itemId = request.getItemId();
Long userId = request.getUserId();
Integer quantity = request.getQuantity();
// 1. 基础校验
FlashSaleItem item = validateFlashSaleRequest(itemId, userId, quantity);
// 2. 分布式锁控制并发
String lockKey = "flash_sale_lock:" + itemId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,最多等待1秒,锁定10秒
if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
// 3. 再次检查库存(双重检查)
if (!checkStock(itemId, quantity)) {
return FlashSaleResult.failure("商品已售罄");
}
// 4. 检查用户购买限制
if (!checkUserPurchaseLimit(userId, itemId, quantity)) {
return FlashSaleResult.failure("超出购买限制");
}
// 5. 扣减库存
boolean stockDecreased = decreaseStock(itemId, quantity);
if (!stockDecreased) {
return FlashSaleResult.failure("库存不足");
}
// 6. 创建订单(异步处理)
FlashSaleOrder order = createFlashSaleOrder(userId, item, quantity);
// 7. 发送订单创建消息
sendOrderCreatedMessage(order);
return FlashSaleResult.success(order.getOrderNo());
} else {
return FlashSaleResult.failure("系统繁忙,请稍后重试");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return FlashSaleResult.failure("系统异常");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 检查库存
*/
private boolean checkStock(Long itemId, Integer quantity) {
String stockKey = "flash_sale:stock:" + itemId;
String currentStockStr = (String) redisTemplate.opsForValue().get(stockKey);
if (currentStockStr == null) {
return false;
}
int currentStock = Integer.parseInt(currentStockStr);
return currentStock >= quantity;
}
/**
* 扣减库存
*/
private boolean decreaseStock(Long itemId, Integer quantity) {
String stockKey = "flash_sale:stock:" + itemId;
// 使用Lua脚本保证原子性
String script =
"local current = redis.call('get', KEYS[1]) " +
"if current and tonumber(current) >= tonumber(ARGV[1]) then " +
" return redis.call('decrby', KEYS[1], ARGV[1]) " +
"else " +
" return -1 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(stockKey),
quantity.toString()
);
return result != null && result >= 0;
}
/**
* 检查用户购买限制
*/
private boolean checkUserPurchaseLimit(Long userId, Long itemId, Integer quantity) {
String userPurchaseKey = "flash_sale:user_purchase:" + itemId + ":" + userId;
String purchasedStr = (String) redisTemplate.opsForValue().get(userPurchaseKey);
int purchased = purchasedStr != null ? Integer.parseInt(purchasedStr) : 0;
// 获取商品限购数量
FlashSaleItem item = getItemFromCache(itemId);
int limitPerUser = item.getLimitPerUser();
return purchased + quantity <= limitPerUser;
}
/**
* 创建秒杀订单
*/
private FlashSaleOrder createFlashSaleOrder(Long userId, FlashSaleItem item, Integer quantity) {
FlashSaleOrder order = new FlashSaleOrder();
order.setOrderNo(generateOrderNo());
order.setUserId(userId);
order.setActivityId(item.getActivityId());
order.setItemId(item.getId());
order.setProductId(item.getProductId());
order.setQuantity(quantity);
order.setUnitPrice(item.getFlashSalePrice());
order.setTotalAmount(item.getFlashSalePrice().multiply(BigDecimal.valueOf(quantity)));
order.setStatus(OrderStatus.PENDING_PAYMENT);
order.setCreateTime(LocalDateTime.now());
order.setExpireTime(LocalDateTime.now().plusMinutes(15)); // 15分钟支付超时
FlashSaleOrder savedOrder = orderRepository.save(order);
// 更新用户购买记录
updateUserPurchaseRecord(userId, item.getId(), quantity);
return savedOrder;
}
/**
* 更新用户购买记录
*/
private void updateUserPurchaseRecord(Long userId, Long itemId, Integer quantity) {
String userPurchaseKey = "flash_sale:user_purchase:" + itemId + ":" + userId;
redisTemplate.opsForValue().increment(userPurchaseKey, quantity);
redisTemplate.expire(userPurchaseKey, Duration.ofDays(1));
}
/**
* 生成订单号
*/
private String generateOrderNo() {
return "FS" + System.currentTimeMillis() + RandomStringUtils.randomNumeric(6);
}
}
# 限流和防刷服务
@Component
public class FlashSaleRateLimitService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 用户级别限流
*/
public boolean checkUserRateLimit(Long userId, Long itemId) {
String key = "rate_limit:user:" + userId + ":" + itemId;
return checkRateLimit(key, 5, 60); // 每分钟最多5次请求
}
/**
* IP级别限流
*/
public boolean checkIpRateLimit(String ip, Long itemId) {
String key = "rate_limit:ip:" + ip + ":" + itemId;
return checkRateLimit(key, 100, 60); // 每分钟最多100次请求
}
/**
* 全局限流
*/
public boolean checkGlobalRateLimit(Long itemId) {
String key = "rate_limit:global:" + itemId;
return checkRateLimit(key, 10000, 1); // 每秒最多10000次请求
}
/**
* 通用限流检查
*/
private boolean checkRateLimit(String key, int limit, int windowSeconds) {
String script =
"local current = redis.call('incr', KEYS[1]) " +
"if current == 1 then " +
" redis.call('expire', KEYS[1], ARGV[2]) " +
"end " +
"return current <= tonumber(ARGV[1])";
Boolean result = redisTemplate.execute(
new DefaultRedisScript<>(script, Boolean.class),
Collections.singletonList(key),
String.valueOf(limit),
String.valueOf(windowSeconds)
);
return Boolean.TRUE.equals(result);
}
/**
* 检查用户是否在黑名单中
*/
public boolean isUserBlacklisted(Long userId) {
String key = "blacklist:user:" + userId;
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
/**
* 将用户加入黑名单
*/
public void addUserToBlacklist(Long userId, Duration duration) {
String key = "blacklist:user:" + userId;
redisTemplate.opsForValue().set(key, "1", duration);
}
}
# 消息队列处理
# 订单处理消息
@Component
public class FlashSaleOrderProcessor {
@Autowired
private FlashSaleOrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
/**
* 处理订单创建消息
*/
@RabbitListener(queues = "flash.sale.order.created")
public void handleOrderCreated(FlashSaleOrderMessage message) {
try {
Long orderId = message.getOrderId();
FlashSaleOrder order = orderRepository.findById(orderId)
.orElseThrow(() -> new BusinessException("订单不存在"));
// 扣减真实库存
boolean success = inventoryService.decreaseStock(
order.getProductId(),
order.getQuantity()
);
if (!success) {
// 库存扣减失败,取消订单并回滚Redis库存
cancelOrderAndRollbackStock(order);
} else {
log.info("订单库存扣减成功, orderNo: {}", order.getOrderNo());
}
} catch (Exception e) {
log.error("处理订单创建消息失败", e);
// 可以考虑重试或者人工处理
}
}
/**
* 处理订单支付超时
*/
@RabbitListener(queues = "flash.sale.order.timeout")
public void handleOrderTimeout(FlashSaleOrderMessage message) {
try {
Long orderId = message.getOrderId();
FlashSaleOrder order = orderRepository.findById(orderId)
.orElse(null);
if (order != null && order.getStatus() == OrderStatus.PENDING_PAYMENT) {
// 取消订单
order.setStatus(OrderStatus.EXPIRED);
orderRepository.save(order);
// 回滚库存
rollbackStock(order);
log.info("订单支付超时已处理, orderNo: {}", order.getOrderNo());
}
} catch (Exception e) {
log.error("处理订单超时消息失败", e);
}
}
/**
* 回滚库存
*/
private void rollbackStock(FlashSaleOrder order) {
// 回滚Redis库存
String stockKey = "flash_sale:stock:" + order.getItemId();
redisTemplate.opsForValue().increment(stockKey, order.getQuantity());
// 回滚真实库存
inventoryService.increaseStock(order.getProductId(), order.getQuantity());
// 回滚用户购买记录
String userPurchaseKey = "flash_sale:user_purchase:" + order.getItemId() + ":" + order.getUserId();
redisTemplate.opsForValue().increment(userPurchaseKey, -order.getQuantity());
}
}
# 数据库设计
# 秒杀活动表
CREATE TABLE `flash_sale_activity` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`activity_name` varchar(100) NOT NULL COMMENT '活动名称',
`activity_desc` varchar(500) DEFAULT NULL COMMENT '活动描述',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`status` varchar(20) NOT NULL DEFAULT 'PENDING' COMMENT '状态',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_status_time` (`status`, `start_time`, `end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀活动表';
# 秒杀商品表
CREATE TABLE `flash_sale_item` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`activity_id` bigint NOT NULL COMMENT '活动ID',
`product_id` bigint NOT NULL COMMENT '商品ID',
`product_name` varchar(200) NOT NULL COMMENT '商品名称',
`original_price` decimal(10,2) NOT NULL COMMENT '原价',
`flash_sale_price` decimal(10,2) NOT NULL COMMENT '秒杀价',
`total_stock` int NOT NULL COMMENT '总库存',
`sold_count` int NOT NULL DEFAULT '0' COMMENT '已售数量',
`limit_per_user` int NOT NULL DEFAULT '1' COMMENT '每人限购数量',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`status` varchar(20) NOT NULL DEFAULT 'PENDING' COMMENT '状态',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_activity_id` (`activity_id`),
KEY `idx_product_id` (`product_id`),
KEY `idx_status_time` (`status`, `start_time`, `end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀商品表';
# 秒杀订单表
CREATE TABLE `flash_sale_order` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_no` varchar(50) NOT NULL COMMENT '订单号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`activity_id` bigint NOT NULL COMMENT '活动ID',
`item_id` bigint NOT NULL COMMENT '商品ID',
`product_id` bigint NOT NULL COMMENT '商品ID',
`quantity` int NOT NULL COMMENT '购买数量',
`unit_price` decimal(10,2) NOT NULL COMMENT '单价',
`total_amount` decimal(10,2) NOT NULL COMMENT '总金额',
`status` varchar(20) NOT NULL DEFAULT 'PENDING_PAYMENT' COMMENT '状态',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`pay_time` datetime DEFAULT NULL COMMENT '支付时间',
`expire_time` datetime NOT NULL COMMENT '过期时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_activity_item` (`activity_id`, `item_id`),
KEY `idx_status_expire` (`status`, `expire_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀订单表';
# 性能优化策略
# 1. 多级缓存
@Component
public class FlashSaleCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 本地缓存
private final Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(5))
.build();
/**
* 多级缓存获取
*/
public <T> T get(String key, Class<T> type, Supplier<T> dataLoader) {
// 1. 先查本地缓存
T value = (T) localCache.getIfPresent(key);
if (value != null) {
return value;
}
// 2. 查Redis缓存
value = (T) redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value);
return value;
}
// 3. 查数据库
value = dataLoader.get();
if (value != null) {
redisTemplate.opsForValue().set(key, value, Duration.ofHours(1));
localCache.put(key, value);
}
return value;
}
}
# 2. 异步处理
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("flashSaleExecutor")
public Executor flashSaleExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(1000);
executor.setThreadNamePrefix("FlashSale-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
# 3. 数据库优化
-- 读写分离配置
-- 主库用于写操作,从库用于读操作
-- 分库分表策略
-- 按用户ID分表
CREATE TABLE `flash_sale_order_0` LIKE `flash_sale_order`;
CREATE TABLE `flash_sale_order_1` LIKE `flash_sale_order`;
-- ... 更多分表
-- 索引优化
CREATE INDEX idx_user_activity_item ON flash_sale_order(user_id, activity_id, item_id);
CREATE INDEX idx_create_time ON flash_sale_order(create_time);
# 监控和告警
# 关键指标监控
@Component
public class FlashSaleMonitorService {
@Autowired
private MeterRegistry meterRegistry;
/**
* 记录秒杀请求量
*/
public void recordFlashSaleRequest(Long itemId, String result) {
Counter.builder("flash_sale_requests")
.tag("item_id", String.valueOf(itemId))
.tag("result", result)
.register(meterRegistry)
.increment();
}
/**
* 记录响应时间
*/
public void recordResponseTime(Long itemId, long duration) {
Timer.builder("flash_sale_response_time")
.tag("item_id", String.valueOf(itemId))
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
}
/**
* 记录库存变化
*/
public void recordStockChange(Long itemId, int stock) {
Gauge.builder("flash_sale_stock")
.tag("item_id", String.valueOf(itemId))
.register(meterRegistry, stock, Number::doubleValue);
}
}
# 总结
秒杀系统的核心设计要点:
- 高并发处理:多级缓存、分布式锁、异步处理
- 库存控制:Redis原子操作、数据库最终一致性
- 限流防刷:多维度限流、黑名单机制
- 性能优化:缓存预热、读写分离、分库分表
- 监控告警:实时监控关键指标、异常告警
- 容错处理:降级策略、熔断机制、补偿事务
通过以上设计,可以构建一个高性能、高可用的秒杀系统,能够应对大流量冲击并保证数据一致性。