Skip to content
清晨的一缕阳光
返回

高并发系统设计实战

高并发系统设计实战

高并发系统是互联网架构的核心挑战。秒杀、抢购、热点事件等场景需要应对瞬时大流量冲击。本文详解高并发系统设计的核心技术:限流、降级、缓存、队列、分库分表等,并提供完整的秒杀系统实战方案。

一、高并发挑战

1.1 典型场景

---
config:
  theme: forest
---
mindmap
  root((高并发场景))
    秒杀/抢购
      特点:瞬时流量大、库存有限
      峰值:10 万 + QPS
      难点:超卖、系统崩溃
    热点事件
      特点:突发流量、不可预测
      峰值:百万 + QPS
      难点:缓存穿透、数据库压力
    大促活动
      特点:持续高并发、业务复杂
      峰值:数十万 QPS
      难点:全链路稳定性
    直播互动
      特点:高并发读写、实时性要求高
      峰值:百万 + 并发连接
      难点:消息推送、状态同步

1.2 性能指标

指标说明目标值
QPS每秒查询数10 万 +
TPS每秒事务数1 万 +
响应时间P99 延迟< 100ms
可用性系统可用率99.99%
并发用户同时在线用户百万 +

1.3 设计原则

高并发设计原则

├── 冗余原则
│   └── 机器冗余、数据冗余、机房冗余

├── 拆分原则
│   ├── 业务拆分:微服务化
│   ├── 数据拆分:分库分表
│   └── 读写拆分:主从复制

├── 缓存原则
│   ├── 多级缓存:CDN → 反向代理 → 应用 → 数据库
│   └── 热点数据:预加载、永不过期

├── 异步原则
│   ├── 消息队列:削峰填谷
│   └── 异步处理:提升响应速度

└── 限流原则
    ├── 入口限流:网关层
    ├── 服务限流:Sentinel、Hystrix
    └── 降级熔断:保护核心业务

二、限流技术

2.1 限流算法

计数器

/**
 * 固定窗口计数器限流
 */
public class CounterRateLimiter {
    private final int limit;          // 限流阈值
    private final long windowMs;      // 窗口大小(毫秒)
    private final AtomicInteger count = new AtomicInteger(0);
    private volatile long windowStart = System.currentTimeMillis();
    
    public CounterRateLimiter(int limit, long windowMs) {
        this.limit = limit;
        this.windowMs = windowMs;
    }
    
    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        
        // 窗口过期,重置计数器
        if (now - windowStart >= windowMs) {
            count.set(0);
            windowStart = now;
        }
        
        // 未超限,允许通过
        if (count.get() < limit) {
            count.incrementAndGet();
            return true;
        }
        
        return false;
    }
}

// 使用示例
RateLimiter limiter = new CounterRateLimiter(100, 1000);  // 1 秒 100 次
if (limiter.tryAcquire()) {
    // 处理请求
} else {
    // 拒绝请求
}

问题:临界问题(窗口切换时流量翻倍)

滑动窗口

/**
 * 滑动窗口限流
 */
public class SlidingWindowRateLimiter {
    private final int limit;           // 限流阈值
    private final int windowSize;      // 窗口数量
    private final long windowMs;       // 窗口总时长
    private final AtomicInteger[] windows;
    private volatile int currentWindow = 0;
    private volatile long windowStart = System.currentTimeMillis();
    
    public SlidingWindowRateLimiter(int limit, int windowSize, long windowMs) {
        this.limit = limit;
        this.windowSize = windowSize;
        this.windowMs = windowMs;
        this.windows = new AtomicInteger[windowSize];
        for (int i = 0; i < windowSize; i++) {
            windows[i] = new AtomicInteger(0);
        }
    }
    
    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        long elapsed = now - windowStart;
        long slotMs = windowMs / windowSize;
        
        // 移动窗口
        int slotsToMove = (int) (elapsed / slotMs);
        if (slotsToMove > 0) {
            for (int i = 0; i < slotsToMove && i < windowSize; i++) {
                int idx = (currentWindow + i) % windowSize;
                windows[idx].set(0);
            }
            currentWindow = (currentWindow + slotsToMove) % windowSize;
            windowStart = now;
        }
        
        // 统计当前窗口内的请求数
        int total = 0;
        for (AtomicInteger window : windows) {
            total += window.get();
        }
        
        if (total < limit) {
            windows[currentWindow].incrementAndGet();
            return true;
        }
        
        return false;
    }
}

令牌桶

/**
 * 令牌桶限流
 */
public class TokenBucketRateLimiter {
    private final int capacity;        // 桶容量
    private final int refillRate;      // 补充速率(个/秒)
    private final AtomicInteger tokens;
    private volatile long lastRefill = System.currentTimeMillis();
    
    public TokenBucketRateLimiter(int capacity, int refillRate) {
        this.capacity = capacity;
        this.refillRate = refillRate;
        this.tokens = new AtomicInteger(capacity);
    }
    
    public boolean tryAcquire() {
        refill();
        
        int currentTokens = tokens.get();
        if (currentTokens > 0 && tokens.compareAndSet(currentTokens, currentTokens - 1)) {
            return true;
        }
        
        return false;
    }
    
    private void refill() {
        long now = System.currentTimeMillis();
        long elapsed = now - lastRefill;
        int tokensToAdd = (int) (elapsed * refillRate / 1000);
        
        if (tokensToAdd > 0) {
            tokens.updateAndGet(current -> Math.min(capacity, current + tokensToAdd));
            lastRefill = now;
        }
    }
}

漏桶

/**
 * 漏桶限流
 */
public class LeakyBucketRateLimiter {
    private final int capacity;        // 桶容量
    private final int leakRate;        // 漏出速率(个/秒)
    private final Queue<Long> queue;
    
    public LeakyBucketRateLimiter(int capacity, int leakRate) {
        this.capacity = capacity;
        this.leakRate = leakRate;
        this.queue = new LinkedList<>();
    }
    
    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        
        // 移除已过期的请求
        while (!queue.isEmpty() && now - queue.peek() > 1000L * capacity / leakRate) {
            queue.poll();
        }
        
        if (queue.size() < capacity) {
            queue.offer(now);
            return true;
        }
        
        return false;
    }
}

2.2 Redis 分布式限流

/**
 * Redis 分布式限流(Lua 脚本保证原子性)
 */
@Component
public class RedisRateLimiter {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String SCRIPT = 
        "local key = KEYS[1]\n" +
        "local limit = tonumber(ARGV[1])\n" +
        "local window = tonumber(ARGV[2])\n" +
        "local now = tonumber(ARGV[3])\n" +
        "\n" +
        "redis.call('ZREMRANGEBYSCORE', key, 0, now - window)\n" +
        "local count = redis.call('ZCARD', key)\n" +
        "\n" +
        "if count < limit then\n" +
        "    redis.call('ZADD', key, now, now)\n" +
        "    redis.call('EXPIRE', key, window)\n" +
        "    return 1\n" +
        "else\n" +
        "    return 0\n" +
        "end";
    
    private final DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(SCRIPT, Long.class);
    
    /**
     * 尝试获取限流许可
     * @param key 限流 key(如:user:123, api:/order)
     * @param limit 限流阈值
     * @param windowMs 窗口大小(毫秒)
     * @return true: 允许通过,false: 拒绝
     */
    public boolean tryAcquire(String key, int limit, long windowMs) {
        long now = System.currentTimeMillis();
        Long result = redisTemplate.execute(
            redisScript,
            Collections.singletonList(key),
            String.valueOf(limit),
            String.valueOf(windowMs),
            String.valueOf(now)
        );
        
        return result != null && result == 1;
    }
}

// 使用示例
@RestController
@RequestMapping("/api")
public class OrderController {
    
    @Autowired
    private RedisRateLimiter rateLimiter;
    
    @PostMapping("/order")
    public Result createOrder(@RequestBody OrderRequest request) {
        String userId = UserContext.getUserId();
        String limitKey = "order:limit:" + userId;
        
        // 限流:每用户每秒 10 次
        if (!rateLimiter.tryAcquire(limitKey, 10, 1000)) {
            return Result.error("请求过于频繁,请稍后再试");
        }
        
        // 处理订单
        orderService.create(request);
        
        return Result.success();
    }
}

2.3 Sentinel 限流

/**
 * Sentinel 限流配置
 */
@Configuration
public class SentinelConfig {
    
    @PostConstruct
    public void initRules() {
        // QPS 限流
        FlowRule qpsRule = new FlowRule();
        qpsRule.setResource("createOrder");
        qpsRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        qpsRule.setCount(100);  // 每秒 100 次
        qpsRule.setLimitApp("default");
        
        // 并发线程数限流
        FlowRule threadRule = new FlowRule();
        threadRule.setResource("createOrder");
        threadRule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
        threadRule.setCount(50);  // 最多 50 个并发线程
        threadRule.setLimitApp("default");
        
        FlowRuleManager.loadRules(Arrays.asList(qpsRule, threadRule));
    }
}

// 使用示例
@Service
public class OrderServiceImpl implements OrderService {
    
    @SentinelResource(
        value = "createOrder",
        blockHandler = "handleBlock",      // 限流处理
        fallback = "handleFallback",        // 降级处理
        exceptionsToIgnore = {IllegalArgumentException.class}
    )
    @Override
    public Order create(OrderRequest request) {
        // 创建订单逻辑
        return orderMapper.insert(request);
    }
    
    // 限流处理方法
    public Order handleBlock(OrderRequest request, BlockException ex) {
        log.warn("限流:{}", request.getUserId());
        throw new BusinessException("请求过于频繁");
    }
    
    // 降级处理方法
    public Order handleFallback(OrderRequest request, Throwable ex) {
        log.error("服务降级:{}", request.getUserId(), ex);
        // 返回默认值或缓存数据
        return null;
    }
}

三、缓存策略

3.1 多级缓存架构

多级缓存架构

用户请求

┌─────────────────────────────────────┐
│ CDN 缓存(静态资源)                 │
│ - HTML/CSS/JS                       │
│ - 图片/视频                         │
│ TTL: 1 天 -1  年                     │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│ Nginx 缓存(反向代理)               │
│ - API 响应缓存                        │
│ - 页面缓存                          │
│ TTL: 1 分钟 -1  小时                   │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│ 本地缓存(Caffeine/Guava)          │
│ - 热点数据                          │
│ - 配置数据                          │
│ TTL: 1 秒 -10  分钟                    │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│ Redis 缓存(分布式缓存)             │
│ - 业务数据                          │
│ - Session 数据                        │
│ TTL: 5 分钟 -24  小时                  │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│ 数据库(MySQL)                      │
│ - 持久化数据                        │
│ - 复杂查询                          │
└─────────────────────────────────────┘

3.2 缓存更新策略

/**
 * 缓存策略模式
 */
@Service
public class CacheStrategy {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 策略 1:Cache Aside(旁路缓存)
     * 读:先读缓存,没有则读 DB 并写入缓存
     * 写:先写 DB,再删除缓存
     */
    public User getUserById(Long id) {
        String key = "user:" + id;
        
        // 读缓存
        User user = (User) redisTemplate.opsForValue().get(key);
        if (user != null) {
            return user;
        }
        
        // 读数据库
        user = userMapper.selectById(id);
        if (user != null) {
            // 写入缓存(设置过期时间,防止缓存不一致)
            redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
        }
        
        return user;
    }
    
    public void updateUser(User user) {
        // 先写数据库
        userMapper.update(user);
        
        // 再删除缓存(下次读取时更新)
        String key = "user:" + user.getId();
        redisTemplate.delete(key);
    }
    
    /**
     * 策略 2:Read/Write Through(读写穿透)
     * 缓存代理数据库,应用只操作缓存
     */
    
    /**
     * 策略 3:Write Behind(异步回写)
     * 写缓存后异步写入数据库
     */
}

3.3 缓存穿透/击穿/雪崩

/**
 * 缓存问题解决方案
 */
@Service
public class CacheProblemSolver {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 问题 1:缓存穿透
     * 现象:查询不存在的数据,缓存不命中,请求直达数据库
     * 解决:布隆过滤器、缓存空值
     */
    public User getUserWithBloomFilter(Long id) {
        // 布隆过滤器检查
        if (!bloomFilter.mightContain(id)) {
            return null;  // 肯定不存在
        }
        
        String key = "user:" + id;
        User user = (User) redisTemplate.opsForValue().get(key);
        
        if (user == null) {
            // 数据库查询
            user = userMapper.selectById(id);
            
            if (user == null) {
                // 缓存空值(防止穿透)
                redisTemplate.opsForValue().set(key, null, 5, TimeUnit.MINUTES);
            } else {
                redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
                bloomFilter.put(id);
            }
        }
        
        return user;
    }
    
    /**
     * 问题 2:缓存击穿
     * 现象:热点 key 过期,大量请求直达数据库
     * 解决:互斥锁、永不过期
     */
    public User getUserWithMutex(Long id) {
        String key = "user:" + id;
        
        User user = (User) redisTemplate.opsForValue().get(key);
        if (user != null) {
            return user;
        }
        
        // 互斥锁
        String lockKey = "lock:user:" + id;
        if (tryLock(lockKey)) {
            try {
                // 双重检查
                user = (User) redisTemplate.opsForValue().get(key);
                if (user != null) {
                    return user;
                }
                
                // 查询数据库
                user = userMapper.selectById(id);
                redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
                return user;
            } finally {
                unlock(lockKey);
            }
        } else {
            // 等待重试
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return getUserWithMutex(id);
        }
    }
    
    /**
     * 问题 3:缓存雪崩
     * 现象:大量 key 同时过期,请求直达数据库
     * 解决:随机过期时间、高可用
     */
    public void cacheUser(User user) {
        String key = "user:" + user.getId();
        
        // 基础过期时间 + 随机值
        long expireTime = 30 + new Random().nextInt(10);  // 30-40 分钟
        
        redisTemplate.opsForValue().set(key, user, expireTime, TimeUnit.MINUTES);
    }
    
    private boolean tryLock(String key) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue()
            .setIfAbsent(key, "locked", 10, TimeUnit.SECONDS));
    }
    
    private void unlock(String key) {
        redisTemplate.delete(key);
    }
    
    @Autowired
    private BloomFilter<Long> bloomFilter;
}

3.4 热点数据发现

/**
 * 热点数据发现与缓存
 */
@Component
public class HotKeyDetector {
    
    private final ConcurrentHashMap<String, LongAdder> keyCounter = new ConcurrentHashMap<>();
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    
    @PostConstruct
    public void init() {
        // 每分钟统计一次
        scheduler.scheduleAtFixedRate(this::detectHotKeys, 1, 1, TimeUnit.MINUTES);
    }
    
    /**
     * 记录 key 访问
     */
    public void recordAccess(String key) {
        LongAdder counter = keyCounter.computeIfAbsent(key, k -> new LongAdder());
        counter.increment();
    }
    
    /**
     * 发现热点 key
     */
    private void detectHotKeys() {
        List<String> hotKeys = keyCounter.entrySet().stream()
            .filter(entry -> entry.getValue().sum() > 1000)  // 阈值:1000 次/分钟
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
        
        // 将热点 key 推送到本地缓存
        for (String key : hotKeys) {
            preloadToCaffeine(key);
        }
        
        // 重置计数器
        keyCounter.clear();
    }
    
    private void preloadToCaffeine(String key) {
        // 从 Redis 加载数据到本地缓存
        Object value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            localCache.put(key, value);
        }
    }
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private Cache<String, Object> localCache;
}

四、消息队列削峰

4.1 队列选型

队列吞吐量延迟可靠性适用场景
Kafka百万 +ms 级日志收集、大数据
RocketMQ十万 +ms 级很高交易、订单
RabbitMQ万级μs 级低延迟、复杂路由
Pulsar百万 +ms 级云原生、多租户

4.2 秒杀队列方案

/**
 * 秒杀队列方案
 */
@Service
public class SeckillQueueService {
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 秒杀入口
     */
    @PostMapping("/seckill/{itemId}")
    public Result seckill(@PathVariable Long itemId) {
        Long userId = UserContext.getUserId();
        
        // 1. 库存预扣减(Redis)
        String stockKey = "seckill:stock:" + itemId;
        Long stock = redisTemplate.opsForValue().decrement(stockKey);
        
        if (stock == null || stock < 0) {
            // 库存不足
            redisTemplate.opsForValue().increment(stockKey);  // 回滚
            return Result.error("库存不足");
        }
        
        // 2. 发送消息到队列(异步下单)
        SeckillMessage message = new SeckillMessage();
        message.setItemId(itemId);
        message.setUserId(userId);
        message.setStock(stock);
        
        rocketMQTemplate.send("seckill_topic", message);
        
        // 3. 返回排队中
        return Result.success("排队中,请稍后查看结果");
    }
    
    /**
     * 消费消息(下单)
     */
    @RocketMQMessageListener(
        topic = "seckill_topic",
        consumerGroup = "seckill_consumer_group"
    )
    public class SeckillConsumer implements RocketMQListener<SeckillMessage> {
        
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void onMessage(SeckillMessage message) {
            try {
                // 1. 检查重复购买
                String buyKey = "seckill:buy:" + message.getItemId() + ":" + message.getUserId();
                if (Boolean.TRUE.equals(redisTemplate.hasKey(buyKey))) {
                    // 重复购买,回滚库存
                    redisTemplate.opsForValue().increment("seckill:stock:" + message.getItemId());
                    return;
                }
                
                // 2. 创建订单
                Order order = new Order();
                order.setItemId(message.getItemId());
                order.setUserId(message.getUserId());
                order.setStatus(OrderStatus.PENDING);
                orderMapper.insert(order);
                
                // 3. 标记已购买
                redisTemplate.opsForValue().set(buyKey, "1", 24, TimeUnit.HOURS);
                
                log.info("秒杀成功:userId={}, itemId={}", message.getUserId(), message.getItemId());
                
            } catch (Exception e) {
                log.error("秒杀失败", e);
                // 回滚库存
                redisTemplate.opsForValue().increment("seckill:stock:" + message.getItemId());
                throw e;  // 重试
            }
        }
    }
}

4.3 队列积压处理

/**
 * 队列积压监控与处理
 */
@Component
public class QueueBacklogHandler {
    
    @Scheduled(fixedRate = 60000)  // 每分钟检查
    public void checkBacklog() {
        // 检查队列积压量
        long backlog = rocketMQTemplate.consumer().backlog("seckill_consumer_group");
        
        if (backlog > 10000) {
            log.warn("队列积压:{}", backlog);
            
            // 告警通知
            alertService.send("队列积压超过阈值:" + backlog);
            
            // 自动扩容消费者
            if (backlog > 50000) {
                scaleUpConsumers();
            }
        }
    }
    
    private void scaleUpConsumers() {
        // 增加消费者实例(K8s HPA 或手动扩容)
        log.info("扩容消费者");
    }
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    @Autowired
    private AlertService alertService;
}

五、数据库优化

5.1 读写分离

# ShardingSphere 配置
spring:
  shardingsphere:
    datasource:
      names: ds-master,ds-slave0,ds-slave1
      
      ds-master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://master:3306/order_db
        username: root
        password: root123
      
      ds-slave0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://slave0:3306/order_db
        username: root
        password: root123
      
      ds-slave1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://slave1:3306/order_db
        username: root
        password: root123
    
    rules:
      readwrite-splitting:
        data-groups:
          pr_ds:
            write-data-source-name: ds-master
            read-data-source-names: ds-slave0,ds-slave1
            load-balancer-name: random
        load-balancers:
          random:
            type: RANDOM

5.2 分库分表

# 分库分表配置
spring:
  shardingsphere:
    rules:
      sharding:
        tables:
          t_order:
            actual-data-nodes: ds$->{0..1}.t_order$->{0..3}
            table-strategy:
              standard:
                sharding-column: order_id
                sharding-algorithm-name: t_order_table_algor
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: t_order_db_algor
        
        sharding-algorithms:
          t_order_table_algor:
            type: CLASS_BASED
            props:
              strategy: COMPLEX
              algorithm-class: com.example.OrderTableShardingAlgorithm
          
          t_order_db_algor:
            type: CLASS_BASED
            props:
              strategy: COMPLEX
              algorithm-class: com.example.OrderDbShardingAlgorithm
/**
 * 分表算法
 */
public class OrderTableShardingAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
    
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, 
                                         ComplexKeysShardingValue<Long> shardingValue) {
        // 根据 order_id 分表
        Long orderId = shardingValue.getColumnNameAndValueMap().get("order_id");
        int tableIndex = (int) (orderId % 4);
        
        return availableTargetNames.stream()
            .filter(name -> name.endsWith(String.valueOf(tableIndex)))
            .collect(Collectors.toList());
    }
}

5.3 SQL 优化

-- 优化前(全表扫描)
SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';

-- 优化后(索引扫描)
SELECT * FROM orders 
WHERE create_time >= '2024-01-01 00:00:00' 
  AND create_time < '2024-01-02 00:00:00';

-- 添加索引
CREATE INDEX idx_create_time ON orders(create_time);

-- 覆盖索引(避免回表)
SELECT order_id, user_id, amount 
FROM orders 
WHERE create_time >= '2024-01-01';

-- 避免 SELECT *
-- 只查询需要的字段

六、秒杀系统实战

6.1 系统架构

---
config:
  theme: neutral
  look: classic
---
graph TB
    subgraph 用户层
        A[用户请求] --> B[CDN 静态资源]
        A --> C[页面静态化]
    end
    
    subgraph 接入层
        B --> D[Nginx 限流]
        C --> D
        D --> E[WAF 防护]
        E --> F[负载均衡]
    end
    
    subgraph 服务层
        F --> G[网关限流 Sentinel]
        G --> H[秒杀服务]
        G --> I[订单服务]
        H -.异步.-> I
    end
    
    subgraph 缓存层
        H --> J[Redis 集群]
        H --> K[本地缓存 Caffeine]
        J --> L[库存缓存]
        J --> M[用户资格缓存]
        K --> N[热点数据]
    end
    
    subgraph 消息层
        I --> O[RocketMQ]
        O --> P[削峰填谷]
        O --> Q[异步下单]
    end
    
    subgraph 数据层
        O --> R[MySQL 主从]
        R --> S[订单库分库分表]
        R --> T[商品库]
        J --> U[Redis 持久化]
    end

6.2 核心代码

/**
 * 秒杀服务
 */
@Service
public class SeckillService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    /**
     * 秒杀活动预热
     */
    @PostMapping("/preheat/{itemId}")
    public void preheat(@PathVariable Long itemId) {
        // 1. 加载库存到 Redis
        SeckillItem item = seckillItemMapper.selectById(itemId);
        String stockKey = "seckill:stock:" + itemId;
        redisTemplate.opsForValue().set(stockKey, item.getStock());
        
        // 2. 预热活动信息
        String itemKey = "seckill:item:" + itemId;
        redisTemplate.opsForValue().set(itemKey, item, 1, TimeUnit.HOURS);
        
        // 3. 加载秒杀白名单(如有)
        loadWhitelist(itemId);
    }
    
    /**
     * 秒杀执行
     */
    @PostMapping("/execute/{itemId}")
    public Result execute(@PathVariable Long itemId) {
        Long userId = UserContext.getUserId();
        
        // 1. 用户资格检查
        if (!checkUserQualification(userId, itemId)) {
            return Result.error("无购买资格");
        }
        
        // 2. 重复购买检查
        String buyKey = "seckill:buy:" + itemId + ":" + userId;
        if (Boolean.TRUE.equals(redisTemplate.hasKey(buyKey))) {
            return Result.error("已购买过");
        }
        
        // 3. 库存预扣减
        String stockKey = "seckill:stock:" + itemId;
        Long stock = redisTemplate.opsForValue().decrement(stockKey);
        
        if (stock == null || stock < 0) {
            // 库存不足,回滚
            redisTemplate.opsForValue().increment(stockKey);
            return Result.error("库存不足");
        }
        
        // 4. 发送秒杀消息(异步下单)
        SeckillMessage message = new SeckillMessage();
        message.setItemId(itemId);
        message.setUserId(userId);
        message.setStock(stock);
        
        rocketMQTemplate.send("seckill_order_topic", message);
        
        // 5. 返回排队中
        return Result.success("排队中");
    }
    
    /**
     * 查询秒杀结果
     */
    @GetMapping("/result/{itemId}")
    public Result getResult(@PathVariable Long itemId) {
        Long userId = UserContext.getUserId();
        
        // 检查是否购买成功
        String buyKey = "seckill:buy:" + itemId + ":" + userId;
        if (Boolean.TRUE.equals(redisTemplate.hasKey(buyKey))) {
            // 查询订单
            Order order = orderMapper.selectByUserAndItem(userId, itemId);
            return Result.success(order);
        }
        
        return Result.success("未中奖");
    }
    
    private boolean checkUserQualification(Long userId, Long itemId) {
        // 黑名单检查
        String blacklistKey = "seckill:blacklist:" + userId;
        if (Boolean.TRUE.equals(redisTemplate.hasKey(blacklistKey))) {
            return false;
        }
        
        // 风控检查(简化)
        return true;
    }
    
    private void loadWhitelist(Long itemId) {
        // 加载白名单用户
        List<Long> whitelist = whitelistMapper.selectByItemId(itemId);
        String whitelistKey = "seckill:whitelist:" + itemId;
        redisTemplate.opsForSet().add(whitelistKey, whitelist.toArray());
    }
}

七、总结

7.1 核心要点

  1. 限流:入口限流、服务限流、降级熔断
  2. 缓存:多级缓存、更新策略、问题预防
  3. 队列:削峰填谷、异步处理
  4. 数据库:读写分离、分库分表、SQL 优化
  5. 监控:全链路监控、告警、自动扩容

7.2 架构演进

架构演进路线

单机 → 集群 → 微服务 → 云原生
 ↓      ↓       ↓         ↓
限流   负载均衡  服务治理   弹性伸缩
缓存   读写分离  消息队列   Serverless

高并发系统设计是系统工程,需要从架构、代码、运维多维度持续优化。


分享这篇文章到:

上一篇文章
缓存策略设计实战
下一篇文章
CI/CD 流水线设计实战