高并发系统架构设计与实现深度解析

1. 什么是高并发系统

1.1 定义

高并发系统:在同一时间段内,能够处理大量用户请求(通常是成千上万甚至百万级别的并发请求),并保证系统稳定性、高可用性和良好用户体验的系统。

1.2 核心指标

指标 说明 典型值
QPS (每秒查询数) 系统每秒处理的请求数量 中等系统:1000-5000 高并发系统:10000+
TPS (每秒事务数) 系统每秒处理的事务数量 电商下单:5000+ 秒杀场景:50000+
RT (响应时间) 从请求发出到收到响应的时间 优秀:< 100ms 可接受:< 500ms 较差:> 1s
并发用户数 同时在线并发访问的用户数 小型:1000-5000 中型:1万-10万 大型:100万+
可用性 系统正常运行时间占比 99.9% (年停机8.76小时) 99.99% (年停机52.56分钟)

1.3 高并发 vs 多线程

复制代码
┌─────────────────────────────────────────────────────────┐
│              高并发 (High Concurrency)                    │
│  ┌──────────────────────────────────────────────────┐   │
│  │         多线程 (Multi-Threading)                   │   │
│  │  单机内通过多线程提高并发处理能力                    │   │
│  └──────────────────────────────────────────────────┘   │
│                                                          │
│  高并发 = 多线程 + 分布式 + 缓存 + 消息队列 + ...         │
└─────────────────────────────────────────────────────────┘

通俗理解

  • 多线程:一个厨师同时炒多个菜(单机多线程)
  • 高并发:开多家分店,每家店多个厨师同时炒菜(分布式 + 多线程)

2. 高并发的核心挑战

2.1 五大核心问题

复制代码
高并发系统面临的挑战
│
├─ 1. 性能瓶颈
│   ├─ CPU密集型计算
│   ├─ 内存不足
│   ├─ 磁盘IO阻塞
│   └─ 网络带宽受限
│
├─ 2. 数据库压力
│   ├─ 连接数耗尽
│   ├─ 慢SQL拖垮系统
│   ├─ 锁竞争激烈
│   └─ 主库单点瓶颈
│
├─ 3. 缓存问题
│   ├─ 缓存穿透
│   ├─ 缓存击穿
│   ├─ 缓存雪崩
│   └─ 缓存一致性
│
├─ 4. 系统稳定性
│   ├─ 服务雪崩
│   ├─ 流量洪峰
│   ├─ 热点数据
│   └─ 资源耗尽
│
└─ 5. 数据一致性
    ├─ 分布式事务
    ├─ 最终一致性
    └─ 脑裂问题

2.2 实际案例:12306崩溃分析

2012年春运12306崩溃事件

复制代码
问题现象:
- 同时在线用户:500万+
- 页面打开时间:> 30秒
- 大量用户无法登录
- 订单提交失败率:> 80%

根本原因:
1. 数据库单点瓶颈(Oracle单库)
2. 没有缓存层(每次都查数据库)
3. 没有消息队列(订单同步处理)
4. 没有限流机制(无限制接收请求)
5. 没有CDN(静态资源从服务器加载)

后果:
- 用户体验极差
- 品牌形象受损
- 系统多次瘫痪

3. 高并发系统设计原则

3.1 四大核心原则

1. 垂直拆分 + 水平扩展
复制代码
垂直拆分(Scale Up):提升单机性能
CPU: 4核 → 16核
内存: 8G → 64G
磁盘: HDD → SSD

水平扩展(Scale Out):增加机器数量  ⭐ 推荐
1台服务器 → 10台服务器 → 100台服务器

为什么推荐水平扩展?

  • 成本低(普通服务器便宜)
  • 无上限(理论上可无限扩展)
  • 高可用(单台故障不影响整体)
2. 无状态服务
java 复制代码
// ❌ 错误:有状态服务
public class OrderService {
    private List<Order> orderCache = new ArrayList<>(); // 状态保存在内存

    public void createOrder(Order order) {
        orderCache.add(order); // 其他服务器看不到这个订单
    }
}

// ✅ 正确:无状态服务
public class OrderService {
    @Autowired
    private RedisTemplate redisTemplate; // 状态保存在外部存储

    public void createOrder(Order order) {
        redisTemplate.opsForValue().set("order:" + order.getId(), order);
    }
}

无状态的好处

  • 任意服务器处理任意请求
  • 服务器可随意增减
  • 负载均衡更简单
3. 异步处理
复制代码
同步处理(用户等待):
用户下单 → 减库存 → 创建订单 → 发送短信 → 发送邮件 → 积分增加 → 返回结果
         ↓        ↓         ↓         ↓         ↓
        50ms    100ms     200ms     150ms     100ms
总耗时:600ms ❌

异步处理(立即返回):
用户下单 → 减库存 → 创建订单 → 返回结果 ✅ (150ms)
                    ↓
              发送到消息队列
                    ↓
         后台异步处理(发短信、邮件、积分)
4. 服务降级与限流
复制代码
正常流量: ████░░░░░░ (40%)  → 全功能运行
高峰流量: ██████████ (100%) → 触发限流 + 降级

限流策略:
- 超过阈值的请求返回"系统繁忙,请稍后再试"
- 保护核心功能,牺牲次要功能

降级策略:
- 关闭推荐系统(不影响核心购买)
- 关闭评论功能
- 使用默认数据代替实时查询

4. 架构层面的高并发解决方案

4.1 架构演进路线

复制代码
阶段1: 单体应用
┌─────────────────┐
│   Tomcat        │
│  ┌───────────┐  │
│  │Application│  │
│  └───────────┘  │
└────────┬────────┘
         │
    ┌────▼─────┐
    │  MySQL   │
    └──────────┘
并发能力:~500 QPS

↓ 演进 ↓

阶段2: 应用集群 + 读写分离
      ┌─────────────┐
      │Load Balancer│
      └──────┬──────┘
         ┌───┴───┬──────┬──────┐
    ┌────▼───┐ ┌─▼───┐ ┌─▼───┐
    │Tomcat 1│ │Tomcat│ │Tomcat│
    └────┬───┘ └──┬──┘ └──┬──┘
         └────────┼───────┘
              ┌───▼────┐
              │  主库   │
              └───┬────┘
         ┌────────┼────────┐
    ┌────▼───┐ ┌─▼───┐ ┌─▼───┐
    │从库1   │ │从库2│ │从库3│
    └────────┘ └─────┘ └─────┘
并发能力:~5000 QPS

↓ 演进 ↓

阶段3: 微服务 + 缓存 + 消息队列
┌─────────────────────────────────────────┐
│              API Gateway                 │
└───┬─────────────┬───────────┬───────────┘
┌───▼──────┐ ┌────▼─────┐ ┌───▼──────┐
│用户服务  │ │订单服务  │ │商品服务  │
└───┬──────┘ └────┬─────┘ └───┬──────┘
    │   ┌─────────▼─────────┐  │
    │   │   Redis Cluster   │  │
    │   └───────────────────┘  │
    │   ┌─────────▼─────────┐  │
    └───│   RabbitMQ        │──┘
        └───────────────────┘
并发能力:~50000+ QPS

4.2 负载均衡算法实现

轮询(Round Robin)
java 复制代码
public class RoundRobinLoadBalancer {
    private final List<String> servers;
    private final AtomicInteger position = new AtomicInteger(0);

    public RoundRobinLoadBalancer(List<String> servers) {
        this.servers = servers;
    }

    public String selectServer() {
        int pos = position.getAndIncrement() % servers.size();
        return servers.get(pos);
    }
}

// 使用示例
List<String> servers = Arrays.asList("192.168.1.1", "192.168.1.2", "192.168.1.3");
RoundRobinLoadBalancer lb = new RoundRobinLoadBalancer(servers);

lb.selectServer(); // 192.168.1.1
lb.selectServer(); // 192.168.1.2
lb.selectServer(); // 192.168.1.3
lb.selectServer(); // 192.168.1.1 (循环)
加权轮询(Weighted Round Robin)
java 复制代码
public class WeightedRoundRobinLoadBalancer {
    private static class Server {
        String ip;
        int weight;           // 权重(配置的固定值)
        int currentWeight;    // 当前权重(动态变化)

        public Server(String ip, int weight) {
            this.ip = ip;
            this.weight = weight;
            this.currentWeight = 0;
        }
    }

    private final List<Server> servers = new ArrayList<>();

    public void addServer(String ip, int weight) {
        servers.add(new Server(ip, weight));
    }

    public String selectServer() {
        if (servers.isEmpty()) return null;

        // 计算总权重
        int totalWeight = servers.stream().mapToInt(s -> s.weight).sum();

        // 找到当前权重最大的服务器
        Server selected = null;
        for (Server server : servers) {
            server.currentWeight += server.weight;  // 增加当前权重

            if (selected == null || server.currentWeight > selected.currentWeight) {
                selected = server;
            }
        }

        // 选中的服务器减去总权重
        selected.currentWeight -= totalWeight;

        return selected.ip;
    }
}

// 使用示例(权重 5:3:2)
WeightedRoundRobinLoadBalancer lb = new WeightedRoundRobinLoadBalancer();
lb.addServer("192.168.1.1", 5); // 性能好的服务器
lb.addServer("192.168.1.2", 3);
lb.addServer("192.168.1.3", 2); // 性能差的服务器

// 10次请求分布:
// 192.168.1.1 → 5次
// 192.168.1.2 → 3次
// 192.168.1.3 → 2次

底层原理详解

复制代码
初始状态(权重 5:3:2):
Server1: currentWeight=0, weight=5
Server2: currentWeight=0, weight=3
Server3: currentWeight=0, weight=2

第1次选择:
1. 所有服务器 currentWeight += weight
   Server1: 0+5=5  ← 最大,被选中
   Server2: 0+3=3
   Server3: 0+2=2
2. 选中服务器 currentWeight -= 总权重(10)
   Server1: 5-10=-5

第2次选择:
1. currentWeight += weight
   Server1: -5+5=0
   Server2: 3+3=6   ← 最大,被选中
   Server3: 2+2=4
2. Server2: 6-10=-4

第3次选择:
1. currentWeight += weight
   Server1: 0+5=5   ← 最大,被选中
   Server2: -4+3=-1
   Server3: 4+2=6

...循环10次后,分布为 5:3:2
最小连接数(Least Connections)
java 复制代码
public class LeastConnectionsLoadBalancer {
    private static class Server {
        String ip;
        AtomicInteger activeConnections = new AtomicInteger(0);

        public Server(String ip) {
            this.ip = ip;
        }
    }

    private final List<Server> servers = new CopyOnWriteArrayList<>();

    public void addServer(String ip) {
        servers.add(new Server(ip));
    }

    public String selectServer() {
        // 找到当前连接数最少的服务器
        Server selected = servers.stream()
            .min(Comparator.comparingInt(s -> s.activeConnections.get()))
            .orElse(null);

        if (selected != null) {
            selected.activeConnections.incrementAndGet();
        }

        return selected != null ? selected.ip : null;
    }

    public void releaseConnection(String ip) {
        servers.stream()
            .filter(s -> s.ip.equals(ip))
            .findFirst()
            .ifPresent(s -> s.activeConnections.decrementAndGet());
    }
}

5. 缓存体系深度解析

5.1 多级缓存架构

复制代码
请求流程(从上到下查找):
┌──────────────────────────────────────────┐
│  1. 浏览器缓存 (Browser Cache)            │  命中率: 20%
│     - LocalStorage / SessionStorage      │  耗时: 0ms
│     - Cookie                             │
└──────────────────┬───────────────────────┘
                   │ Miss
┌──────────────────▼───────────────────────┐
│  2. CDN缓存                               │  命中率: 30%
│     - 静态资源 (图片、CSS、JS)            │  耗时: 10-50ms
└──────────────────┬───────────────────────┘
                   │ Miss
┌──────────────────▼───────────────────────┐
│  3. Nginx本地缓存 (Proxy Cache)           │  命中率: 15%
│     - 热点数据                            │  耗时: 1-5ms
└──────────────────┬───────────────────────┘
                   │ Miss
┌──────────────────▼───────────────────────┐
│  4. 应用层本地缓存 (Caffeine/Guava)       │  命中率: 20%
│     - JVM堆内缓存                         │  耗时: < 1ms
└──────────────────┬───────────────────────┘
                   │ Miss
┌──────────────────▼───────────────────────┐
│  5. Redis分布式缓存                       │  命中率: 10%
│     - 热点数据                            │  耗时: 1-5ms
└──────────────────┬───────────────────────┘
                   │ Miss
┌──────────────────▼───────────────────────┐
│  6. 数据库 (MySQL)                        │  命中率: 5%
│     - 持久化数据                          │  耗时: 10-100ms
└──────────────────────────────────────────┘

总缓存命中率 ≈ 95%,平均响应时间 < 10ms

5.2 本地缓存实现(Caffeine)

java 复制代码
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;

@Service
public class ProductService {

    // 本地缓存配置
    private final Cache<Long, Product> localCache = Caffeine.newBuilder()
        .maximumSize(10000)                          // 最大缓存数量
        .expireAfterWrite(5, TimeUnit.MINUTES)       // 写入5分钟后过期
        .recordStats()                                // 记录统计信息
        .build();

    @Autowired
    private ProductMapper productMapper;

    public Product getProduct(Long productId) {
        // 先查本地缓存
        Product product = localCache.getIfPresent(productId);
        if (product != null) {
            return product;
        }

        // 缓存未命中,查数据库
        product = productMapper.selectById(productId);
        if (product != null) {
            localCache.put(productId, product);  // 写入缓存
        }

        return product;
    }

    public void updateProduct(Product product) {
        productMapper.updateById(product);
        localCache.invalidate(product.getId());  // 删除缓存
    }
}

Caffeine底层原理

复制代码
1. 数据结构:ConcurrentHashMap(线程安全)
   ┌─────────────────────────────────────┐
   │ Key: productId  │  Value: Product   │
   │ Hash: xxxxxxx   │  访问时间、频率    │
   └─────────────────────────────────────┘

2. 淘汰算法:W-TinyLFU(Window Tiny LFU)
   - LFU (Least Frequently Used): 淘汰访问频率最低的
   - Window: 保护新数据
   - Tiny: 使用Count-Min Sketch节省内存

   Window Cache (1%)    Main Cache (99%)
   ┌────────────┐      ┌──────────────┐
   │新数据保护区│ ───> │  LFU淘汰区   │
   └────────────┘      └──────────────┘

3. 性能优化:
   - 读操作:O(1) 时间复杂度
   - 写操作:异步执行淘汰(不阻塞)
   - 内存占用:比Guava少10%

5.3 Redis分布式缓存实战

缓存穿透解决方案

问题:查询一个不存在的数据,缓存和数据库都没有,导致每次请求都打到数据库。

java 复制代码
@Service
public class ProductService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private ProductMapper productMapper;

    // 解决方案1: 缓存空对象
    public Product getProduct(Long productId) {
        String key = "product:" + productId;

        // 1. 查询Redis
        Product product = (Product) redisTemplate.opsForValue().get(key);
        if (product != null) {
            return product;
        }

        // 2. 查询数据库
        product = productMapper.selectById(productId);

        if (product != null) {
            // 3. 存在则缓存
            redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
        } else {
            // 4. 不存在也缓存(空对象),设置较短过期时间
            redisTemplate.opsForValue().set(key, new Product(), 5, TimeUnit.MINUTES);
        }

        return product;
    }

    // 解决方案2: 布隆过滤器
    @Autowired
    private RedissonClient redissonClient;

    private RBloomFilter<Long> bloomFilter;

    @PostConstruct
    public void initBloomFilter() {
        bloomFilter = redissonClient.getBloomFilter("product:bloom");
        // 预期元素数量100万,误判率1%
        bloomFilter.tryInit(1000000L, 0.01);

        // 将所有商品ID加入布隆过滤器
        List<Long> productIds = productMapper.selectAllIds();
        productIds.forEach(bloomFilter::add);
    }

    public Product getProductWithBloom(Long productId) {
        // 1. 布隆过滤器判断
        if (!bloomFilter.contains(productId)) {
            return null;  // 一定不存在,直接返回
        }

        // 2. 可能存在,查询缓存和数据库
        String key = "product:" + productId;
        Product product = (Product) redisTemplate.opsForValue().get(key);
        if (product != null) {
            return product;
        }

        product = productMapper.selectById(productId);
        if (product != null) {
            redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
        }

        return product;
    }
}

布隆过滤器底层原理

复制代码
1. 数据结构:位数组(Bit Array)
   初始状态(所有位都是0):
   [0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]
    0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15

2. 添加元素(productId = 1001):
   使用3个哈希函数计算:
   Hash1(1001) = 3
   Hash2(1001) = 7
   Hash3(1001) = 12

   将对应位置设置为1:
   [0][0][0][1][0][0][0][1][0][0][0][0][1][0][0][0]
    0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15

3. 判断元素是否存在(productId = 1001):
   Hash1(1001) = 3  → 位为1 ✓
   Hash2(1001) = 7  → 位为1 ✓
   Hash3(1001) = 12 → 位为1 ✓
   结论:可能存在(有误判可能)

4. 判断元素是否存在(productId = 9999):
   Hash1(9999) = 5  → 位为0 ✗
   结论:一定不存在(不会误判)

优点:
- 空间效率极高(1亿数据只需12MB)
- 查询速度快(O(k),k为哈希函数数量)

缺点:
- 有误判率(可以通过增加位数组大小降低)
- 不支持删除(删除会影响其他元素)
缓存击穿解决方案

问题:热点key过期瞬间,大量请求同时打到数据库。

java 复制代码
@Service
public class ProductService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private Redisson redisson;

    @Autowired
    private ProductMapper productMapper;

    // 解决方案1: 互斥锁
    public Product getProduct(Long productId) {
        String key = "product:" + productId;
        String lockKey = "lock:product:" + productId;

        // 1. 查询缓存
        Product product = (Product) redisTemplate.opsForValue().get(key);
        if (product != null) {
            return product;
        }

        // 2. 缓存未命中,获取分布式锁
        RLock lock = redisson.getLock(lockKey);
        try {
            // 尝试加锁,最多等待10秒,锁自动释放时间30秒
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                // 3. 再次检查缓存(双重检查)
                product = (Product) redisTemplate.opsForValue().get(key);
                if (product != null) {
                    return product;
                }

                // 4. 查询数据库
                product = productMapper.selectById(productId);

                // 5. 写入缓存
                if (product != null) {
                    redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
                }

                return product;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }

        return null;
    }

    // 解决方案2: 逻辑过期(热点数据永不过期)
    @Data
    static class ProductWithExpire {
        private Product product;
        private LocalDateTime expireTime;  // 逻辑过期时间
    }

    private static final ExecutorService CACHE_REBUILD_EXECUTOR =
        Executors.newFixedThreadPool(10);

    public Product getProductWithLogicalExpire(Long productId) {
        String key = "product:" + productId;

        // 1. 查询缓存(永不过期)
        ProductWithExpire productWithExpire =
            (ProductWithExpire) redisTemplate.opsForValue().get(key);

        if (productWithExpire == null) {
            return null;  // 数据不存在
        }

        // 2. 检查逻辑过期时间
        if (productWithExpire.getExpireTime().isAfter(LocalDateTime.now())) {
            // 未过期,直接返回
            return productWithExpire.getProduct();
        }

        // 3. 已过期,尝试获取锁
        String lockKey = "lock:product:" + productId;
        RLock lock = redisson.getLock(lockKey);

        if (lock.tryLock()) {
            // 4. 获取锁成功,开启异步线程重建缓存
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    // 查询数据库
                    Product product = productMapper.selectById(productId);

                    // 写入缓存(设置逻辑过期时间)
                    ProductWithExpire newData = new ProductWithExpire();
                    newData.setProduct(product);
                    newData.setExpireTime(LocalDateTime.now().plusMinutes(30));

                    redisTemplate.opsForValue().set(key, newData);
                } finally {
                    lock.unlock();
                }
            });
        }

        // 5. 返回过期数据(不阻塞用户)
        return productWithExpire.getProduct();
    }
}
缓存雪崩解决方案

问题:大量key同时过期,导致请求全部打到数据库。

java 复制代码
@Service
public class ProductService {

    // 解决方案1: 过期时间加随机值
    public void saveProduct(Product product) {
        String key = "product:" + product.getId();

        // 基础过期时间30分钟 + 随机0-5分钟
        long expireTime = 30 + new Random().nextInt(5);

        redisTemplate.opsForValue().set(key, product, expireTime, TimeUnit.MINUTES);
    }

    // 解决方案2: 热点数据永不过期
    public void saveHotProduct(Product product) {
        String key = "hot:product:" + product.getId();

        // 设置为永不过期
        redisTemplate.opsForValue().set(key, product);
    }

    // 解决方案3: Redis集群 + 持久化
    // 配置Redis主从复制 + 哨兵模式
    // 即使Redis宕机,也能快速恢复数据
}

5.4 缓存一致性解决方案

java 复制代码
@Service
public class ProductService {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 方案1: Cache Aside Pattern(旁路缓存)⭐ 推荐
    // 读:先读缓存,未命中再读数据库,然后写缓存
    // 写:先更新数据库,再删除缓存

    public Product getProduct(Long productId) {
        String key = "product:" + productId;

        // 1. 读缓存
        Product product = (Product) redisTemplate.opsForValue().get(key);
        if (product != null) {
            return product;
        }

        // 2. 读数据库
        product = productMapper.selectById(productId);

        // 3. 写缓存
        if (product != null) {
            redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
        }

        return product;
    }

    @Transactional
    public void updateProduct(Product product) {
        String key = "product:" + product.getId();

        // 1. 先更新数据库
        productMapper.updateById(product);

        // 2. 再删除缓存(而不是更新缓存)
        redisTemplate.delete(key);

        // 为什么删除而不是更新?
        // 1. 避免并发问题(两个线程同时更新,顺序不确定)
        // 2. 懒加载(只有被访问时才写缓存,节省资源)
    }

    // 方案2: 延迟双删(解决并发问题)
    @Transactional
    public void updateProductWithDoubleDelete(Product product) {
        String key = "product:" + product.getId();

        // 1. 删除缓存(第一次)
        redisTemplate.delete(key);

        // 2. 更新数据库
        productMapper.updateById(product);

        // 3. 延迟删除缓存(第二次)
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(500);  // 延迟500ms
                redisTemplate.delete(key);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
    }

    // 方案3: 订阅MySQL Binlog(终极方案)
    // 使用Canal监听MySQL binlog变化,异步更新缓存
    @Component
    public class CanalClient {

        @Autowired
        private RedisTemplate<String, Object> redisTemplate;

        // 监听product表的变化
        public void onProductChange(CanalEntry.RowChange rowChange) {
            for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
                if (rowChange.getEventType() == CanalEntry.EventType.UPDATE
                    || rowChange.getEventType() == CanalEntry.EventType.DELETE) {

                    // 获取主键ID
                    String productId = getColumnValue(rowData, "id");

                    // 删除缓存
                    redisTemplate.delete("product:" + productId);
                }
            }
        }
    }
}

为什么是"先更新数据库,再删除缓存"?

复制代码
情况1: 先删除缓存,再更新数据库(❌ 不推荐)
时间轴:
t1: 线程A删除缓存
t2: 线程B读取缓存(未命中)
t3: 线程B读取数据库(旧数据)
t4: 线程B写入缓存(旧数据)
t5: 线程A更新数据库(新数据)
结果:缓存是旧数据,数据库是新数据 ❌

情况2: 先更新数据库,再删除缓存(✅ 推荐)
时间轴:
t1: 线程A更新数据库
t2: 线程A删除缓存
t3: 线程B读取缓存(未命中)
t4: 线程B读取数据库(新数据)
t5: 线程B写入缓存(新数据)
结果:缓存和数据库都是新数据 ✅

6. 数据库层面的高并发优化

6.1 读写分离架构

复制代码
主从复制原理:
┌─────────────────┐
│   主库(Master)   │  ← 写操作
└────────┬────────┘
         │ Binlog同步
    ┌────┼─────┬──────┐
┌───▼──┐ ┌─▼──┐ ┌───▼──┐
│从库1 │ │从库2│ │从库3 │  ← 读操作
└──────┘ └────┘ └──────┘

Binlog同步流程:
1. 主库执行SQL,写入Binlog
2. 从库IO线程读取主库Binlog
3. 从库写入Relay Log(中继日志)
4. 从库SQL线程执行Relay Log

MyBatis实现读写分离

java 复制代码
// 1. 配置多数据源
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource routingDataSource(
        @Qualifier("masterDataSource") DataSource masterDataSource,
        @Qualifier("slaveDataSource") DataSource slaveDataSource) {

        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);
        targetDataSources.put("slave", slaveDataSource);

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);

        return dynamicDataSource;
    }
}

// 2. 动态数据源路由
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

public class DataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSource(String dataSource) {
        CONTEXT_HOLDER.set(dataSource);
    }

    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }
}

// 3. 注解实现读写分离
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Master {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Slave {
}

@Aspect
@Component
public class DataSourceAspect {

    @Around("@annotation(master)")
    public Object routeToMaster(ProceedingJoinPoint point, Master master) throws Throwable {
        try {
            DataSourceContextHolder.setDataSource("master");
            return point.proceed();
        } finally {
            DataSourceContextHolder.clearDataSource();
        }
    }

    @Around("@annotation(slave)")
    public Object routeToSlave(ProceedingJoinPoint point, Slave slave) throws Throwable {
        try {
            DataSourceContextHolder.setDataSource("slave");
            return point.proceed();
        } finally {
            DataSourceContextHolder.clearDataSource();
        }
    }
}

// 4. 使用示例
@Service
public class ProductService {

    @Autowired
    private ProductMapper productMapper;

    @Slave  // 从库读取
    public Product getProduct(Long productId) {
        return productMapper.selectById(productId);
    }

    @Master  // 主库写入
    public void updateProduct(Product product) {
        productMapper.updateById(product);
    }
}

6.2 分库分表(Sharding)

垂直分库
复制代码
单库(所有表在一起):
┌──────────────────────┐
│      MySQL库         │
│  ┌────────────────┐  │
│  │   用户表        │  │
│  │   订单表        │  │
│  │   商品表        │  │
│  │   支付表        │  │
│  └────────────────┘  │
└──────────────────────┘

↓ 按业务拆分 ↓

垂直分库(按业务模块拆分):
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 用户库  │ │ 订单库  │ │ 商品库  │ │ 支付库  │
│ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │
│ │用户表│ │ │ │订单表│ │ │ │商品表│ │ │ │支付表│ │
│ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘

优点:
- 降低单库压力
- 业务隔离,互不影响
- 微服务友好
水平分表
复制代码
单表(1亿数据):
┌──────────────────────┐
│   订单表 (1亿行)      │
│  ┌────────────────┐  │
│  │ order_id       │  │
│  │ user_id        │  │
│  │ product_id     │  │
│  │ ...            │  │
│  └────────────────┘  │
└──────────────────────┘
问题:
- 单表太大,查询慢
- 索引失效
- 磁盘IO高

↓ 按用户ID hash分表 ↓

水平分表(分成16张表):
┌─────────┐ ┌─────────┐     ┌──────────┐
│ order_0 │ │ order_1 │ ... │ order_15 │
│(625万行)│ │(625万行)│     │(625万行) │
└─────────┘ └─────────┘     └──────────┘

分表规则:
tableIndex = user_id % 16

优点:
- 单表数据量减少
- 查询性能提升
- 可扩展性好

ShardingSphere实现分库分表

java 复制代码
// 1. 配置分片规则
@Configuration
public class ShardingConfig {

    @Bean
    public DataSource dataSource() throws SQLException {
        // 配置真实数据源
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        dataSourceMap.put("ds0", createDataSource("jdbc:mysql://localhost:3306/db0"));
        dataSourceMap.put("ds1", createDataSource("jdbc:mysql://localhost:3306/db1"));

        // 配置分片规则
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());
        shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(
            new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}")
        );

        return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties());
    }

    private TableRuleConfiguration getOrderTableRuleConfiguration() {
        TableRuleConfiguration result = new TableRuleConfiguration("t_order", "ds${0..1}.t_order_${0..15}");

        // 分库策略:按user_id % 2
        result.setDatabaseShardingStrategyConfig(
            new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}")
        );

        // 分表策略:按user_id % 16
        result.setTableShardingStrategyConfig(
            new InlineShardingStrategyConfiguration("user_id", "t_order_${user_id % 16}")
        );

        return result;
    }
}

// 2. 使用示例
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    // 插入订单(自动路由到对应分片)
    public void createOrder(Order order) {
        // user_id = 1001
        // 分库:ds${1001 % 2} = ds1
        // 分表:t_order_${1001 % 16} = t_order_9
        // 实际插入到:ds1.t_order_9
        orderMapper.insert(order);
    }

    // 查询订单(带分片键,直接路由)
    public Order getOrder(Long userId, Long orderId) {
        return orderMapper.selectByUserIdAndOrderId(userId, orderId);
    }

    // 查询订单(不带分片键,全表扫描)⚠️ 性能差
    public Order getOrderById(Long orderId) {
        // 会查询所有分片,性能很差
        return orderMapper.selectById(orderId);
    }
}

分片键选择原则

复制代码
1. 查询频繁的字段(避免跨分片查询)
   ✅ user_id(用户查询自己的订单)
   ❌ order_status(需要查询所有分片)

2. 数据分布均匀的字段
   ✅ user_id % 16(哈希均匀)
   ❌ create_time(时间可能集中)

3. 不经常变更的字段
   ✅ user_id(不会变)
   ❌ order_status(经常变化)

6.3 数据库连接池优化

java 复制代码
// HikariCP 配置(性能最优的连接池)
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.hikari")
    public HikariDataSource dataSource() {
        HikariConfig config = new HikariConfig();

        // 核心配置
        config.setJdbcUrl("jdbc:mysql://localhost:3306/db");
        config.setUsername("root");
        config.setPassword("password");

        // 连接池大小(核心参数)⭐
        config.setMaximumPoolSize(20);        // 最大连接数
        config.setMinimumIdle(10);             // 最小空闲连接数

        // 连接超时配置
        config.setConnectionTimeout(30000);    // 获取连接超时时间(30秒)
        config.setIdleTimeout(600000);         // 空闲连接超时时间(10分钟)
        config.setMaxLifetime(1800000);        // 连接最大存活时间(30分钟)

        // 性能优化
        config.setAutoCommit(true);            // 自动提交
        config.setConnectionTestQuery("SELECT 1"); // 连接测试查询

        // 泄漏检测
        config.setLeakDetectionThreshold(60000); // 连接泄漏检测(60秒)

        return new HikariDataSource(config);
    }
}

连接池大小如何计算?

复制代码
公式:
connections = ((core_count * 2) + effective_spindle_count)

core_count: CPU核心数
effective_spindle_count: 磁盘数量

示例:
4核CPU + 1块磁盘 = (4 * 2) + 1 = 9
建议配置:最大连接数 10-20

实际调优:
1. 压测观察
2. 监控连接使用情况
3. 根据业务特点调整
   - IO密集型:可以适当增加
   - CPU密集型:不宜过大

7. 消息队列削峰填谷

7.1 消息队列核心作用

复制代码
场景1: 秒杀场景(削峰)
┌─────────────────────────────────────────┐
│ 瞬时请求:10万QPS                        │
└────────────────┬────────────────────────┘
                 │
         ┌───────▼────────┐
         │  消息队列       │  ← 缓冲
         │  (RabbitMQ)    │
         └───────┬────────┘
                 │
         ┌───────▼────────┐
         │  订单服务       │  ← 平稳处理:1000QPS
         │  (消费者)       │
         └────────────────┘

优点:
- 保护下游系统
- 提升用户体验(快速返回)
- 系统稳定性高

场景2: 异步处理(解耦)
用户下单 ─┐
         ├─→ 消息队列 ─┬─→ 订单服务
         │             ├─→ 库存服务
         │             ├─→ 积分服务
         │             └─→ 短信服务
         └─ 立即返回(200ms)

同步处理:800ms(用户等待)
异步处理:200ms(用户体验好)

7.2 RabbitMQ实战

java 复制代码
// 1. 生产者(订单服务)
@Service
public class OrderService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private OrderMapper orderMapper;

    public String createOrder(Order order) {
        // 1. 创建订单(快速返回)
        orderMapper.insert(order);

        // 2. 发送消息到队列(异步处理)
        OrderMessage message = new OrderMessage();
        message.setOrderId(order.getId());
        message.setUserId(order.getUserId());

        rabbitTemplate.convertAndSend("order.exchange", "order.create", message);

        // 3. 立即返回
        return "订单创建成功,正在处理中...";
    }
}

// 2. 消费者(库存服务)
@Component
public class StockConsumer {

    @Autowired
    private StockService stockService;

    @RabbitListener(queues = "stock.queue")
    public void handleStockDeduction(OrderMessage message) {
        try {
            // 扣减库存
            stockService.deductStock(message.getOrderId());

        } catch (Exception e) {
            // 处理失败,进入重试队列
            throw new AmqpRejectAndDontRequeueException("库存扣减失败", e);
        }
    }
}

// 3. 配置死信队列(处理失败消息)
@Configuration
public class RabbitMQConfig {

    // 业务队列
    @Bean
    public Queue stockQueue() {
        return QueueBuilder.durable("stock.queue")
            .deadLetterExchange("dlx.exchange")  // 死信交换机
            .deadLetterRoutingKey("dlx.stock")   // 死信路由键
            .ttl(60000)  // 消息过期时间60秒
            .build();
    }

    // 死信队列
    @Bean
    public Queue deadLetterQueue() {
        return new Queue("dlx.stock.queue", true);
    }

    @Bean
    public DirectExchange deadLetterExchange() {
        return new DirectExchange("dlx.exchange");
    }

    @Bean
    public Binding deadLetterBinding() {
        return BindingBuilder
            .bind(deadLetterQueue())
            .to(deadLetterExchange())
            .with("dlx.stock");
    }
}

// 4. 死信队列消费者(人工处理)
@Component
public class DeadLetterConsumer {

    @RabbitListener(queues = "dlx.stock.queue")
    public void handleDeadLetter(OrderMessage message) {
        // 记录日志,人工介入处理
        log.error("订单{}库存扣减失败,进入死信队列", message.getOrderId());

        // 发送告警
        alertService.sendAlert("库存扣减失败", message);
    }
}

7.3 消息可靠性保证

java 复制代码
// 1. 生产者确认(确保消息发送成功)
@Configuration
public class RabbitMQConfig {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);

        // 开启发送确认
        template.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                log.info("消息发送成功");
            } else {
                log.error("消息发送失败:{}", cause);
                // 重试或记录数据库
            }
        });

        // 开启返回确认(消息未路由到队列)
        template.setReturnsCallback(returned -> {
            log.error("消息未路由到队列:{}", returned.getMessage());
        });

        return template;
    }
}

// 2. 消费者手动ACK(确保消息处理成功)
@Component
public class OrderConsumer {

    @RabbitListener(queues = "order.queue", ackMode = "MANUAL")
    public void handleOrder(OrderMessage message, Channel channel,
                           @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
        try {
            // 处理业务
            processOrder(message);

            // 手动ACK(确认消息)
            channel.basicAck(tag, false);

        } catch (Exception e) {
            try {
                // 处理失败,NACK(重新入队)
                channel.basicNack(tag, false, true);
            } catch (IOException ex) {
                log.error("NACK失败", ex);
            }
        }
    }
}

// 3. 消息持久化(防止MQ宕机丢失消息)
@Bean
public Queue orderQueue() {
    return QueueBuilder
        .durable("order.queue")  // 队列持久化
        .build();
}

@Bean
public DirectExchange orderExchange() {
    return new DirectExchange("order.exchange", true, false); // 交换机持久化
}

// 发送消息时设置持久化
rabbitTemplate.convertAndSend("order.exchange", "order.create", message, msg -> {
    msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 消息持久化
    return msg;
});

消息可靠性保证机制

复制代码
完整链路:
生产者 ────────────> RabbitMQ ────────────> 消费者
    │                  │                  │
    ├─ 1. 确认机制     ├─ 2. 持久化      ├─ 3. 手动ACK
    │  (ConfirmCallback)│  (Durable)      │  (basicAck)
    │                  │                  │
    └─ 4. 本地消息表   └─ 5. 集群        └─ 6. 幂等性
       (防止丢失)        (高可用)          (防止重复)

消息丢失的3个环节:
1. 生产者 → MQ:网络故障 → 解决方案:发送确认
2. MQ存储:MQ宕机 → 解决方案:持久化 + 集群
3. MQ → 消费者:消费失败 → 解决方案:手动ACK + 重试

8. 限流降级熔断机制

8.1 限流算法实现

固定窗口计数器
java 复制代码
public class FixedWindowRateLimiter {
    private final int maxRequests;  // 最大请求数
    private final long windowSize;  // 窗口大小(毫秒)

    private AtomicInteger counter = new AtomicInteger(0);
    private long windowStart;

    public FixedWindowRateLimiter(int maxRequests, long windowSize) {
        this.maxRequests = maxRequests;
        this.windowSize = windowSize;
        this.windowStart = System.currentTimeMillis();
    }

    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();

        // 判断是否进入新窗口
        if (now - windowStart >= windowSize) {
            counter.set(0);
            windowStart = now;
        }

        // 判断是否超过限制
        if (counter.get() < maxRequests) {
            counter.incrementAndGet();
            return true;
        }

        return false;
    }
}

// 使用示例:每秒最多100个请求
FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(100, 1000);

if (limiter.tryAcquire()) {
    // 处理请求
} else {
    // 限流,返回错误
    return "系统繁忙,请稍后再试";
}

固定窗口问题

复制代码
窗口边界流量突刺问题:

第1秒:  00:00 ─────────────────────> 00:01
请求:   0个                          100个 (允许)

第2秒:  00:01 ─────────────────────> 00:02
请求:   100个 (允许)                  0个

在00:01前后1秒内,实际处理了200个请求(超过限制)❌
滑动窗口计数器
java 复制代码
public class SlidingWindowRateLimiter {
    private final int maxRequests;
    private final long windowSize;
    private final int subWindowCount;  // 子窗口数量

    private final AtomicInteger[] subWindowCounters;
    private long windowStart;

    public SlidingWindowRateLimiter(int maxRequests, long windowSize, int subWindowCount) {
        this.maxRequests = maxRequests;
        this.windowSize = windowSize;
        this.subWindowCount = subWindowCount;
        this.subWindowCounters = new AtomicInteger[subWindowCount];
        for (int i = 0; i < subWindowCount; i++) {
            subWindowCounters[i] = new AtomicInteger(0);
        }
        this.windowStart = System.currentTimeMillis();
    }

    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        long subWindowSize = windowSize / subWindowCount;

        // 计算当前子窗口索引
        int currentIndex = (int) ((now / subWindowSize) % subWindowCount);

        // 计算过期的子窗口并清零
        long elapsedSubWindows = (now - windowStart) / subWindowSize;
        if (elapsedSubWindows > 0) {
            for (int i = 0; i < Math.min(elapsedSubWindows, subWindowCount); i++) {
                int expiredIndex = (int) (((windowStart / subWindowSize) + i) % subWindowCount);
                subWindowCounters[expiredIndex].set(0);
            }
            windowStart = (now / subWindowSize) * subWindowSize;
        }

        // 计算总请求数
        int totalCount = Arrays.stream(subWindowCounters)
            .mapToInt(AtomicInteger::get)
            .sum();

        if (totalCount < maxRequests) {
            subWindowCounters[currentIndex].incrementAndGet();
            return true;
        }

        return false;
    }
}

滑动窗口原理

复制代码
将1秒分为10个100ms的子窗口:

|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|
| 10  | 10  | 10  | 10  | 10  | 10  | 10  | 10  | 10  | 10  | = 100个请求

时间推移,窗口滑动:
                 ↓ 当前时间
|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|100ms|
  过期   过期              ← 统计这个窗口内的请求 →

优点:平滑限流,没有突刺问题
令牌桶算法(Token Bucket)⭐ 推荐
java 复制代码
public class TokenBucketRateLimiter {
    private final long capacity;      // 桶容量
    private final long refillRate;    // 令牌生成速率(每秒)

    private AtomicLong tokens;        // 当前令牌数
    private long lastRefillTime;      // 上次补充令牌时间

    public TokenBucketRateLimiter(long capacity, long refillRate) {
        this.capacity = capacity;
        this.refillRate = refillRate;
        this.tokens = new AtomicLong(capacity);
        this.lastRefillTime = System.currentTimeMillis();
    }

    public synchronized boolean tryAcquire(long tokensNeeded) {
        // 1. 补充令牌
        refillTokens();

        // 2. 尝试获取令牌
        if (tokens.get() >= tokensNeeded) {
            tokens.addAndGet(-tokensNeeded);
            return true;
        }

        return false;
    }

    private void refillTokens() {
        long now = System.currentTimeMillis();
        long elapsedTime = now - lastRefillTime;

        // 计算应该补充的令牌数
        long tokensToAdd = (elapsedTime * refillRate) / 1000;

        if (tokensToAdd > 0) {
            long newTokens = Math.min(capacity, tokens.get() + tokensToAdd);
            tokens.set(newTokens);
            lastRefillTime = now;
        }
    }
}

// 使用示例:桶容量100,每秒生成10个令牌
TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(100, 10);

if (limiter.tryAcquire(1)) {
    // 处理请求
} else {
    // 限流
    return "系统繁忙";
}

令牌桶原理

复制代码
令牌桶模型:
┌──────────────────────────┐
│     令牌桶 (容量100)      │
│  ┌────────────────────┐  │
│  │ ●●●●●●●●●●●●●●●●●● │  │ ← 令牌
│  └────────────────────┘  │
└────────▲──────────┬──────┘
         │          │
   每秒生成10个      │ 请求消费令牌
   令牌(固定速率)    │
                     │
              ┌──────▼──────┐
              │   请求处理   │
              └─────────────┘

优点:
1. 允许突发流量(桶内有预存的令牌)
2. 平均速率限制(令牌生成速率固定)
3. 实现简单
Guava RateLimiter(生产级实现)
java 复制代码
@Service
public class OrderService {

    // 每秒允许10个请求
    private final RateLimiter rateLimiter = RateLimiter.create(10.0);

    public String createOrder(Order order) {
        // 尝试获取令牌(最多等待1秒)
        if (rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) {
            // 处理订单
            return processOrder(order);
        } else {
            // 限流
            return "系统繁忙,请稍后再试";
        }
    }

    // 平滑预热限流(适用于系统启动场景)
    private final RateLimiter warmupLimiter =
        RateLimiter.create(100, 10, TimeUnit.SECONDS); // 10秒预热到100QPS
}
Redis实现分布式限流
java 复制代码
@Component
public class RedisRateLimiter {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 基于Redis的滑动窗口限流
     * @param key 限流键
     * @param maxRequests 最大请求数
     * @param windowSize 窗口大小(秒)
     */
    public boolean tryAcquire(String key, int maxRequests, int windowSize) {
        long now = System.currentTimeMillis();
        long windowStart = now - windowSize * 1000;

        String redisKey = "rate_limit:" + key;

        // Lua脚本保证原子性
        String luaScript =
            "redis.call('zremrangebyscore', KEYS[1], 0, ARGV[1]) " +  // 删除过期记录
            "local count = redis.call('zcard', KEYS[1]) " +            // 获取当前数量
            "if count < tonumber(ARGV[2]) then " +                     // 未超过限制
            "  redis.call('zadd', KEYS[1], ARGV[3], ARGV[3]) " +       // 添加当前请求
            "  redis.call('expire', KEYS[1], ARGV[4]) " +              // 设置过期时间
            "  return 1 " +
            "else " +
            "  return 0 " +
            "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);

        Long result = redisTemplate.execute(
            redisScript,
            Collections.singletonList(redisKey),
            windowStart, maxRequests, now, windowSize
        );

        return result != null && result == 1;
    }
}

// 使用示例
@RestController
public class OrderController {

    @Autowired
    private RedisRateLimiter rateLimiter;

    @PostMapping("/order")
    public String createOrder(@RequestBody Order order, HttpServletRequest request) {
        String userId = request.getHeader("userId");

        // 每个用户每秒最多10个请求
        if (!rateLimiter.tryAcquire("user:" + userId, 10, 1)) {
            return "请求过于频繁,请稍后再试";
        }

        // 处理订单
        return orderService.createOrder(order);
    }
}

8.2 服务降级

java 复制代码
@Service
public class ProductService {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private RecommendService recommendService;

    // 降级开关(可通过配置中心动态修改)
    @Value("${service.degradation.recommend:false}")
    private boolean recommendDegraded;

    public ProductDetailVO getProductDetail(Long productId) {
        ProductDetailVO detail = new ProductDetailVO();

        // 1. 核心功能:商品基本信息(不降级)
        Product product = productMapper.selectById(productId);
        detail.setProduct(product);

        // 2. 次要功能:推荐商品(可降级)
        if (!recommendDegraded) {
            try {
                List<Product> recommendations = recommendService.getRecommendations(productId);
                detail.setRecommendations(recommendations);
            } catch (Exception e) {
                log.error("获取推荐商品失败,使用降级方案", e);
                detail.setRecommendations(getDefaultRecommendations()); // 降级:返回默认推荐
            }
        } else {
            // 降级:直接返回默认推荐
            detail.setRecommendations(getDefaultRecommendations());
        }

        // 3. 次要功能:用户评论(可降级)
        try {
            List<Comment> comments = getComments(productId);
            detail.setComments(comments);
        } catch (Exception e) {
            detail.setComments(Collections.emptyList()); // 降级:返回空列表
        }

        return detail;
    }

    private List<Product> getDefaultRecommendations() {
        // 返回热门商品或缓存的推荐结果
        return redisTemplate.opsForList().range("hot:products", 0, 9);
    }
}

降级策略分级

复制代码
服务分级(金字塔模型):
┌─────────────────┐
│  L0: 核心服务   │ ← 永不降级(登录、下单、支付)
├─────────────────┤
│  L1: 重要服务   │ ← 高峰时降级(搜索、详情页)
├─────────────────┤
│  L2: 一般服务   │ ← 优先降级(推荐、评论)
├─────────────────┤
│  L3: 边缘服务   │ ← 立即降级(广告、统计)
└─────────────────┘

降级触发条件:
1. 错误率 > 50%
2. 响应时间 > 1秒
3. 系统负载 > 80%
4. 人工触发(大促活动)

8.3 熔断机制(Sentinel)

java 复制代码
// 1. 添加依赖
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

// 2. 配置熔断规则
@Configuration
public class SentinelConfig {

    @PostConstruct
    public void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();

        // 慢调用比例熔断
        DegradeRule slowCallRule = new DegradeRule("getProduct")
            .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
            .setCount(0.5)              // 慢调用比例阈值 50%
            .setTimeWindow(10)          // 熔断时长 10秒
            .setSlowRatioThreshold(0.5) // 慢调用比例
            .setMinRequestAmount(10)    // 最小请求数
            .setStatIntervalMs(1000);   // 统计时长 1秒

        // 异常比例熔断
        DegradeRule errorRateRule = new DegradeRule("createOrder")
            .setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType())
            .setCount(0.5)              // 异常比例阈值 50%
            .setTimeWindow(10)          // 熔断时长 10秒
            .setMinRequestAmount(10);   // 最小请求数

        rules.add(slowCallRule);
        rules.add(errorRateRule);

        DegradeRuleManager.loadRules(rules);
    }
}

// 3. 使用熔断
@Service
public class ProductService {

    @SentinelResource(
        value = "getProduct",
        blockHandler = "getProductBlockHandler",  // 熔断降级方法
        fallback = "getProductFallback"           // 异常降级方法
    )
    public Product getProduct(Long productId) {
        // 调用远程服务
        return remoteProductService.getProduct(productId);
    }

    // 熔断降级方法
    public Product getProductBlockHandler(Long productId, BlockException ex) {
        log.warn("服务被熔断,productId={}", productId);

        // 返回降级数据
        return getCachedProduct(productId);
    }

    // 异常降级方法
    public Product getProductFallback(Long productId, Throwable ex) {
        log.error("服务调用异常,productId={}", productId, ex);

        // 返回默认数据
        return getDefaultProduct();
    }
}

熔断状态机

复制代码
熔断器三种状态:

1. 关闭状态 (Closed)
   ┌─────────────┐
   │   正常请求   │
   │ 错误率 < 50% │
   └──────┬──────┘
          │ 错误率 >= 50%
          ↓
2. 打开状态 (Open)
   ┌─────────────┐
   │  快速失败   │ ← 所有请求直接降级,不调用服务
   │ (熔断10秒)  │
   └──────┬──────┘
          │ 10秒后
          ↓
3. 半开状态 (Half-Open)
   ┌─────────────┐
   │ 尝试恢复     │ ← 允许少量请求通过
   │ 放行10%请求  │
   └──────┬──────┘
          │
    成功 ↓        ↓ 失败
   ┌─────┴─┐   ┌──┴─────┐
   │ 关闭   │   │  打开   │
   └───────┘   └────────┘

9. 负载均衡深度解析

9.1 Nginx负载均衡配置

nginx 复制代码
# nginx.conf

http {
    # 定义上游服务器组
    upstream backend_servers {
        # 负载均衡算法选择

        # 1. 轮询(默认)
        server 192.168.1.10:8080;
        server 192.168.1.11:8080;
        server 192.168.1.12:8080;

        # 2. 加权轮询
        # server 192.168.1.10:8080 weight=5;  # 高性能服务器
        # server 192.168.1.11:8080 weight=3;
        # server 192.168.1.12:8080 weight=2;  # 低性能服务器

        # 3. IP Hash(同一IP固定路由到同一服务器)
        # ip_hash;

        # 4. 最少连接
        # least_conn;

        # 健康检查
        server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
        server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
        server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;

        # 备用服务器(所有服务器都挂了才使用)
        server 192.168.1.99:8080 backup;

        # Keepalive连接池(提升性能)
        keepalive 32;
    }

    server {
        listen 80;
        server_name www.example.com;

        location / {
            proxy_pass http://backend_servers;

            # 传递真实IP
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;

            # 超时配置
            proxy_connect_timeout 5s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;

            # Keepalive
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
    }
}

9.2 四层负载均衡 vs 七层负载均衡

复制代码
四层负载均衡(传输层):
┌─────────────────────────────────────┐
│          LVS / F5                   │
│  根据 IP + Port 转发                │
└────────────┬────────────────────────┘
             │
    ┌────────┼────────┐
┌───▼──┐ ┌───▼──┐ ┌───▼──┐
│服务器1│ │服务器2│ │服务器3│
└──────┘ └──────┘ └──────┘

优点:
- 性能极高(不解析HTTP)
- 支持任何协议(TCP/UDP)
缺点:
- 功能单一(无法根据URL路由)

七层负载均衡(应用层):
┌─────────────────────────────────────┐
│          Nginx / HAProxy            │
│  根据 URL / Cookie / Header 转发    │
└────────────┬────────────────────────┘
             │
    ┌────────┼────────┐
┌───▼──┐ ┌───▼──┐ ┌───▼──┐
│订单服务│ │用户服务│ │商品服务│
└──────┘ └──────┘ └──────┘

优点:
- 功能丰富(URL路由、会话保持)
- 可以缓存、压缩、SSL卸载
缺点:
- 性能略低(需要解析HTTP)

10. 代码层面的优化

10.1 避免大事务

java 复制代码
// ❌ 错误:大事务
@Transactional
public void processOrder(Order order) {
    // 1. 创建订单(需要事务)
    orderMapper.insert(order);

    // 2. 扣减库存(需要事务)
    stockMapper.deduct(order.getProductId(), order.getQuantity());

    // 3. 发送短信(不需要事务)⚠️ 耗时操作
    smsService.sendOrderNotification(order.getUserId());

    // 4. 发送邮件(不需要事务)⚠️ 耗时操作
    emailService.sendOrderEmail(order.getUserId());

    // 5. 增加积分(需要事务)
    pointMapper.addPoints(order.getUserId(), order.getAmount());
}
// 问题:事务时间太长,数据库连接被长时间占用

// ✅ 正确:拆分事务 + 异步处理
@Transactional
public void processOrder(Order order) {
    // 1. 核心事务:创建订单 + 扣库存
    orderMapper.insert(order);
    stockMapper.deduct(order.getProductId(), order.getQuantity());
}

@Async
public void processOrderAsync(Order order) {
    // 2. 异步处理:发送通知(不需要事务)
    smsService.sendOrderNotification(order.getUserId());
    emailService.sendOrderEmail(order.getUserId());
}

@Transactional
public void addPoints(Long userId, BigDecimal amount) {
    // 3. 独立事务:增加积分
    pointMapper.addPoints(userId, amount);
}

10.2 批量操作优化

java 复制代码
// ❌ 错误:循环插入
public void importProducts(List<Product> products) {
    for (Product product : products) {
        productMapper.insert(product);  // 1000次SQL
    }
}
// 耗时:1000次 × 10ms = 10秒

// ✅ 正确:批量插入
public void importProducts(List<Product> products) {
    // MyBatis批量插入
    productMapper.batchInsert(products);  // 1次SQL
}
// 耗时:100ms

// Mapper XML
<insert id="batchInsert" parameterType="java.util.List">
    INSERT INTO product (id, name, price, stock)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.id}, #{item.name}, #{item.price}, #{item.stock})
    </foreach>
</insert>

// 大数据量分批插入(避免SQL太长)
public void importProductsBatch(List<Product> products) {
    int batchSize = 500;
    for (int i = 0; i < products.size(); i += batchSize) {
        int end = Math.min(i + batchSize, products.size());
        List<Product> batch = products.subList(i, end);
        productMapper.batchInsert(batch);
    }
}

10.3 N+1查询问题

java 复制代码
// ❌ 错误:N+1查询
public List<OrderVO> getOrders(Long userId) {
    // 1次查询:查询订单列表
    List<Order> orders = orderMapper.selectByUserId(userId);

    List<OrderVO> result = new ArrayList<>();
    for (Order order : orders) {
        OrderVO vo = new OrderVO();
        vo.setOrder(order);

        // N次查询:查询每个订单的商品信息
        Product product = productMapper.selectById(order.getProductId());
        vo.setProduct(product);

        result.add(vo);
    }

    return result;  // 总共 1 + N 次查询
}

// ✅ 正确:一次查询
public List<OrderVO> getOrders(Long userId) {
    // 1次联表查询
    return orderMapper.selectOrdersWithProduct(userId);
}

// Mapper XML
<select id="selectOrdersWithProduct" resultMap="OrderVOMap">
    SELECT
        o.*,
        p.id as product_id,
        p.name as product_name,
        p.price as product_price
    FROM
        `order` o
    LEFT JOIN
        product p ON o.product_id = p.id
    WHERE
        o.user_id = #{userId}
</select>

10.4 索引优化

sql 复制代码
-- 1. 创建合适的索引
-- ❌ 错误:没有索引
SELECT * FROM order WHERE user_id = 1001 AND status = 'PAID';
-- 全表扫描,耗时 1000ms

-- ✅ 正确:创建联合索引
CREATE INDEX idx_user_status ON `order`(user_id, status);
-- 索引扫描,耗时 10ms

-- 2. 联合索引顺序(最左前缀原则)
CREATE INDEX idx_user_status_time ON `order`(user_id, status, create_time);

-- ✅ 能使用索引
SELECT * FROM order WHERE user_id = 1001;
SELECT * FROM order WHERE user_id = 1001 AND status = 'PAID';
SELECT * FROM order WHERE user_id = 1001 AND status = 'PAID' AND create_time > '2024-01-01';

-- ❌ 不能使用索引
SELECT * FROM order WHERE status = 'PAID';  -- 没有user_id
SELECT * FROM order WHERE create_time > '2024-01-01';  -- 没有user_id和status

-- 3. 覆盖索引(避免回表)
-- ❌ 需要回表
SELECT id, user_id, status, amount FROM order WHERE user_id = 1001;
-- 索引:idx_user_status(user_id, status)
-- 需要回表查询amount字段

-- ✅ 覆盖索引,不需要回表
CREATE INDEX idx_user_status_amount ON `order`(user_id, status, amount);
SELECT id, user_id, status, amount FROM order WHERE user_id = 1001;
-- 所有字段都在索引中,不需要回表

-- 4. 避免索引失效
-- ❌ 索引失效
SELECT * FROM order WHERE YEAR(create_time) = 2024;  -- 函数导致失效
SELECT * FROM order WHERE user_id + 1 = 1002;         -- 运算导致失效
SELECT * FROM order WHERE name LIKE '%手机%';          -- 前缀模糊查询失效

-- ✅ 正确使用索引
SELECT * FROM order WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';
SELECT * FROM order WHERE user_id = 1001;
SELECT * FROM order WHERE name LIKE '手机%';  -- 后缀模糊查询可以使用索引

10.5 线程池配置

java 复制代码
@Configuration
public class ThreadPoolConfig {

    @Bean("asyncExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        // 核心线程数 = CPU核心数 + 1
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() + 1);

        // 最大线程数 = CPU核心数 * 2
        executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);

        // 队列容量
        executor.setQueueCapacity(500);

        // 线程空闲时间(60秒)
        executor.setKeepAliveSeconds(60);

        // 线程名称前缀
        executor.setThreadNamePrefix("async-");

        // 拒绝策略:CallerRunsPolicy(调用者线程执行)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 等待任务完成后关闭
        executor.setWaitForTasksToCompleteOnShutdown(true);

        executor.initialize();
        return executor;
    }
}

// 使用示例
@Service
public class OrderService {

    @Async("asyncExecutor")
    public void sendNotification(Order order) {
        // 异步发送通知
        smsService.send(order.getUserId(), "订单创建成功");
    }
}

线程池参数如何配置?

复制代码
1. CPU密集型任务:
   核心线程数 = CPU核心数 + 1
   最大线程数 = CPU核心数 * 2

   示例:图片处理、视频编码

2. IO密集型任务:
   核心线程数 = CPU核心数 * 2
   最大线程数 = CPU核心数 * 4

   示例:数据库查询、HTTP请求

3. 混合型任务:
   核心线程数 = CPU核心数 + 1
   最大线程数 = CPU核心数 * 2
   队列容量 = 根据业务调整

11. 完整案例:电商秒杀系统

11.1 系统架构

复制代码
秒杀系统架构(高并发场景):

                    ┌──────────────┐
                    │   用户浏览器  │
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │   CDN静态化  │ ← 秒杀页面静态化
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │  Nginx限流   │ ← 单机10万QPS → 只放行1万QPS
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │  网关限流     │ ← 用户级限流(每用户5次/秒)
                    └──────┬───────┘
                           │
          ┌────────────────┼────────────────┐
    ┌─────▼─────┐   ┌─────▼─────┐   ┌─────▼─────┐
    │秒杀服务1  │   │秒杀服务2  │   │秒杀服务3  │
    └─────┬─────┘   └─────┬─────┘   └─────┬─────┘
          │               │               │
          └───────────────┼───────────────┘
                          │
                   ┌──────▼──────┐
                   │ Redis预减库存│ ← 内存级别,QPS百万级
                   └──────┬──────┘
                          │
                   ┌──────▼──────┐
                   │ 消息队列     │ ← 异步处理订单
                   └──────┬──────┘
                          │
                   ┌──────▼──────┐
                   │ 订单服务     │ ← 慢慢消费(1000QPS)
                   └──────┬──────┘
                          │
                   ┌──────▼──────┐
                   │ MySQL        │ ← 最终持久化
                   └──────────────┘

11.2 核心代码实现

秒杀接口
java 复制代码
@RestController
@RequestMapping("/seckill")
public class SeckillController {

    @Autowired
    private SeckillService seckillService;

    @Autowired
    private RedisRateLimiter rateLimiter;

    /**
     * 秒杀接口
     */
    @PostMapping("/{productId}")
    public Result<String> seckill(
        @PathVariable Long productId,
        @RequestHeader("userId") Long userId) {

        // 1. 用户级限流(每用户每秒5次)
        String rateLimitKey = "seckill:limit:user:" + userId;
        if (!rateLimiter.tryAcquire(rateLimitKey, 5, 1)) {
            return Result.fail("请求过于频繁,请稍后再试");
        }

        // 2. 参数校验
        if (productId == null || userId == null) {
            return Result.fail("参数错误");
        }

        // 3. 执行秒杀
        try {
            String orderId = seckillService.doSeckill(productId, userId);
            return Result.success(orderId);
        } catch (SeckillException e) {
            return Result.fail(e.getMessage());
        }
    }
}
秒杀业务逻辑
java 复制代码
@Service
public class SeckillService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RedissonClient redissonClient;

    private static final String STOCK_KEY_PREFIX = "seckill:stock:";
    private static final String USER_KEY_PREFIX = "seckill:user:";

    /**
     * 秒杀主流程
     */
    public String doSeckill(Long productId, Long userId) {
        String stockKey = STOCK_KEY_PREFIX + productId;
        String userKey = USER_KEY_PREFIX + productId + ":" + userId;

        // 1. 判断用户是否已经秒杀过(防止重复秒杀)
        Boolean hasOrdered = redisTemplate.hasKey(userKey);
        if (Boolean.TRUE.equals(hasOrdered)) {
            throw new SeckillException("您已经参与过该商品的秒杀");
        }

        // 2. Redis预减库存(Lua脚本保证原子性)
        Long stock = decrStock(stockKey);
        if (stock < 0) {
            // 库存不足
            throw new SeckillException("商品已售罄");
        }

        // 3. 记录用户已秒杀(防止重复)
        redisTemplate.opsForValue().set(userKey, "1", 24, TimeUnit.HOURS);

        // 4. 生成订单ID
        String orderId = generateOrderId();

        // 5. 发送消息到队列(异步创建订单)
        SeckillMessage message = new SeckillMessage();
        message.setOrderId(orderId);
        message.setProductId(productId);
        message.setUserId(userId);

        rabbitTemplate.convertAndSend("seckill.exchange", "seckill.order", message);

        // 6. 立即返回(不等待订单创建完成)
        return orderId;
    }

    /**
     * Redis预减库存(Lua脚本保证原子性)
     */
    private Long decrStock(String stockKey) {
        String luaScript =
            "local stock = redis.call('get', KEYS[1]) " +
            "if not stock then " +
            "  return -1 " +
            "end " +
            "if tonumber(stock) > 0 then " +
            "  return redis.call('decr', KEYS[1]) " +
            "else " +
            "  return -1 " +
            "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
        return redisTemplate.execute(redisScript, Collections.singletonList(stockKey));
    }

    private String generateOrderId() {
        // 雪花算法生成订单ID
        return String.valueOf(SnowflakeIdWorker.generateId());
    }

    /**
     * 初始化库存到Redis(秒杀开始前调用)
     */
    public void initStock(Long productId, Integer stock) {
        String stockKey = STOCK_KEY_PREFIX + productId;
        redisTemplate.opsForValue().set(stockKey, stock);
    }
}
订单消费者
java 复制代码
@Component
public class SeckillOrderConsumer {

    @Autowired
    private OrderService orderService;

    @Autowired
    private StockService stockService;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @RabbitListener(queues = "seckill.order.queue")
    public void handleSeckillOrder(SeckillMessage message) {
        try {
            // 1. 创建订单
            Order order = new Order();
            order.setId(message.getOrderId());
            order.setUserId(message.getUserId());
            order.setProductId(message.getProductId());
            order.setStatus("UNPAID");

            orderService.createOrder(order);

            // 2. 扣减数据库库存(最终一致性)
            stockService.deductStock(message.getProductId(), 1);

            log.info("秒杀订单创建成功:{}", message.getOrderId());

        } catch (Exception e) {
            log.error("秒杀订单创建失败:{}", message, e);

            // 3. 失败则恢复Redis库存
            String stockKey = "seckill:stock:" + message.getProductId();
            redisTemplate.opsForValue().increment(stockKey);

            // 4. 删除用户秒杀记录
            String userKey = "seckill:user:" + message.getProductId() + ":" + message.getUserId();
            redisTemplate.delete(userKey);
        }
    }
}
数据库库存扣减(防止超卖)
java 复制代码
@Service
public class StockService {

    @Autowired
    private StockMapper stockMapper;

    /**
     * 扣减库存(防止超卖)
     */
    @Transactional
    public void deductStock(Long productId, Integer quantity) {
        // 使用乐观锁防止超卖
        int rows = stockMapper.deductStockWithOptimisticLock(productId, quantity);

        if (rows == 0) {
            throw new StockException("库存不足");
        }
    }
}

// Mapper
@Mapper
public interface StockMapper {

    /**
     * 乐观锁扣减库存(防止超卖)
     */
    @Update("UPDATE stock SET stock = stock - #{quantity}, version = version + 1 " +
            "WHERE product_id = #{productId} AND stock >= #{quantity} AND version = #{version}")
    int deductStockWithOptimisticLock(
        @Param("productId") Long productId,
        @Param("quantity") Integer quantity);

    /**
     * 悲观锁扣减库存(性能较差,但绝对安全)
     */
    @Select("SELECT * FROM stock WHERE product_id = #{productId} FOR UPDATE")
    Stock selectForUpdate(@Param("productId") Long productId);
}

11.3 秒杀优化技巧总结

复制代码
秒杀优化技巧(10万QPS → 100QPS):

1. 前端优化:
   ✅ 静态页面(CDN缓存)
   ✅ 按钮防重复点击
   ✅ 验证码(防止机器人)

2. 网关层优化:
   ✅ Nginx限流(10万 → 1万)
   ✅ 网关限流(用户级)

3. 服务层优化:
   ✅ Redis预减库存(QPS百万级)
   ✅ 布隆过滤器(防止缓存穿透)
   ✅ 分布式锁(防止超卖)

4. 异步处理:
   ✅ 消息队列(削峰填谷,1万 → 1000)
   ✅ 订单异步创建

5. 数据库优化:
   ✅ 乐观锁(防止超卖)
   ✅ 索引优化
   ✅ 读写分离

最终效果:
- 前端QPS:100万(静态页面,CDN承载)
- 后端QPS:1万(限流后)
- Redis QPS:1万(预减库存)
- 消息队列:1万入队,1000出队(削峰)
- 数据库QPS:1000(异步处理)

12. 性能指标与监控

12.1 关键性能指标

java 复制代码
@Component
public class PerformanceMonitor {

    private final MeterRegistry meterRegistry;

    public PerformanceMonitor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    /**
     * 记录请求耗时
     */
    public void recordRequestTime(String api, long duration) {
        Timer.builder("http.request.duration")
            .tag("api", api)
            .register(meterRegistry)
            .record(duration, TimeUnit.MILLISECONDS);
    }

    /**
     * 记录QPS
     */
    public void recordQPS(String api) {
        Counter.builder("http.request.count")
            .tag("api", api)
            .register(meterRegistry)
            .increment();
    }

    /**
     * 记录错误率
     */
    public void recordError(String api, String errorType) {
        Counter.builder("http.request.error")
            .tag("api", api)
            .tag("error_type", errorType)
            .register(meterRegistry)
            .increment();
    }
}

// 使用示例
@RestController
public class OrderController {

    @Autowired
    private PerformanceMonitor monitor;

    @PostMapping("/order")
    public Result createOrder(@RequestBody Order order) {
        long startTime = System.currentTimeMillis();

        try {
            // 处理业务
            orderService.createOrder(order);

            // 记录QPS
            monitor.recordQPS("/order");

            return Result.success();

        } catch (Exception e) {
            // 记录错误
            monitor.recordError("/order", e.getClass().getSimpleName());
            throw e;

        } finally {
            // 记录耗时
            long duration = System.currentTimeMillis() - startTime;
            monitor.recordRequestTime("/order", duration);
        }
    }
}

12.2 监控大盘

复制代码
Grafana监控大盘:

┌─────────────────────────────────────────────┐
│           系统监控大盘                       │
├─────────────────────────────────────────────┤
│  QPS: 15000 ↑                               │
│  ├─ 秒杀接口: 12000                          │
│  ├─ 订单接口: 2000                           │
│  └─ 商品接口: 1000                           │
├─────────────────────────────────────────────┤
│  响应时间(P99): 150ms ✓                      │
│  ├─ 秒杀接口: 100ms                          │
│  ├─ 订单接口: 200ms                          │
│  └─ 商品接口: 120ms                          │
├─────────────────────────────────────────────┤
│  错误率: 0.5% ✓                              │
│  ├─ 超时错误: 0.3%                           │
│  └─ 业务错误: 0.2%                           │
├─────────────────────────────────────────────┤
│  系统资源:                                   │
│  ├─ CPU使用率: 65% ✓                        │
│  ├─ 内存使用率: 70% ✓                       │
│  ├─ 磁盘IO: 45% ✓                           │
│  └─ 网络带宽: 500Mbps / 1Gbps ✓             │
├─────────────────────────────────────────────┤
│  缓存命中率:                                 │
│  ├─ Redis: 95% ✓                            │
│  ├─ 本地缓存: 80% ✓                         │
│  └─ CDN: 90% ✓                              │
├─────────────────────────────────────────────┤
│  数据库:                                     │
│  ├─ 连接池使用率: 60% ✓                     │
│  ├─ 慢查询数量: 2 ⚠️                        │
│  └─ 主从延迟: 0.1s ✓                        │
└─────────────────────────────────────────────┘

告警规则:
✅ 正常:绿色
⚠️ 警告:黄色(需要关注)
❌ 严重:红色(立即处理)

13. 面试高频问题

Q1: 如何设计一个高并发系统?

完整回答模板

复制代码
我会从以下几个层面来设计高并发系统:

1. 架构层面(Scale Out):
   - 采用微服务架构,服务水平扩展
   - 无状态服务设计,便于负载均衡
   - 使用Nginx/Gateway做负载均衡

2. 缓存层面(减少DB压力):
   - 多级缓存:浏览器 → CDN → Nginx → 本地缓存 → Redis
   - 缓存预热:提前加载热点数据
   - 解决缓存三大问题:穿透、击穿、雪崩

3. 数据库层面:
   - 读写分离(主库写,从库读)
   - 分库分表(水平拆分)
   - 索引优化、SQL优化
   - 连接池配置优化

4. 异步处理:
   - 消息队列削峰填谷(RabbitMQ/Kafka)
   - 核心业务同步,次要业务异步

5. 限流降级:
   - 限流:令牌桶、滑动窗口
   - 降级:关闭次要功能
   - 熔断:快速失败,避免雪崩

6. 代码层面:
   - 避免大事务
   - 批量操作
   - 线程池复用
   - 索引优化

实际案例:
我们做过一个秒杀系统,峰值QPS 10万,通过上述优化,
最终实现了99.9%的成功率和平均100ms的响应时间。

Q2: Redis和MySQL如何保证数据一致性?

回答

复制代码
我们采用 Cache Aside Pattern(旁路缓存)策略:

读操作:
1. 先查Redis
2. 未命中则查MySQL
3. 写入Redis

写操作:
1. 先更新MySQL
2. 再删除Redis缓存

为什么是"先更新DB,再删除缓存"?
- 如果"先删缓存,再更新DB",可能出现:
  t1: A删除缓存
  t2: B读取缓存(未命中)
  t3: B读取DB(旧数据)
  t4: B写入缓存(旧数据)
  t5: A更新DB(新数据)
  结果:缓存是旧数据❌

- "先更新DB,再删除缓存":
  t1: A更新DB
  t2: A删除缓存
  t3: B读取缓存(未命中)
  t4: B读取DB(新数据)
  t5: B写入缓存(新数据)
  结果:一致✅

进阶方案:延迟双删
1. 删除缓存
2. 更新DB
3. 延迟500ms再删除缓存(兜底)

终极方案:订阅MySQL Binlog(Canal)
异步更新缓存,保证最终一致性

Q3: 如何防止缓存穿透、击穿、雪崩?

回答

复制代码
1. 缓存穿透(查询不存在的数据):
   问题:恶意请求不存在的数据,绕过缓存直接打DB

   解决方案:
   ① 布隆过滤器:提前判断数据是否存在
   ② 缓存空对象:不存在的数据也缓存(TTL较短)

2. 缓存击穿(热点key过期):
   问题:热点key过期瞬间,大量请求打到DB

   解决方案:
   ① 互斥锁:只允许一个线程查DB,其他线程等待
   ② 逻辑过期:热点数据永不过期,异步更新
   ③ 热点数据永不过期

3. 缓存雪崩(大量key同时过期):
   问题:大量key同时过期,请求全部打到DB

   解决方案:
   ① 过期时间加随机值(30分钟 + 随机0-5分钟)
   ② Redis集群 + 持久化
   ③ 限流降级(保底方案)

Q4: 秒杀系统如何防止超卖?

回答

复制代码
防止超卖的多重保障:

1. Redis预减库存(第一道防线):
   - 使用Lua脚本保证原子性
   - 库存不足直接返回,不查DB

2. 消息队列(削峰):
   - 秒杀成功的请求进入队列
   - 慢慢消费,避免DB压力

3. 数据库乐观锁(最后防线):
   UPDATE stock
   SET stock = stock - 1, version = version + 1
   WHERE product_id = ?
     AND stock > 0
     AND version = ?

   - 版本号不匹配则更新失败
   - 绝对不会超卖

4. 分布式锁(可选):
   - 使用Redisson分布式锁
   - 同一商品同一时刻只有一个线程扣库存

实际效果:
我们的秒杀系统支持10万QPS,通过上述方案,
实现了0超卖、99.9%成功率。

Q5: 分库分表后如何查询?

回答

复制代码
分库分表后的查询策略:

1. 带分片键的查询(推荐):
   SELECT * FROM order WHERE user_id = 1001 AND order_id = 123
   - 直接路由到对应分片(ds1.t_order_9)
   - 性能最优

2. 不带分片键的查询(避免):
   SELECT * FROM order WHERE order_id = 123
   - 需要查询所有分片(全表扫描)
   - 性能很差

   解决方案:
   ① 冗余分片键:在其他需要查询的地方也存储user_id
   ② 使用ES:将订单数据同步到ES,支持任意字段查询
   ③ 分库分表中间件:ShardingSphere自动聚合结果

3. 跨分片聚合查询:
   SELECT COUNT(*) FROM order

   解决方案:
   ① 分别查询每个分片,应用层聚合
   ② 使用数据仓库(OLAP):Hive、ClickHouse

4. 分布式事务:
   订单服务 + 库存服务

   解决方案:
   ① TCC(Try-Confirm-Cancel)
   ② Saga(长事务)
   ③ 本地消息表 + 定时任务

总结

高并发系统的核心思想:

复制代码
1. 能缓存就缓存(Cache)
2. 能异步就异步(Async)
3. 能拆分就拆分(Partition)
4. 能限流就限流(Rate Limit)

┌─────────────────────────────────────┐
│       高并发系统 = 削峰 + 限流        │
│                                     │
│  请求洪峰: 10万QPS                   │
│     ↓                               │
│  ① CDN + 静态化(9万被拦截)          │
│     ↓                               │
│  ② Nginx限流(8000被拦截)            │
│     ↓                               │
│  ③ Redis缓存(命中率95%)             │
│     ↓                               │
│  ④ 消息队列削峰(1万入队 → 1000出队) │
│     ↓                               │
│  ⑤ 数据库承载: 1000 QPS ✅            │
└─────────────────────────────────────┘

记住这个公式

复制代码
高并发能力 =
  (系统吞吐量 × 缓存命中率)
  ÷
  (平均响应时间 × (1 - 降级比例))

提升方向:
↑ 增加系统吞吐量(水平扩展)
↑ 提高缓存命中率(多级缓存)
↓ 降低响应时间(异步、优化SQL)
↑ 增加降级比例(关闭次要功能)
相关推荐
今天只学一颗糖9 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
若风的雨13 小时前
【deepseek】RT-Thread 对 PCIe 的支持情况
系统架构
我只会写Bug啊17 小时前
【软考】系统架构设计师-论文范文(三)
系统架构·软考·系统架构师·系统分析师·十大管理·信息项目管理工程师
我只会写Bug啊18 小时前
【软考】系统架构设计师-论文范文(二)
系统架构·软考·系统分析师·十大管理·信息项目管理
AI周红伟1 天前
周红伟:ChatADS聊天广告,OpenAI广告系统架构设计:实时投放与访问扩展的工程实现
系统架构
C澒2 天前
电商供应链履约中台架构与业务全流程解析
前端·架构·系统架构·教育电商·交通物流
愿你天黑有灯下雨有伞2 天前
高效异步处理:基于RocketMQ的延迟消费系统架构全解析
系统架构·rocketmq
知识即是力量ol2 天前
亿级日活背后的技术挑战:如何构建高并发通用计数系统?——《亿级流量系统架构设计与实战》
redis·系统架构·高并发·计数系统·亿级日活
我只会写Bug啊2 天前
【软考】系统架构设计师-论文范文(一)
大数据·系统架构·信息系统项目管理师·架构设计·系统分析师
AIagenttest2 天前
2026年招聘系统架构演进:为何视觉语义智能体正在取代传统爬虫?(含ROI算力模型分析)
爬虫·系统架构