百万并发下的生存艺术:SpringBoot遇上缓存与消息队列的奇幻漂流

77 阅读5分钟

💥 ​​一、开篇暴击:为什么你的系统在流量洪峰前瑟瑟发抖?​

​1.1 青铜选手的日常:裸奔的数据库之殇​

public void seckill(Long itemId) {  
    Item item = itemMapper.selectById(itemId);  // 百万请求瞬间击穿MySQL  
    if (item.getStock() > 0) {  
        item.setStock(item.getStock() - 1);  
        itemMapper.updateById(item);  // 库存扣减引发锁竞争雪崩  
    }  
}  

​灵魂拷问​​:

当库存只剩1件时,100个请求同时查库看到库存>0 → 全部执行扣减 → ​​超卖99件​​!

​1.2 黄金觉悟:Redis缓存屏障的魔法​

sequenceDiagram  
    用户->>+Redis: 查询库存缓存  
    Redis-->>-用户: 返回库存数  
    用户->>Redis: 执行DECR(原子操作)  
    Redis->>Kafka: 发送扣减消息  
    Kafka->>MySQL: 异步落库保证最终一致  

​核心逻辑​​:

  • Redis原子操作防超卖
  • Kafka异步解耦保性能

​1.3 王者思维:分层削峰四重奏​

用户请求 → Nginx限流 → Redis库存屏障 → Kafka异步落库 → MySQL最终一致  

📌 ​​真理​​:​​让专业的人干专业的事​​,Redis抗瞬时流量,MySQL保数据安全


🛡️ ​​二、Redis缓存三大生死劫与渡劫指南​

​2.1 缓存击穿:热点Key的“定点爆破”​

​场景​​:某明星商品缓存过期瞬间,10万请求直冲数据库!
​逃生代码​​:

public Item getItem(Long id) {  
    String key = "item:" + id;  
    Item item = redisTemplate.opsForValue().get(key);  
    if (item == null) {  
        // 分布式锁防并发重建  
        if (redisTemplate.opsForValue().setIfAbsent("lock:"+id, "1", 30, TimeUnit.SECONDS)) {  
            try {  
                item = dbMapper.selectById(id);  
                redisTemplate.opsForValue().set(key, item, 5, TimeUnit.MINUTES);  
            } finally {  
                redisTemplate.delete("lock:"+id);  
            }  
        } else {  
            Thread.sleep(100);  // 未抢到锁的线程休眠重试  
            return getItem(id);  
        }  
    }  
    return item;  
}  

​2.2 缓存雪崩:集体过期的“末日审判”​

​解决方案​​:

spring:  
  cache:  
    redis:  
      time-to-live: 300000  # 基础5分钟  
      randomized-ttl-offset: 60000 # ±1分钟随机偏移  

💡 ​​效果​​:避免大量Key同时失效,流量均匀分散

​2.3 数据不一致:缓存与DB的“离婚大战”​

​黄金法则​​:

graph LR  
    A[更新DB] --> B[删除缓存]  
    C[读请求] --> D{缓存存在?}  
    D -->|是| E[返回缓存]  
    D -->|否| F[查DB并重建缓存]  

​关键点​​:

  1. 写操作:​​先更DB再删缓存​
  2. 读操作:​​串行化重建防击穿​

⚡ ​​三、Kafka:把百万并发揉成面团的太极大师​

​3.1 分区策略:并发压力的“分筋错骨手”​

​脑图逻辑​​:

生产者 → 按用户ID哈希分区 →  
   分区1 → 消费者实例A  
   分区2 → 消费者实例B  
   分区3 → 消费者实例C  

​实战代码​​:

public class UserPartitioner implements Partitioner {  
    @Override  
    public int partition(String topic, Object key, Cluster cluster) {  
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);  
        return Math.abs(key.hashCode()) % partitions.size();  // 用户级保序  
    }  
}  

​3.2 顺序消费:秒杀订单的“先来后到”​

​关键配置​​:

spring:  
  kafka:  
    consumer:  
      max-poll-records: 1 # 一次拉1条保顺序  
      isolation-level: read_committed  
    listener:  
      ack-mode: manual # 手动提交防丢失  

​3.3 死信队列:失败消息的“VIP休息室”​

@KafkaListener(topics = "orders")  
public void consume(Order order, Acknowledgment ack) {  
    try {  
        processOrder(order);  
        ack.acknowledge();  // 手动提交  
    } catch (Exception e) {  
        kafkaTemplate.send("order_dlt", order); // 死信队列  
        ack.acknowledge(); // 避免阻塞  
    }  
}  

🍲 ​​四、神操作:缓存+消息队列的“鸳鸯火锅”架构​

​4.1 写操作:异步解耦流水线​

sequenceDiagram  
    用户->>Redis: 原子扣减库存  
    Redis->>Kafka: 发送事件  
    Kafka->>消费者1: 同步ES索引  
    Kafka->>消费者2: 更新MySQL  
    Kafka->>消费者3: 发通知  

​优势​​:

  • Redis响应<2ms
  • MySQL更新延迟≈100ms

​4.2 读操作:多级缓存“俄罗斯套娃”​

用户请求 → Nginx本地缓存 → Redis集群 → 回源DB  

​Spring配置​​:

caffeine:  
  spec: maximumSize=1000,expireAfterWrite=60s  
redis:  
  timeToLive: 30m  

​4.3 熔断降级:系统的“金钟罩铁布衫”​

@HystrixCommand(  
  fallbackMethod = "getItemFallback",  
  commandProperties = {  
    @HystrixProperty(name="circuitBreaker.requestVolumeThreshold", value="20")  
  }  
)  
public Item getItemWithProtection(Long id) {  
    // 业务逻辑  
}  

public Item getItemFallback(Long id) {  
    return new Item("服务降级中,请稍后重试"); // 优雅兜底  
}  

🔧 ​​五、性能调优:从参数到硬件的终极奥义​

​5.1 Redis内存优化:从挥霍到抠门​

​神操作​​:

# 大Value拆分  
HMSET user:1001 name "Jack" age 30 address "New York"  

# ZSET压缩存储  
ZADD user:1001:login 1717043200 "2024-05-30"  

​5.2 Kafka磁盘黑科技:顺序写+零拷贝​

​传统 vs Kafka​​:

传统写盘:  
  数据 → 页缓存 → 用户空间 → Socket缓冲区 → 网卡  

Kafka零拷贝:  
  数据 → 页缓存 → 网卡(省2次拷贝+2次上下文切换)  

​5.3 MySQL索引玄学:最左前缀的“防坑指南”​

​索引失效案例​​:

-- 索引: idx_user_order(user_id, status, create_time)  
SELECT * FROM orders  
WHERE user_id=1001  
  AND create_time > '2025-05-01'; -- 跳status字段 → 索引失效!  

-- 正确写法:  
SELECT * FROM orders  
WHERE user_id=1001  
  AND status IN (1,2,3) -- 补齐中间字段  
  AND create_time > '2025-05-01';  

👁️ ​​六、监控体系:给系统装上“天眼通”​

​6.1 Redis监控三件套​

​指标​​报警阈值​​检查命令​
内存使用率>85%redis-cli info memory
缓存命中率<90%info stats
慢查询数量>5个/分钟slowlog get 10

​6.2 Kafka Lag风暴预警​

kafka-consumer-groups.sh --bootstrap-server kafka:9092 \  
  --describe --group order_group | grep "LAG"  

​自动化处理​​:

if lag > 10000:  
    k8s.scale_consumer(group_id, current_pods + 2) # 自动扩容  

​6.3 全链路追踪:SkyWalking的上帝视角​

用户请求 → Gateway → SpringBoot → Redis → Kafka → MySQL  
              ↓  
可视化链路(精确到每个组件耗时)  

📌 ​​价值​​:定位慢调用如Redis从1ms→100ms


💎 ​​结语:高并发世界的生存法则​

曾经我也以为 synchronized 能搞定一切,直到遇见:

  • Redis集群脑裂导致脏读(凌晨3点被报警吓醒)
  • Kafka生产者缓冲区溢出引发消息丢失(对账差10万元)
  • MySQL索引失效让CPU飙到100%(DBA提着刀来找我)
    如今终于悟了:​​技术没有银弹,唯敬畏每一行代码,紧盯每一次监控!​

​附录:高并发架构脑图​

用户 → Nginx(限流) → SpringBoot(熔断)  
                   → Redis(缓存屏障)  
                   → Kafka(异步削峰) → MySQL(最终一致)  
                   ↗ Elasticsearch(搜索)  
                   ↘ MongoDB(日志)  

​转载声明​​:原创不易,侵权必究!本文部分案例参考自美团/阿里实战