程序員面試資料大全|各種技術書籍等資料-1000G
IDEA開發工具- FREE
一、雙寫一致性問題本質
在分布式系統中,緩存與數據庫雙寫一致性指當數據被修改時,如何確保緩存(如Redis)和數據庫(如MySQL)中的數據保持同步。核心挑戰在于處理并發操作和系統故障場景下的數據一致性問題。
典型不一致場景
二、主流解決方案對比
方案 | 適用場景 | 優點 | 缺點 | 一致性強度 |
---|---|---|---|---|
Cache-Aside | 讀多寫少 | 簡單易實現 | 存在不一致時間窗口 | 最終一致 |
Write-Through | 寫密集型 | 強一致性保證 | 性能損耗大 | 強一致 |
Write-Behind | 高吞吐場景 | 高性能 | 數據丟失風險 | 最終一致 |
雙刪策略 | 高一致性要求 | 減少不一致窗口 | 實現復雜 | 強一致 |
三、核心解決方案詳解
方案1:Cache-Aside(旁路緩存)
最佳實踐:讀多寫少場景
關鍵實現代碼:
public void updateData(Data data) {// 1. 更新數據庫dataDao.update(data);// 2. 刪除緩存redis.del(data.getId());
}public Data getData(String id) {// 1. 從緩存獲取Data data = redis.get(id);if (data != null) {return data;}// 2. 從數據庫讀取data = dataDao.get(id);// 3. 寫入緩存(設置過期時間)redis.setex(id, 300, data);return data;
}
方案2:Write-Through(穿透寫入)
最佳實踐:強一致性要求場景
特點:
- 緩存層作為數據庫代理
- 所有寫操作同步更新緩存和數據庫
- 讀操作只訪問緩存
方案3:Write-Behind(異步回寫)
最佳實踐:高吞吐場景
實現代碼示例:
// 使用內存隊列實現異步更新
private BlockingQueue<Data> writeQueue = new LinkedBlockingQueue<>();public void updateData(Data data) {// 1. 更新緩存redis.set(data.getId(), data);// 2. 加入異步隊列writeQueue.offer(data);
}// 單獨的消費者線程
class DbWriter implements Runnable {public void run() {while (true) {Data data = writeQueue.take();dataDao.update(data); // 批量更新優化}}
}
方案4:雙刪策略(Double Delete)
最佳實踐:高一致性要求場景
實現代碼:
public void updateDataWithDoubleDelete(Data data) {// 1. 首次刪除緩存redis.del(data.getId());// 2. 更新數據庫dataDao.update(data);// 3. 延遲二次刪除executor.schedule(() -> {redis.del(data.getId());}, 500, TimeUnit.MILLISECONDS); // 500ms延遲
}
四、高級一致性保障方案
方案1:分布式事務(強一致)
實現技術:
- 2PC(兩階段提交)
- TCC(Try-Confirm-Cancel)
- Saga事務模式
方案2:基于Binlog的數據同步
實現組件:
- Canal監聽MySQL Binlog
- Kafka/RocketMQ作為消息隊列
- 消費者服務更新緩存
優點:
- 完全解耦
- 保證最終一致性
- 支持重試機制
五、異常場景處理方案
1. 緩存更新失敗
2. 數據庫更新失敗
- 事務回滾
- 補償機制恢復緩存
public void updateDataWithCompensation(Data data) {try {// 1. 開啟事務transaction.begin();// 2. 更新數據庫dataDao.update(data);// 3. 刪除緩存redis.del(data.getId());// 4. 提交事務transaction.commit();} catch (Exception e) {// 5. 事務回滾transaction.rollback();// 6. 恢復緩存Data oldData = dataDao.get(data.getId());redis.set(data.getId(), oldData);}
}
六、最佳實踐選擇指南
場景特征 | 推薦方案 | 配置建議 |
---|---|---|
讀多寫少,容忍短暫不一致 | Cache-Aside | 緩存過期時間 5-30分鐘 |
寫密集型,強一致性要求 | Write-Through | 配合本地緩存減少DB壓力 |
超高吞吐,可接受秒級延遲 | Write-Behind | 批量大小100-500條,刷新間隔1s |
金融交易類系統 | 分布式事務 | TCC模式+異步對賬 |
大型電商平臺 | Binlog同步 | Canal+Kafka+消費者集群 |
七、性能優化技巧
-
批量處理:合并多個緩存操作
public void batchUpdate(List<Data> dataList) {// 批量更新數據庫dataDao.batchUpdate(dataList);// 批量刪除緩存List<String> keys = dataList.stream().map(Data::getId).collect(Collectors.toList());redis.del(keys.toArray(new String[0])); }
-
熱點數據特殊處理
// 使用互斥鎖防止緩存擊穿 public Data getHotData(String id) {Data data = redis.get(id);if (data == null) {if (redis.setnx("lock:" + id, "1")) {redis.expire("lock:" + id, 10); // 設置鎖超時data = dataDao.get(id);redis.set(id, data);redis.del("lock:" + id);} else {// 等待重試Thread.sleep(50);return getHotData(id);}}return data; }
-
多級緩存策略
八、監控與度量指標
-
關鍵監控項:
- 緩存命中率(Hit Ratio)
- 緩存更新延遲(Update Latency)
- 不一致事件計數
- 重試隊列長度
-
告警規則:
# Prometheus告警規則示例 - alert: HighCacheInconsistencyRateexpr: rate(cache_inconsistency_count[5m]) > 0.5for: 10mlabels:severity: criticalannotations:summary: "緩存不一致率過高"- alert: CacheUpdateTimeoutexpr: cache_update_latency_seconds > 1for: 5mlabels:severity: warningannotations:summary: "緩存更新延遲超過閾值"
程序員面試資料大全|各種技術書籍等資料-1000G
IDEA開發工具- FREE