第三部分:緩存雪崩——大量key失效引發的“系統性崩潰”
緩存雪崩的本質是“大量緩存key在同一時間失效,或緩存集群整體故障”,導致請求全量穿透至DB,引發“系統性崩潰”。
案例4:電商首頁的“批量過期”災難
故障現場
某電商平臺首頁緩存架構為“Redis集群+MySQL”,所有首頁商品緩存key(home:item:*
)設置相同過期時間(2小時),每日凌晨2點批量更新緩存。
- 故障:某日凌晨2點,緩存批量過期,首頁訪問量(5000QPS)全量穿透至MySQL,DB連接池瞬間耗盡,首頁無法訪問,連帶訂單、支付等核心服務因依賴首頁接口超時,引發系統性崩潰。
根因解剖
- 所有熱點key設置相同過期時間,導致“時間點共振”;
- 緩存更新采用“先刪除舊緩存,再更新DB,最后寫入新緩存”的方式,存在“更新窗口”內的穿透風險;
- 未設置多級緩存,Redis故障后無降級方案。
四層防御方案落地
方案1:過期時間“隨機化”
核心邏輯:對同一類key設置基礎過期時間+隨機偏移量(如2小時±10分鐘),避免“時間點共振”。
實戰代碼:
@Service
public class HomeCacheService {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate ItemMapper itemMapper;// 基礎過期時間(2小時)private static final long BASE_TTL = 7200;// 隨機偏移量范圍(±10分鐘,即600秒)private static final int RANDOM_OFFSET = 600;/*** 更新首頁商品緩存(帶隨機過期時間)*/public void updateHomeItems() {List<ItemDTO> items = itemMapper.listHomeItems();// 生成隨機數生成器Random random = new Random();for (ItemDTO item : items) {String cacheKey = "home:item:" + item.getId();// 計算隨機過期時間(BASE_TTL ± RANDOM_OFFSET)long ttl = BASE_TTL + (random.nextInt(2 * RANDOM_OFFSET) - RANDOM_OFFSET);redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(item), ttl, TimeUnit.SECONDS);}}
}
效果:緩存過期時間分散在110-130分鐘,避免批量過期,DB峰值QPS從5000降至1500。
方案2:多級緩存架構
核心邏輯:構建“本地緩存(Caffeine)→ Redis → DB”的三級緩存,即使Redis失效,本地緩存仍能攔截部分流量。
實戰代碼:
@Service
public class HomeItemService {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate ItemMapper itemMapper;// 本地緩存(Caffeine):首頁商品緩存10分鐘private final LoadingCache<String, ItemDTO> localCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(1000).build(this::loadFromRedis);/*** 查詢首頁商品(三級緩存)*/public ItemDTO getHomeItem(Long itemId) {String key = "home:item:" + itemId;try {// 1. 查詢本地緩存return localCache.get(key);} catch (Exception e) {log.warn("本地緩存未命中,itemId={}", itemId, e);// 2. 本地緩存失效,直接查詢DB并更新各級緩存ItemDTO item = itemMapper.selectById(itemId);if (item != null) {// 更新RedisredisTemplate.opsForValue().set(key, JSON.toJSONString(item), getRandomTtl(), TimeUnit.SECONDS);// 更新本地緩存localCache.put(key, item);}return item;}}// 從Redis加載(Caffeine的加載函數)private ItemDTO loadFromRedis(String key) {String redisVal = redisTemplate.opsForValue().get(key);return redisVal != null ? JSON.parseObject(redisVal, ItemDTO.class) : null;}// 隨機過期時間(同方案1)private long getRandomTtl() { ... }
}
架構圖:
[用戶請求] → [本地緩存(Caffeine)] → [Redis集群] → [MySQL]↓ ↓ ↓10分鐘過期 2小時±10分鐘 最終數據源
實戰效果:Redis集群故障時,本地緩存攔截60%的請求,DB查詢量從5000QPS降至2000QPS,系統未崩潰。
方案3:緩存更新“先更新后刪除”
核心邏輯:將“刪除舊緩存→更新DB→寫入新緩存”改為“更新DB→寫入新緩存→刪除舊緩存”,避免更新窗口內的穿透。
實戰代碼:
@Transactional(rollbackFor = Exception.class)
public void updateItem(ItemDTO item) {// 1. 先更新DBitemMapper.updateById(item);// 2. 寫入新緩存(帶新版本號)String newKey = "item:v2:" + item.getId();redisTemplate.opsForValue().set(newKey, JSON.toJSONString(item), getRandomTtl(), TimeUnit.SECONDS);// 3. 異步刪除舊緩存(避免阻塞主流程)CompletableFuture.runAsync(() -> {String oldKey = "item:v1:" + item.getId();redisTemplate.delete(oldKey);});
}
效果:緩存更新窗口從1秒縮短至10ms,穿透風險降低99%。
方案4:Redis高可用+熔斷降級
核心邏輯:
- Redis集群部署(主從+哨兵),確保單點故障不影響整體;
- 對Redis操作設置熔斷(如Resilience4j),故障時快速降級。
實戰代碼(Redis熔斷):
@Service
public class RedisServiceWithFallback {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate CircuitBreakerRegistry circuitBreakerRegistry;/*** 帶熔斷的Redis查詢*/public String getWithFallback(String key) {CircuitBreaker breaker = circuitBreakerRegistry.circuitBreaker("redisGet");return Try.ofSupplier(CircuitBreaker.decorateSupplier(breaker, () -> redisTemplate.opsForValue().get(key))).recover(Exception.class, e -> {log.warn("Redis查詢熔斷,key={}", key, e);return null; // 熔斷時返回null,觸發后續降級邏輯}).get();}
}
實戰效果:Redis集群單節點故障時,哨兵自動切換(30秒內),熔斷機制確保故障期間接口不超時,系統可用性達99.99%。
雪崩防御總結
方案 | 適用場景 | 優點 | 缺點 | 實施成本 |
---|---|---|---|---|
過期時間隨機化 | 批量緩存場景 | 實現簡單,無額外依賴 | 無法解決集群故障問題 | 低 |
多級緩存 | 核心業務保護 | 多一層防護,性能好 | 一致性維護復雜 | 中 |
更新策略優化 | 緩存頻繁更新場景 | 減少更新窗口穿透 | 需要版本號管理 | 中 |
高可用+熔斷 | 集群級故障防護 | 兜底保障,可用性高 | 運維成本高 | 高 |
實戰總覽:緩存故障防御決策樹
面對緩存三大劫,需根據業務場景選擇合適方案,以下決策樹可快速定位防御策略:
-
是否為高頻無效key?
→ 是 → 布隆過濾器+緩存空值
→ 否 → 進入下一步 -
是否存在熱點key?
→ 是 → 邏輯永不過期/分布式鎖
→ 否 → 進入下一步 -
是否有批量過期風險?
→ 是 → 過期時間隨機化+多級緩存
→ 否 → 進入下一步 -
是否需極端場景保護?
→ 是 → 限流+熔斷降級
→ 否 → 基礎緩存策略
緩存防御的核心不是“消滅問題”,而是“控制風險”。通過多層防御體系,將故障影響控制在可接受范圍,同時平衡性能、一致性和開發成本,才是實戰中的最優解。記住:最好的防御方案,永遠是最適合業務場景的方案。