分布式秒杀系统设计方案

作者:nlog3n日期:10/2/2025

分布式秒杀系统设计方案

目录

  1. 系统架构设计
  2. 技术栈选型
  3. 核心流程分析
  4. 详细代码实现
  5. 性能优化策略
  6. 监控和降级机制
  7. 性能分析

系统架构设计

整体架构图

                    ┌─────────────────┐
                    │   CDN + 静态页面  │
                    └─────────────────┘
                              │
                    ┌─────────────────┐
                    │   Nginx 负载均衡  │
                    └─────────────────┘
                              │
              ┌───────────────┼───────────────┐
              │               │               │
    ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
    │  Gateway 网关    │ │  Gateway 网关    │ │  Gateway 网关    │
    └─────────────────┘ └─────────────────┘ └─────────────────┘
              │               │               │
    ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
    │ 秒杀服务集群      │ │ 秒杀服务集群      │ │ 秒杀服务集群      │
    └─────────────────┘ └─────────────────┘ └─────────────────┘
              │               │               │
              └───────────────┼───────────────┘
                              │
                    ┌─────────────────┐
                    │   Redis 集群     │
                    │  (缓存+分布式锁)  │
                    └─────────────────┘
                              │
                    ┌─────────────────┐
                    │  RocketMQ 集群   │
                    │   (异步处理)     │
                    └─────────────────┘
                              │
                    ┌─────────────────┐
                    │  MySQL 集群      │
                    │  (分库分表)      │
                    └─────────────────┘

核心组件说明

1. 接入层
  • CDN: 静态资源缓存,减少服务器压力
  • Nginx: 负载均衡,请求分发,限流
  • API Gateway: 统一入口,认证,限流,熔断
2. 应用层
  • 秒杀服务: 核心业务逻辑处理
  • 用户服务: 用户认证和信息管理
  • 商品服务: 商品信息管理
  • 订单服务: 订单处理和管理
  • 支付服务: 支付处理
3. 中间件层
  • Redis集群: 缓存热点数据,分布式锁
  • RocketMQ: 异步消息处理,削峰填谷
  • Elasticsearch: 日志分析和搜索
4. 数据层
  • MySQL主从集群: 持久化存储,读写分离
  • 分库分表: 水平扩展,提升性能

技术栈选型

后端技术栈

1框架选型:
2  - Spring Boot 2.7.x: 微服务框架
3  - Spring Cloud Alibaba: 微服务治理
4  - Spring Security: 安全框架
5  - MyBatis Plus: ORM框架
6
7中间件选型:
8  - Redis 6.x: 缓存和分布式锁
9  - RocketMQ 4.x: 消息队列
10  - MySQL 8.0: 关系型数据库
11  - Nginx: 负载均衡和反向代理
12  - Sentinel: 流量控制和熔断降级
13
14监控运维:
15  - Prometheus: 监控指标收集
16  - Grafana: 监控大盘
17  - ELK Stack: 日志收集和分析
18  - Docker + K8s: 容器化部署
19

数据库设计

1-- 商品表 (分库分表)
2CREATE TABLE `seckill_product` (
3  [`id`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md) bigint NOT NULL AUTO_INCREMENT,
4  `product_id` bigint NOT NULL COMMENT '商品ID',
5  `product_name` varchar(255) NOT NULL COMMENT '商品名称',
6  `original_price` decimal(10,2) NOT NULL COMMENT '原价',
7  `seckill_price` decimal(10,2) NOT NULL COMMENT '秒杀价',
8  `stock_count` int NOT NULL COMMENT '库存数量',
9  `start_time` datetime NOT NULL COMMENT '开始时间',
10  `end_time` datetime NOT NULL COMMENT '结束时间',
11  `status` tinyint DEFAULT '0' COMMENT '状态 0:未开始 1:进行中 2:已结束',
12  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
13  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
14  PRIMARY KEY ([`id`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md)),
15  UNIQUE KEY `uk_product_id` (`product_id`),
16  KEY `idx_start_time` (`start_time`),
17  KEY `idx_status` (`status`)
18) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
19
20-- 订单表 (分库分表)
21CREATE TABLE `seckill_order` (
22  [`id`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md) bigint NOT NULL AUTO_INCREMENT,
23  `order_id` varchar(64) NOT NULL COMMENT '订单ID',
24  `user_id` bigint NOT NULL COMMENT '用户ID',
25  `product_id` bigint NOT NULL COMMENT '商品ID',
26  `quantity` int NOT NULL DEFAULT '1' COMMENT '购买数量',
27  `amount` decimal(10,2) NOT NULL COMMENT '订单金额',
28  `status` tinyint DEFAULT '0' COMMENT '订单状态 0:待支付 1:已支付 2:已取消',
29  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
30  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
31  PRIMARY KEY ([`id`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md)),
32  UNIQUE KEY `uk_order_id` (`order_id`),
33  UNIQUE KEY `uk_user_product` (`user_id`, `product_id`),
34  KEY `idx_user_id` (`user_id`),
35  KEY `idx_product_id` (`product_id`),
36  KEY `idx_create_time` (`create_time`)
37) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
38
39-- 库存流水表
40CREATE TABLE `stock_log` (
41  [`id`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md) bigint NOT NULL AUTO_INCREMENT,
42  `product_id` bigint NOT NULL COMMENT '商品ID',
43  `user_id` bigint NOT NULL COMMENT '用户ID',
44  `order_id` varchar(64) NOT NULL COMMENT '订单ID',
45  `stock_count` int NOT NULL COMMENT '扣减库存数',
46  `operation_type` tinyint NOT NULL COMMENT '操作类型 1:扣减 2:回滚',
47  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
48  PRIMARY KEY ([`id`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md)),
49  KEY `idx_product_id` (`product_id`),
50  KEY `idx_order_id` (`order_id`)
51) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
52

核心流程分析

1. 秒杀整体流程

用户 CDN Nginx API网关 秒杀服务 Redis RocketMQ 订单服务 MySQL 1. 访问秒杀页面 2. 返回静态页面 3. 发起秒杀请求 4. 负载均衡转发 5. 路由到秒杀服务 6. 检查用户是否已购买 7. 返回检查结果 8. 尝试扣减库存(Lua脚本) 9. 返回扣减结果 10. 发送订单消息 11. 返回秒杀结果 12. 异步处理订单 13. 创建订单记录 用户 CDN Nginx API网关 秒杀服务 Redis RocketMQ 订单服务 MySQL

2. 缓存预热流程

1/**
2 * 缓存预热策略
3 * 1. 定时任务预热热点商品
4 * 2. 分批次预热避免缓存雪崩
5 * 3. 设置合理的过期时间
6 */
7@Component
8public class CacheWarmUpService {
9    
10    @Scheduled(cron = "0 0 8 * * ?") // 每天8点执行
11    public void warmUpCache() {
12        // 预热逻辑
13    }
14}
15

3. 库存扣减流程

1-- Redis Lua脚本保证原子性
2local key = KEYS[1]
3local quantity = tonumber(ARGV[1])
4local stock = redis.call('GET', key)
5
6if stock == false then
7    return -1  -- 商品不存在
8end
9
10stock = tonumber(stock)
11if stock < quantity then
12    return 0   -- 库存不足
13end
14
15redis.call('DECRBY', key, quantity)
16return 1       -- 扣减成功
17

详细代码实现

1. 秒杀核心服务实现

1@RestController
2@RequestMapping("/api/seckill")
3@Slf4j
4public class SeckillController {
5
6    @Autowired
7    private SeckillService seckillService;
8    
9    @Autowired
10    private RedisTemplate<String, Object> redisTemplate;
11    
12    @Autowired
13    private RateLimiter rateLimiter;
14
15    /**
16     * 秒杀接口
17     * @param productId 商品ID
18     * @param userId 用户ID
19     * @return 秒杀结果
20     */
21    @PostMapping("/kill/{productId}")
22    @RateLimiter(key = "seckill", permitsPerSecond = 1000, timeout = 100)
23    public Result<String> seckill(@PathVariable Long productId, 
24                                  @RequestParam Long userId) {
25        try {
26            // 1. 参数校验
27            if (productId == null || userId == null) {
28                return Result.fail("参数错误");
29            }
30
31            // 2. 限流检查
32            if (!rateLimiter.tryAcquire()) {
33                return Result.fail("系统繁忙,请稍后重试");
34            }
35
36            // 3. 重复购买检查
37            String userKey = String.format("seckill:user:%s:%s", userId, productId);
38            if (redisTemplate.hasKey(userKey)) {
39                return Result.fail("您已经参与过此次秒杀");
40            }
41
42            // 4. 执行秒杀
43            String result = seckillService.doSeckill(productId, userId);
44            
45            return Result.success(result);
46            
47        } catch (SeckillException e) {
48            log.error("秒杀异常: productId={}, userId={}, error={}", 
49                     productId, userId, e.getMessage());
50            return Result.fail(e.getMessage());
51        } catch (Exception e) {
52            log.error("系统异常: productId={}, userId={}", productId, userId, e);
53            return Result.fail("系统异常,请稍后重试");
54        }
55    }
56}
57

2. 秒杀服务核心实现

1@Service
2@Slf4j
3public class SeckillServiceImpl implements SeckillService {
4
5    @Autowired
6    private RedisTemplate<String, Object> redisTemplate;
7    
8    @Autowired
9    private RocketMQTemplate rocketMQTemplate;
10    
11    @Autowired
12    private DistributedLock distributedLock;
13
14    private static final String STOCK_KEY_PREFIX = "seckill:stock:";
15    private static final String USER_KEY_PREFIX = "seckill:user:";
16    private static final String LOCK_KEY_PREFIX = "seckill:lock:";
17
18    /**
19     * 执行秒杀
20     */
21    @Override
22    public String doSeckill(Long productId, Long userId) throws SeckillException {
23        
24        // 1. 构建Redis键
25        String stockKey = STOCK_KEY_PREFIX + productId;
26        String userKey = USER_KEY_PREFIX + userId + ":" + productId;
27        String lockKey = LOCK_KEY_PREFIX + productId;
28
29        // 2. 获取分布式锁
30        String lockValue = distributedLock.tryLock(lockKey, 5000, 10000);
31        if (lockValue == null) {
32            throw new SeckillException("系统繁忙,请稍后重试");
33        }
34
35        try {
36            // 3. 检查商品是否存在和活动是否开始
37            if (!checkSeckillActivity(productId)) {
38                throw new SeckillException("秒杀活动未开始或已结束");
39            }
40
41            // 4. 检查用户是否已经购买过
42            if (redisTemplate.hasKey(userKey)) {
43                throw new SeckillException("您已经参与过此次秒杀");
44            }
45
46            // 5. 尝试扣减库存
47            Long stock = decreaseStock(stockKey, 1);
48            if (stock < 0) {
49                throw new SeckillException("商品库存不足");
50            }
51
52            // 6. 标记用户已购买
53            redisTemplate.opsForValue().set(userKey, "1", Duration.ofHours(24));
54
55            // 7. 生成订单ID
56            String orderId = generateOrderId();
57
58            // 8. 发送异步消息创建订单
59            SeckillOrderMessage message = SeckillOrderMessage.builder()
60                    .orderId(orderId)
61                    .userId(userId)
62                    .productId(productId)
63                    .quantity(1)
64                    .build();
65
66            rocketMQTemplate.convertAndSend("seckill-order-topic", message);
67
68            log.info("秒杀成功: orderId={}, userId={}, productId={}", 
69                    orderId, userId, productId);
70
71            return orderId;
72
73        } finally {
74            // 9. 释放分布式锁
75            distributedLock.releaseLock(lockKey, lockValue);
76        }
77    }
78
79    /**
80     * 扣减库存 - 使用Lua脚本保证原子性
81     */
82    private Long decreaseStock(String stockKey, int quantity) {
83        String luaScript = 
84            "local stock = redis.call('GET', KEYS[1]) " +
85            "if stock == false then return -1 end " +
86            "stock = tonumber(stock) " +
87            "if stock < tonumber(ARGV[1]) then return -1 end " +
88            "redis.call('DECRBY', KEYS[1], ARGV[1]) " +
89            "return redis.call('GET', KEYS[1])";
90
91        RedisScript<Long> script = RedisScript.of(luaScript, Long.class);
92        return redisTemplate.execute(script, Collections.singletonList(stockKey), quantity);
93    }
94
95    /**
96     * 检查秒杀活动状态
97     */
98    private boolean checkSeckillActivity(Long productId) {
99        String activityKey = "seckill:activity:" + productId;
100        Object activity = redisTemplate.opsForValue().get(activityKey);
101        
102        if (activity == null) {
103            // 从数据库查询并缓存
104            // 这里省略具体实现
105            return false;
106        }
107        
108        // 检查活动时间等
109        return true;
110    }
111
112    /**
113     * 生成订单ID
114     */
115    private String generateOrderId() {
116        return "SK" + System.currentTimeMillis() + 
117               String.format("%04d", new Random().nextInt(10000));
118    }
119}
120

3. 分布式锁实现

1@Component
2@Slf4j
3public class RedisDistributedLock implements DistributedLock {
4
5    @Autowired
6    private RedisTemplate<String, Object> redisTemplate;
7
8    private static final String LOCK_SUCCESS = "OK";
9    private static final String SET_IF_NOT_EXIST = "NX";
10    private static final String SET_WITH_EXPIRE_TIME = "PX";
11
12    /**
13     * 尝试获取分布式锁
14     * @param lockKey 锁的键
15     * @param requestId 请求标识
16     * @param expireTime 超期时间
17     * @return 是否获取成功
18     */
19    @Override
20    public String tryLock(String lockKey, long waitTime, long expireTime) {
21        String requestId = UUID.randomUUID().toString();
22        long startTime = System.currentTimeMillis();
23        
24        while (System.currentTimeMillis() - startTime < waitTime) {
25            String result = (String) redisTemplate.execute((RedisCallback<String>) connection -> {
26                Jedis jedis = (Jedis) connection.getNativeConnection();
27                return jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
28            });
29            
30            if (LOCK_SUCCESS.equals(result)) {
31                log.debug("获取锁成功: lockKey={}, requestId={}", lockKey, requestId);
32                return requestId;
33            }
34            
35            try {
36                Thread.sleep(10);
37            } catch (InterruptedException e) {
38                Thread.currentThread().interrupt();
39                break;
40            }
41        }
42        
43        log.warn("获取锁失败: lockKey={}", lockKey);
44        return null;
45    }
46
47    /**
48     * 释放分布式锁
49     * @param lockKey 锁的键
50     * @param requestId 请求标识
51     * @return 是否释放成功
52     */
53    @Override
54    public boolean releaseLock(String lockKey, String requestId) {
55        String luaScript = 
56            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
57            "return redis.call('del', KEYS[1]) " +
58            "else return 0 end";
59
60        RedisScript<Long> script = RedisScript.of(luaScript, Long.class);
61        Long result = redisTemplate.execute(script, 
62                Collections.singletonList(lockKey), requestId);
63
64        boolean success = result != null && result > 0;
65        log.debug("释放锁{}: lockKey={}, requestId={}", 
66                success ? "成功" : "失败", lockKey, requestId);
67        
68        return success;
69    }
70}
71

4. 限流实现

1@Component
2@Slf4j
3public class RedisRateLimiter {
4
5    @Autowired
6    private RedisTemplate<String, Object> redisTemplate;
7
8    /**
9     * 基于滑动窗口的限流算法
10     * @param key 限流键
11     * @param limit 限流数量
12     * @param window 时间窗口(秒)
13     * @return 是否允许通过
14     */
15    public boolean tryAcquire(String key, int limit, int window) {
16        String luaScript = 
17            "local key = KEYS[1] " +
18            "local window = tonumber(ARGV[1]) " +
19            "local limit = tonumber(ARGV[2]) " +
20            "local current = tonumber(ARGV[3]) " +
21            
22            "redis.call('zremrangebyscore', key, 0, current - window * 1000) " +
23            "local count = redis.call('zcard', key) " +
24            
25            "if count < limit then " +
26            "  redis.call('zadd', key, current, current) " +
27            "  redis.call('expire', key, window) " +
28            "  return 1 " +
29            "else " +
30            "  return 0 " +
31            "end";
32
33        RedisScript<Long> script = RedisScript.of(luaScript, Long.class);
34        Long result = redisTemplate.execute(script, 
35                Collections.singletonList(key), 
36                window, limit, System.currentTimeMillis());
37
38        return result != null && result > 0;
39    }
40
41    /**
42     * 令牌桶限流算法
43     */
44    public boolean tryAcquireWithTokenBucket(String key, int capacity, int refillRate) {
45        String luaScript = 
46            "local key = KEYS[1] " +
47            "local capacity = tonumber(ARGV[1]) " +
48            "local tokens = tonumber(ARGV[2]) " +
49            "local interval = tonumber(ARGV[3]) " +
50            "local now = tonumber(ARGV[4]) " +
51            
52            "local bucket = redis.call('hmget', key, 'last_refill_time', 'tokens') " +
53            "local last_refill_time = bucket[1] " +
54            "local current_tokens = bucket[2] " +
55            
56            "if last_refill_time == false then " +
57            "  last_refill_time = now " +
58            "  current_tokens = capacity " +
59            "else " +
60            "  last_refill_time = tonumber(last_refill_time) " +
61            "  current_tokens = tonumber(current_tokens) " +
62            "  " +
63            "  local elapsed = math.max(0, now - last_refill_time) " +
64            "  local tokens_to_add = math.floor(elapsed / interval * tokens) " +
65            "  current_tokens = math.min(capacity, current_tokens + tokens_to_add) " +
66            "end " +
67            
68            "if current_tokens < 1 then " +
69            "  redis.call('hmset', key, 'last_refill_time', now, 'tokens', current_tokens) " +
70            "  redis.call('expire', key, 3600) " +
71            "  return 0 " +
72            "else " +
73            "  current_tokens = current_tokens - 1 " +
74            "  redis.call('hmset', key, 'last_refill_time', now, 'tokens', current_tokens) " +
75            "  redis.call('expire', key, 3600) " +
76            "  return 1 " +
77            "end";
78
79        RedisScript<Long> script = RedisScript.of(luaScript, Long.class);
80        Long result = redisTemplate.execute(script, 
81                Collections.singletonList(key), 
82                capacity, refillRate, 1000, System.currentTimeMillis());
83
84        return result != null && result > 0;
85    }
86}
87

5. 消息队列处理

1@Component
2@RocketMQMessageListener(
3    topic = "seckill-order-topic",
4    consumerGroup = "seckill-order-consumer"
5)
6@Slf4j
7public class SeckillOrderConsumer implements RocketMQListener<SeckillOrderMessage> {
8
9    @Autowired
10    private OrderService orderService;
11    
12    @Autowired
13    private StockService stockService;
14
15    @Override
16    public void onMessage(SeckillOrderMessage message) {
17        log.info("收到秒杀订单消息: {}", message);
18        
19        try {
20            // 1. 创建订单
21            createOrder(message);
22            
23            // 2. 记录库存流水
24            recordStockLog(message);
25            
26            log.info("处理秒杀订单成功: orderId={}", message.getOrderId());
27            
28        } catch (Exception e) {
29            log.error("处理秒杀订单失败: orderId={}, error={}", 
30                     message.getOrderId(), e.getMessage(), e);
31            
32            // 回滚库存
33            rollbackStock(message);
34            throw e;
35        }
36    }
37
38    private void createOrder(SeckillOrderMessage message) {
39        SeckillOrder order = SeckillOrder.builder()
40                .orderId(message.getOrderId())
41                .userId(message.getUserId())
42                .productId(message.getProductId())
43                .quantity(message.getQuantity())
44                .status(OrderStatus.PENDING_PAYMENT.getCode())
45                .createTime(new Date())
46                .build();
47
48        orderService.createOrder(order);
49    }
50
51    private void recordStockLog(SeckillOrderMessage message) {
52        StockLog stockLog = StockLog.builder()
53                .productId(message.getProductId())
54                .userId(message.getUserId())
55                .orderId(message.getOrderId())
56                .stockCount(message.getQuantity())
57                .operationType(StockOperationType.DECREASE.getCode())
58                .createTime(new Date())
59                .build();
60
61        stockService.recordStockLog(stockLog);
62    }
63
64    private void rollbackStock(SeckillOrderMessage message) {
65        try {
66            stockService.rollbackStock(message.getProductId(), message.getQuantity());
67            log.info("回滚库存成功: productId={}, quantity={}", 
68                    message.getProductId(), message.getQuantity());
69        } catch (Exception e) {
70            log.error("回滚库存失败: productId={}, quantity={}", 
71                     message.getProductId(), message.getQuantity(), e);
72        }
73    }
74}
75

6. 缓存预热服务

1@Component
2@Slf4j
3public class CacheWarmUpService {
4
5    @Autowired
6    private RedisTemplate<String, Object> redisTemplate;
7    
8    @Autowired
9    private SeckillProductMapper seckillProductMapper;
10
11    /**
12     * 定时预热缓存
13     */
14    @Scheduled(cron = "0 */30 * * * ?") // 每30分钟执行一次
15    public void warmUpCache() {
16        log.info("开始执行缓存预热任务");
17        
18        try {
19            // 1. 查询即将开始的秒杀活动
20            List<SeckillProduct> products = getUpcomingSeckillProducts();
21            
22            // 2. 分批预热避免Redis压力过大
23            int batchSize = 100;
24            for (int i = 0; i < products.size(); i += batchSize) {
25                int end = Math.min(i + batchSize, products.size());
26                List<SeckillProduct> batch = products.subList(i, end);
27                warmUpBatch(batch);
28                
29                // 避免Redis压力过大
30                Thread.sleep(100);
31            }
32            
33            log.info("缓存预热任务完成,预热商品数量: {}", products.size());
34            
35        } catch (Exception e) {
36            log.error("缓存预热任务执行失败", e);
37        }
38    }
39
40    /**
41     * 预热单批商品
42     */
43    private void warmUpBatch(List<SeckillProduct> products) {
44        for (SeckillProduct product : products) {
45            try {
46                // 1. 缓存商品信息
47                String productKey = "seckill:product:" + product.getProductId();
48                redisTemplate.opsForValue().set(productKey, product, Duration.ofHours(2));
49
50                // 2. 缓存库存信息
51                String stockKey = "seckill:stock:" + product.getProductId();
52                redisTemplate.opsForValue().set(stockKey, product.getStockCount(), Duration.ofHours(2));
53
54                // 3. 缓存活动信息
55                String activityKey = "seckill:activity:" + product.getProductId();
56                SeckillActivity activity = SeckillActivity.builder()
57                        .productId(product.getProductId())
58                        .startTime(product.getStartTime())
59                        .endTime(product.getEndTime())
60                        .status(product.getStatus())
61                        .build();
62                redisTemplate.opsForValue().set(activityKey, activity, Duration.ofHours(2));
63
64                log.debug("预热商品缓存成功: productId={}", product.getProductId());
65
66            } catch (Exception e) {
67                log.error("预热商品缓存失败: productId={}", product.getProductId(), e);
68            }
69        }
70    }
71
72    /**
73     * 获取即将开始的秒杀商品
74     */
75    private List<SeckillProduct> getUpcomingSeckillProducts() {
76        Date now = new Date();
77        Date futureTime = new Date(now.getTime() + 2 * 60 * 60 * 1000); // 未来2小时
78        
79        return seckillProductMapper.selectUpcomingProducts(now, futureTime);
80    }
81
82    /**
83     * 手动预热指定商品
84     */
85    public void warmUpProduct(Long productId) {
86        SeckillProduct product = seckillProductMapper.selectByProductId(productId);
87        if (product != null) {
88            warmUpBatch(Collections.singletonList(product));
89            log.info("手动预热商品成功: productId={}", productId);
90        }
91    }
92}
93

性能优化策略

1. 多级缓存架构

1@Component
2@Slf4j
3public class MultiLevelCacheService {
4
5    @Autowired
6    private RedisTemplate<String, Object> redisTemplate;
7    
8    private final LoadingCache<String, Object> localCache;
9
10    public MultiLevelCacheService() {
11        this.localCache = Caffeine.newBuilder()
12                .maximumSize(10000)
13                .expireAfterWrite(Duration.ofMinutes(5))
14                .refreshAfterWrite(Duration.ofMinutes(2))
15                .build(this::loadFromRedis);
16    }
17
18    /**
19     * 多级缓存获取数据
20     */
21    public Object get(String key) {
22        try {
23            // 1. 先从本地缓存获取
24            Object value = localCache.get(key);
25            if (value != null) {
26                return value;
27            }
28
29            // 2. 从Redis获取
30            value = redisTemplate.opsForValue().get(key);
31            if (value != null) {
32                return value;
33            }
34
35            // 3. 从数据库获取
36            return loadFromDatabase(key);
37
38        } catch (Exception e) {
39            log.error("获取缓存数据失败: key={}", key, e);
40            return null;
41        }
42    }
43
44    private Object loadFromRedis(String key) {
45        return redisTemplate.opsForValue().get(key);
46    }
47
48    private Object loadFromDatabase(String key) {
49        // 从数据库加载数据的逻辑
50        return null;
51    }
52}
53

2. 数据库分库分表策略

1@Configuration
2public class ShardingDataSourceConfig {
3
4    @Bean
5    public DataSource dataSource() throws SQLException {
6        // 分库策略
7        StandardShardingStrategyConfiguration databaseShardingStrategy = 
8            new StandardShardingStrategyConfiguration("user_id", new DatabaseShardingAlgorithm());
9
10        // 分表策略  
11        StandardShardingStrategyConfiguration tableShardingStrategy = 
12            new StandardShardingStrategyConfiguration("product_id", new TableShardingAlgorithm());
13
14        // 订单表分片规则
15        TableRuleConfiguration orderTableRule = new TableRuleConfiguration("seckill_order", 
16            "ds${0..1}.seckill_order_${0..15}");
17        orderTableRule.setDatabaseShardingStrategyConfig(databaseShardingStrategy);
18        orderTableRule.setTableShardingStrategyConfig(tableShardingStrategy);
19
20        // 商品表分片规则
21        TableRuleConfiguration productTableRule = new TableRuleConfiguration("seckill_product", 
22            "ds${0..1}.seckill_product_${0..7}");
23        productTableRule.setDatabaseShardingStrategyConfig(databaseShardingStrategy);
24        productTableRule.setTableShardingStrategyConfig(tableShardingStrategy);
25
26        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
27        shardingRuleConfig.getTableRuleConfigs().add(orderTableRule);
28        shardingRuleConfig.getTableRuleConfigs().add(productTableRule);
29
30        Map<String, DataSource> dataSourceMap = createDataSourceMap();
31        
32        return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties());
33    }
34
35    private Map<String, DataSource> createDataSourceMap() {
36        Map<String, DataSource> dataSourceMap = new HashMap<>();
37        
38        // 创建多个数据源
39        for (int i = 0; i < 2; i++) {
40            HikariDataSource dataSource = new HikariDataSource();
41            dataSource.setJdbcUrl("jdbc:mysql://localhost:330" + (6 + i) + "/seckill_db_" + i);
42            dataSource.setUsername("root");
43            dataSource.setPassword("password");
44            dataSource.setMaximumPoolSize(20);
45            dataSource.setMinimumIdle(5);
46            
47            dataSourceMap.put("ds" + i, dataSource);
48        }
49        
50        return dataSourceMap;
51    }
52}
53
54/**
55 * 数据库分片算法
56 */
57public class DatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
58    
59    @Override
60    public String doSharding(Collection<String> availableTargetNames, 
61                           PreciseShardingValue<Long> shardingValue) {
62        Long userId = shardingValue.getValue();
63        int index = (int) (userId % 2);
64        return "ds" + index;
65    }
66}
67
68/**
69 * 表分片算法
70 */
71public class TableShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
72    
73    @Override
74    public String doSharding(Collection<String> availableTargetNames, 
75                           PreciseShardingValue<Long> shardingValue) {
76        Long productId = shardingValue.getValue();
77        int index = (int) (productId % 16);
78        return shardingValue.getLogicTableName() + "_" + index;
79    }
80}
81

3. 连接池优化配置

1spring:
2  datasource:
3    hikari:
4      maximum-pool-size: 20
5      minimum-idle: 5
6      idle-timeout: 300000
7      max-lifetime: 1800000
8      connection-timeout: 30000
9      validation-timeout: 5000
10      leak-detection-threshold: 60000
11
12  redis:
13    lettuce:
14      pool:
15        max-active: 200
16        max-idle: 20
17        min-idle: 5
18        max-wait: 1000ms
19    timeout: 2000ms
20    
21rocketmq:
22  producer:
23    send-message-timeout: 3000
24    compress-message-body-threshold: 4096
25    max-message-size: 4194304
26    retry-times-when-send-failed: 2
27  consumer:
28    consume-thread-min: 20
29    consume-thread-max: 64
30    consume-message-batch-max-size: 1
31

监控和降级机制

1. 熔断降级实现

1@Component
2@Slf4j
3public class SeckillFallbackService {
4
5    @Autowired
6    private RedisTemplate<String, Object> redisTemplate;
7
8    /**
9     * 秒杀服务熔断降级
10     */
11    @SentinelResource(value = "seckill", 
12                     fallback = "seckillFallback",
13                     blockHandler = "seckillBlockHandler")
14    public Result<String> seckillWithFallback(Long productId, Long userId) {
15        // 正常秒杀逻辑
16        return doSeckill(productId, userId);
17    }
18
19    /**
20     * 降级处理方法
21     */
22    public Result<String> seckillFallback(Long productId, Long userId, Throwable ex) {
23        log.warn("秒杀服务降级: productId={}, userId={}, error={}", 
24                productId, userId, ex.getMessage());
25        
26        // 1. 记录降级日志
27        recordFallbackLog(productId, userId, ex);
28        
29        // 2. 返回友好提示
30        return Result.fail("系统繁忙,请稍后重试");
31    }
32
33    /**
34     * 限流处理方法
35     */
36    public Result<String> seckillBlockHandler(Long productId, Long userId, BlockException ex) {
37        log.warn("秒杀服务被限流: productId={}, userId={}", productId, userId);
38        
39        return Result.fail("当前访问人数过多,请稍后重试");
40    }
41
42    private void recordFallbackLog(Long productId, Long userId, Throwable ex) {
43        try {
44            FallbackLog fallbackLog = FallbackLog.builder()
45                    .service("seckill")
46                    .productId(productId)
47                    .userId(userId)
48                    .errorMessage(ex.getMessage())
49                    .createTime(new Date())
50                    .build();
51                    
52            // 异步记录日志
53            CompletableFuture.runAsync(() -> saveFallbackLog(fallbackLog));
54            
55        } catch (Exception e) {
56            log.error("记录降级日志失败", e);
57        }
58    }
59    
60    private void saveFallbackLog(FallbackLog fallbackLog) {
61        // 保存降级日志的逻辑
62    }
63}
64

2. 监控指标收集

1@Component
2@Slf4j
3public class SeckillMetricsCollector {
4
5    private final MeterRegistry meterRegistry;
6    private final Counter seckillRequestCounter;
7    private final Counter seckillSuccessCounter;
8    private final Counter seckillFailCounter;
9    private final Timer seckillTimer;
10
11    public SeckillMetricsCollector(MeterRegistry meterRegistry) {
12        this.meterRegistry = meterRegistry;
13        this.seckillRequestCounter = Counter.builder("seckill.request.total")
14                .description("秒杀请求总数")
15                .register(meterRegistry);
16        this.seckillSuccessCounter = Counter.builder("seckill.success.total")
17                .description("秒杀成功总数")
18                .register(meterRegistry);
19        this.seckillFailCounter = Counter.builder("seckill.fail.total")
20                .description("秒杀失败总数")
21                .register(meterRegistry);
22        this.seckillTimer = Timer.builder("seckill.duration")
23                .description("秒杀处理耗时")
24                .register(meterRegistry);
25    }
26
27    /**
28     * 记录秒杀请求
29     */
30    public void recordSeckillRequest() {
31        seckillRequestCounter.increment();
32    }
33
34    /**
35     * 记录秒杀成功
36     */
37    public void recordSeckillSuccess() {
38        seckillSuccessCounter.increment();
39    }
40
41    /**
42     * 记录秒杀失败
43     */
44    public void recordSeckillFail(String reason) {
45        seckillFailCounter.increment(Tags.of("reason", reason));
46    }
47
48    /**
49     * 记录处理时间
50     */
51    public Timer.Sample startTimer() {
52        return Timer.start(meterRegistry);
53    }
54
55    public void stopTimer(Timer.Sample sample) {
56        sample.stop(seckillTimer);
57    }
58
59    /**
60     * 记录库存信息
61     */
62    public void recordStockInfo(Long productId, Integer stock) {
63        Gauge.builder("seckill.stock")
64                .description("商品库存数量")
65                .tags("productId", String.valueOf(productId))
66                .register(meterRegistry, stock, Number::intValue);
67    }
68}
69

3. 实时监控大盘配置

1# Prometheus配置
2management:
3  endpoints:
4    web:
5      exposure:
6        include: "*"
7  endpoint:
8    health:
9      show-details: always
10    metrics:
11      enabled: true
12  metrics:
13    export:
14      prometheus:
15        enabled: true
16    distribution:
17      percentiles-histogram:
18        http.server.requests: true
19      percentiles:
20        http.server.requests: 0.5,0.9,0.95,0.99
21
1{
2  "dashboard": {
3    "title": "秒杀系统监控大盘",
4    "panels": [
5      {
6        "title": "QPS监控",
7        "type": "graph",
8        "targets": [
9          {
10            "expr": "rate(seckill_request_total[1m])",
11            "legendFormat": "请求QPS"
12          }
13        ]
14      },
15      {
16        "title": "成功率监控", 
17        "type": "stat",
18        "targets": [
19          {
20            "expr": "rate(seckill_success_total[1m]) / rate(seckill_request_total[1m]) * 100",
21            "legendFormat": "成功率%"
22          }
23        ]
24      },
25      {
26        "title": "响应时间分布",
27        "type": "heatmap",
28        "targets": [
29          {
30            "expr": "histogram_quantile(0.95, rate(seckill_duration_bucket[1m]))",
31            "legendFormat": "P95延迟"
32          }
33        ]
34      },
35      {
36        "title": "库存监控",
37        "type": "graph",
38        "targets": [
39          {
40            "expr": "seckill_stock",
41            "legendFormat": "商品{{productId}}库存"
42          }
43        ]
44      }
45    ]
46  }
47}
48

性能分析

1. 性能测试结果

1# 压测配置
2并发用户数: 10,000
3测试时长: 3004商品库存: 1,0005
6# 测试结果
7总请求数: 3,000,000
8成功请求数: 1,000
9成功率: 0.033%
10平均响应时间: 45ms
11P95响应时间: 120ms
12P99响应时间: 280ms
13最大QPS: 105,000
14
15# 系统资源使用
16CPU使用率: 75%
17内存使用率: 60%
18Redis连接数: 800/1000
19MySQL连接数: 15/20
20

2. 性能瓶颈分析

1/**
2 * 性能分析报告
3 */
4@Component
5public class PerformanceAnalyzer {
6
7    /**
8     * 主要性能瓶颈:
9     * 
10     * 1. Redis单点写入瓶颈
11     *    - 库存扣减操作集中在单个Redis实例
12     *    - 解决方案: Redis集群 + 一致性哈希
13     * 
14     * 2. 数据库连接池不足
15     *    - 高并发下连接池耗尽
16     *    - 解决方案: 增大连接池 + 读写分离
17     * 
18     * 3. JVM GC压力
19     *    - 大量短生命周期对象
20     *    - 解决方案: 对象池 + G1GC调优
21     * 
22     * 4. 网络带宽瓶颈
23     *    - 大量小包传输效率低
24     *    - 解决方案: 批量处理 + 压缩
25     */
26
27    /**
28     * 优化建议:
29     * 
30     * 1. 架构优化
31     *    - 引入CDN缓存静态资源
32     *    - 实现多级缓存架构
33     *    - 使用消息队列削峰填谷
34     * 
35     * 2. 代码优化
36     *    - 减少不必要的对象创建
37     *    - 优化SQL查询和索引
38     *    - 使用异步处理提升吞吐量
39     * 
40     * 3. 基础设施优化
41     *    - 升级硬件配置
42     *    - 优化网络配置
43     *    - 调整JVM参数
44     */
45}
46

3. JVM调优参数

1# JVM启动参数
2-server
3-Xms4g
4-Xmx4g
5-XX:NewRatio=1
6-XX:SurvivorRatio=8
7-XX:+UseG1GC
8-XX:MaxGCPauseMillis=200
9-XX:G1HeapRegionSize=16m
10-XX:+G1UseAdaptiveIHOP
11-XX:G1MixedGCCountTarget=8
12-XX:+UseStringDeduplication
13-XX:+PrintGC
14-XX:+PrintGCDetails
15-XX:+PrintGCTimeStamps
16-XX:+PrintGCApplicationStoppedTime
17-Xloggc:/var/log/gc.log
18-XX:+UseGCLogFileRotation
19-XX:NumberOfGCLogFiles=5
20-XX:GCLogFileSize=100M
21

4. 容量规划

1# 系统容量规划
2capacity_planning:
3  target_qps: 100000
4  peak_concurrent_users: 50000
5  
6  server_specs:
7    cpu_cores: 16
8    memory_gb: 32
9    disk_type: SSD
10    network_bandwidth: 10Gbps
11  
12  middleware_specs:
13    redis_cluster:
14      nodes: 6
15      memory_per_node: 16GB
16      max_connections: 10000
17    
18    mysql_cluster:
19      master_nodes: 2
20      slave_nodes: 4
21      connection_pool_size: 200
22    
23    rocketmq_cluster:
24      broker_nodes: 4
25      nameserver_nodes: 3
26      queue_capacity: 1000000
27  
28  estimated_costs:
29    monthly_infrastructure: "$15,000"
30    annual_maintenance: "$50,000"
31

总结

本分布式秒杀系统设计方案具有以下特点:

核心优势

  1. 高性能: 支持10万+QPS,响应时间控制在100ms以内
  2. 高可用: 多级缓存、熔断降级、故障转移机制
  3. 数据一致性: Redis分布式锁 + Lua脚本保证原子性
  4. 可扩展性: 微服务架构,支持水平扩展
  5. 可监控: 完整的监控体系和告警机制

技术亮点

  1. 多级缓存: 本地缓存 + Redis + 数据库
  2. 异步处理: 消息队列削峰填谷
  3. 分库分表: 提升数据库处理能力
  4. 限流降级: 保护系统稳定性
  5. 实时监控: 全链路性能监控

适用场景

  • 电商平台秒杀活动
  • 票务系统抢票
  • 限量商品发售
  • 高并发营销活动

该方案经过生产环境验证,能够稳定支撑大规模秒杀活动,为企业提供可靠的技术保障。


分布式秒杀系统设计方案》 是转载文章,点击查看原文


相关推荐


2025 年 AI+BI 趋势下,Wyn 商业智能软件如何重构企业决策效率?
葡萄城技术团队9/30/2025

2025年AI+BI趋势下,Wyn商业智能软件通过&quot;嵌入式架构+AI原生能力&quot;重构企业决策效率。Gartner预测,60%的企业将依赖自然语言交互完成数据分析。Wyn具备三大核心优势:1)零门槛AI对话分析,业务人员可自然语言提问获取分析结果;2)国产化与灵活部署,适配统信UOS等国产系统;3)嵌入式全域集成,可融入MES、OA等业务系统。典型案例显示,Wyn帮助制造企业减少40%设备停机时间,医药企业提升70%决策响应速度。选型考量聚焦可信性、易用性、集成性和国产化。


关于win11的Microsoft To Pdf打印机修改端口后无法再刷新显示于设备界面的问题
随风万里无云2025/10/2

请记住,有时候死钻牛角尖,反倒是不值得; 从24号到30号,再到今天国庆节第一天才记录,这就是过程,每个过程都结束的时候, 所以,请别焦虑,或许换个思路,就能柳暗花明又一村 (如果你只是需要解决的方法,直接看2.2往后) 1.问题起因: 我需要修改端口实现打印不弹出选择的保存界面,直接存在固定的位置 2.修改完成端口本地端口为固定路径 测试打印没问题,然后离谱的就出现了! 设备界面中再也找不到这个打印机了,但是你打印的时候依旧可以正常打印 我在网上找了很多帖子想要


什么是 ONNX Runtime?
Cosolar2025/10/2

在人工智能技术飞速发展的今天,模型训练与部署之间的“鸿沟”始终是行业痛点——训练好的模型往往因框架差异、硬件限制等问题难以高效落地。而ONNX Runtime的出现,为这一难题提供了强有力的解决方案。作为微软开源的跨平台推理引擎,ONNX Runtime凭借其跨框架兼容性、全硬件加速能力和极致的性能优化,已成为AI模型部署领域的关键基础设施。本文将深入解析ONNX Runtime的核心价值、技术原理与应用场景,带你领略它如何为AI落地“加速”。 1、什么是ONNX Runtime? ONNX R


基于PyTorch的CIFAR10加载与TensorBoard可视化实践
StarPrayers.2025/10/3

视频学习来源:https://www.bilibili.com/video/BV1hE411t7RN?t=1.1&p=15 import torchvision from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter from test_03 import writer # 添加 添加 download=True 参数来下载数据集 test_data = torchv


AI 自动化测试:接口测试全流程自动化的实现方法
Jinkxs2025/10/4

在 AI 技术飞速渗透各行各业的当下,我们早已告别 “谈 AI 色变” 的观望阶段,迈入 “用 AI 提效” 的实战时代 💡。无论是代码编写时的智能辅助 💻、数据处理中的自动化流程 📊,还是行业场景里的精准解决方案 ,AI 正以润物细无声的方式,重构着我们的工作逻辑与行业生态 🌱。曾几何时,我们需要花费数小时查阅文档 📚、反复调试代码 ⚙️,或是在海量数据中手动筛选关键信息 ,而如今,一个智能工具 🧰、一次模型调用 ⚡,就能将这些繁琐工作的效率提升数倍 📈。正是在这样的变革中,AI


第4篇 vs2019+QT调用SDK连接海康相机显示图片
txwtech笛克电科2025/10/5

vs2019+QT调用SDK连接海康相机显示图片 连接,采图,获取与设置参数,曝光,增益,帧率 新建项目-文件结构: debug x64 调用类: TTcamera.cpp #include "TTcamera.h" #include <QDebug> TTcamera::TTcamera() { m_hDevHandle = NULL; m_pBufForSaveImage = nullptr; m_nBufSizeForSaveImage = 0;


【征文计划】基于Rokid CXR-M SDK 打造AI 实时会议助手:从连接到自定义界面的完整实践
_摘星_2025/10/6

【征文计划】基于Rokid CXR-M SDK 打造AI 实时会议助手:从连接到自定义界面的完整实践 > **摘要**:本文基于 Rokid CXR-M SDK,详细阐述如何构建一个面向商务会议场景的“AI 实时会议助手”应用。通过手机端与 Rokid 智能眼镜的协同,实现语音转写、要点提炼、提词引导、多语翻译与会后纪要自动生成。文章涵盖从环境配置、蓝牙/Wi-Fi 连接、设备控制、AI 场景交互到自定义 UI 渲染的完整开发流程,并提供关键代码示例与最佳实践建议。 > > ![](https:


CICD工具选型指南,Jenkins vs Arbess哪一款更好用?
高效研发之旅2025/10/8

Jenkins是一款常用的CICD工具,Arbess作为一款新兴的国产开源免费的CICD工具,两款工具各有特点。本文将从安装配置、功能特性、用户体验等几个方面对两款软件进行详细对比。 1、安装配置 项目 Jenkins Arbess 安装难度需要预装Java环境,需要手动配置端口和后台服务。一键安装,私有部署不同环境均支持傻瓜式一键安装。配置难度需要配置国内镜像源,安装核心插件零配置,安装后即刻可用,无需额外配置。支持操作系统支持Windows、ma


深入解析 Vue 3 源码:computed 的底层实现原理
excel2025/10/9

在 Vue 3 的响应式系统中,computed 是一个非常重要的功能,它用于创建基于依赖自动更新的计算属性。本文将通过分析源码,理解 computed 的底层实现逻辑,帮助你从源码层面掌握它的原理。 一、computed 的基本使用 在使用层面上,computed 有两种常见用法: 1. 只读计算属性 const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value)


先用js快速开发,后续引入ts是否是一个好的实践?
你的人类朋友2025/10/11

前言 我在业余时间做一些小项目时,每次进行技术选型,都会有一个疑问: 应该直接使用 TypeScript 开发,还是先用 JavaScript 快速启动,后续再引入 TypeScript? 今天干脆来整理一下思路,方便后续复用这些想法 正文 一、快速开发的优势 先用 JavaScript 进行快速开发确实有其明显优势: 开发速度更快 无需类型定义和接口声明 跳过类型检查的编译步骤 ⭐ 【重要】特别适合【原型开发】和【概念验证】,个人认为这个是最重要的 学习成本低 更容易上手 ⭐ 【重要】减

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0