1. Cache Aside Pattern(旁路緩存模式)??
?核心思想?:應用代碼直接管理緩存與數據的同步,分為讀寫兩個流程:
- ?讀取數據?:
- 先查本地緩存(如 Guava Cache)。
- 若本地未命中,則查 Redis。
- 若 Redis 也未命中,則從數據庫加載數據,并回填到本地緩存和 Redis。
- ?寫入數據?:
- 直接更新數據庫。
- ?刪除本地緩存和 Redis 中的相關數據?(避免舊數據殘留)。
// 偽代碼示例
public Data getData(String key) {// 1. 查本地緩存Data data = localCache.getIfPresent(key);if (data != null) return data;// 2. 查 Redisdata = redis.get(key);if (data != null) {// 回填本地緩存localCache.put(key, data);return data;}// 3. 從數據庫加載data = db.load(key);if (data != null) {localCache.put(key, data);redis.set(key, data);}return data;
}public void updateData(String key, Data newData) {// 1. 更新數據庫db.update(newData);// 2. 刪除緩存(本地 + Redis)localCache.invalidate(key);redis.delete(key);
}
?優點?:實現簡單,適用于讀多寫少的場景。
?缺點?:存在短暫不一致窗口(如寫入后需等待緩存過期)。
?2. 發布訂閱模式(Pub/Sub)??
?適用場景?:分布式系統中,多個應用實例需要同步緩存狀態。
?方案?:
- 當數據更新時,發送消息到消息隊列(如 Redis 的 Pub/Sub 或 Kafka)。
- 所有訂閱該主題的應用實例監聽到消息后,主動刪除本地緩存和 Redis 中的舊數據。
// 發布消息示例(更新數據時)
public void updateData(String key, Data newData) {db.update(newData);// 刪除 Redis 緩存redis.delete(key);// 發布失效事件redis.publish("cache-invalidation", key);
}// 訂閱消息示例(各實例啟動時訂閱)
redis.subscribe("cache-invalidation", (channel, message) -> {localCache.invalidate(message); // 刪除本地緩存
});
?優點?:解耦緩存失效邏輯,適合分布式系統。
?缺點?:消息可能丟失或延遲,需處理冪等性。
?3. 過期時間策略?
?核心思想?:為緩存設置合理的 TTL(Time-To-Live),依賴自動過期減少不一致時間窗口。
?適用場景?:對實時一致性要求不高,允許最終一致性的場景。
?優化點?:
- 通過隨機 TTL 避免緩存雪崩。
- 結合主動失效(如更新時刪除)縮短過期時間。
// 本地緩存設置 TTL
Cache<String, Data> cache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES) // 本地緩存 5 分鐘過期.build();// Redis 設置 TTL
redis.setex(key, 300, data); // Redis 緩存 5 分鐘過期
?4. 雙刪策略(Double Delete)??
?適用場景?:高并發寫操作場景,減少緩存臟數據。
?流程?:
- 更新數據庫前,先刪除緩存。
- 更新數據庫后,延遲一段時間再次刪除緩存(防止并發讀寫導致的臟數據)。
public void updateData(String key, Data newData) {// 第一步:刪除緩存localCache.invalidate(key);redis.delete(key);// 更新數據庫db.update(newData);// 第二步:延遲刪除(如通過異步線程)scheduleDelete(key, 1000); // 1 秒后再次刪除
}
?5. 讀寫鎖(Read-Write Lock)??
?核心思想?:通過鎖機制保證讀寫操作的原子性。
?方案?:
- 寫操作時加鎖,阻止其他讀寫操作。
- 讀操作時加讀鎖,允許多個并發讀取。
// 使用 Redis 分布式鎖(示例)
public void updateDataWithLock(String key, Data newData) {String lockKey = "lock:" + key;boolean locked = redis.setnx(lockKey, "locked", 10, TimeUnit.SECONDS);if (locked) {try {// 更新數據庫db.update(newData);// 刪除緩存localCache.invalidate(key);redis.delete(key);} finally {redis.del(lockKey);}} else {// 獲取鎖失敗,重試或返回錯誤}
}
?6. 本地緩存與 Redis 的協同設計?
- ?分層緩存?:本地緩存作為一級緩存,Redis 作為二級緩存。
- 優先從本地緩存讀取,未命中則查詢 Redis。
- 更新時同步刪除兩級緩存。
- ?熱點數據管理?:對熱點數據設置更短的 TTL 或主動推送更新。
?關鍵注意事項?
- ?最終一致性?:多數場景下無需強一致,允許短暫延遲。
- ?緩存穿透?:對空值或無效 Key 也進行緩存(如設置短 TTL)。
- ?緩存雪崩?:設置隨機 TTL,避免大量緩存同時失效。
- ?監控與告警?:通過統計信息(如?
cache.stats()
)監控命中率、延遲等指標。
?總結方案選擇?
?場景? | ?推薦方案? |
---|---|
單機應用,低并發 | Cache Aside + TTL |
分布式系統,多實例 | Pub/Sub + Cache Aside |
高并發寫操作 | 雙刪策略 + 分布式鎖 |
允許最終一致性 | TTL 過期 + 異步更新 |
通過結合業務需求,靈活采用上述策略,可以有效降低本地緩存與 Redis 的不一致風險。