一文徹底搞定Redis與MySQL的資料同步

lgx211發表於2024-10-23

Redis 和 MySQL 一致性問題是企業級應用中常見的挑戰之一,特別是在高併發、高可用的場景下。由於 Redis 是記憶體型資料庫,具備極高的讀寫速度,而 MySQL 作為持久化資料庫,通常用於資料的可靠儲存,如何保證兩者資料的一致性需要具體業務場景的設計與最佳化。

下面我們將結合幾個典型的業務場景,逐步分析如何在不同的場景下保證 Redis 和 MySQL 之間的資料一致性。

1. 快取更新策略:Cache Aside Pattern(旁路快取模式)

場景:

在大部分業務系統中,Redis 作為快取層用於提升系統的讀取效能,而 MySQL 作為持久化儲存,用於保證資料的可靠性。最常見的場景是:

  • 系統先查詢 Redis 快取,如果快取中沒有資料,再從 MySQL 中查詢並將資料寫入 Redis 快取。
  • 更新資料時,更新 MySQL 並刪除 Redis 快取,使快取資料失效,保證下次讀取時能拿到最新資料。

典型業務場景:

  • 商品詳情頁面:當使用者請求某個商品詳情時,首先查詢 Redis 快取,如果快取中沒有,則查詢 MySQL,將查詢結果快取到 Redis 中;如果商品資訊發生變更時,更新 MySQL 並刪除 Redis 中的快取。

方案分析:

  • 讀取路徑:從 Redis 獲取快取,如果快取命中則直接返回資料;如果快取未命中,則查詢 MySQL,將結果寫入 Redis,並返回資料。
  • 寫入路徑:更新時先操作 MySQL,然後刪除 Redis 快取中的資料。下次讀取時,由於快取未命中,會重新從 MySQL 中獲取最新資料。

如何保障一致性:

  • 快取淘汰策略:MySQL 資料更新後立即刪除 Redis 快取,確保下次讀取時能獲取到最新資料。即透過 "刪除快取" 的方式避免髒資料存在於快取中。

  • 併發問題:當併發請求較高時,可能會出現“快取雪崩”或“快取擊穿”問題。例如:A 更新 MySQL 資料,B 在快取失效的瞬間讀取了舊資料,再次快取到 Redis。為解決此問題,可以採用 延遲雙刪策略

    1. 刪除 Redis 快取。
    2. 更新 MySQL。
    3. 適當延遲(如 500ms),再次刪除 Redis 快取,確保在併發情況下不存在快取不一致問題。
  • 業務例項:

    // 更新商品詳情的虛擬碼
    public void updateProduct(Product product) {
        // 1. 更新資料庫
        updateProductInMySQL(product);
        // 2. 刪除快取
        deleteProductCache(product.getId());
        
        // 3. 延遲雙刪,解決併發下不一致問題
        try {
            Thread.sleep(500);  // 可以根據實際業務場景調整
        } catch (InterruptedException e) {
            // handle exception
        }
        deleteProductCache(product.getId());
    }
    

2. 先更新快取再更新資料庫

場景:

在某些實時性要求較高的場景中,可以考慮先更新 Redis 快取,然後再非同步更新 MySQL 資料庫。

典型業務場景:

  • 秒殺系統:例如商品庫存的扣減,使用者購買商品時,首先更新 Redis 中的庫存數量,保證極低延遲的實時性體驗。然後將變更非同步寫入 MySQL,確保持久化儲存的一致性。

方案分析:

  • 讀取路徑:讀取 Redis 快取的庫存資訊,能夠提供快速的讀取響應。
  • 寫入路徑:更新 Redis 中的庫存數量後,使用訊息佇列或其他非同步機制將更新同步到 MySQL。

如何保障一致性:

  • 資料最終一致性:Redis 作為前端實時資料的快取,MySQL 作為後端資料的持久化儲存,採用非同步更新策略時,一致性無法保證是強一致性,但可以透過使用訊息佇列等手段來保證最終一致性。非同步寫入 MySQL 時,如果操作失敗,可以透過重試機制或補償機制恢復一致性。

  • 業務例項:

    // 扣減庫存的虛擬碼
    public void reduceStock(Long productId, int amount) {
        // 1. 先更新 Redis 中的庫存
        redisTemplate.decrement("stock:" + productId, amount);
        
        // 2. 透過訊息佇列非同步更新 MySQL 中的庫存
        sendUpdateStockMessage(productId, amount);
    }
    
    // 消費訊息佇列更新 MySQL
    @RabbitListener(queues = "stock_update_queue")
    public void updateStockInMySQL(UpdateStockMessage msg) {
        // 從 MySQL 中扣減庫存
        productRepository.reduceStock(msg.getProductId(), msg.getAmount());
    }
    

一致性保證策略:

  • 冪等性保障:確保訊息的處理是冪等的,即相同的訊息即使被處理多次,也不會導致庫存重複扣減。
  • 訊息重試機制:如果消費訊息時更新 MySQL 失敗,可以設定重試機制或訊息補償機制,保證最終資料一致性。

3. 雙寫操作(快取與資料庫同時更新)

場景:

有時業務需要同時更新 Redis 和 MySQL 的資料,如使用者餘額更新、積分獎勵系統等場景中,Redis 和 MySQL 需要同步寫入。

典型業務場景:

  • 積分系統:使用者消費時增加或減少積分,需要同時更新 Redis 和 MySQL 中的積分記錄。

方案分析:

  • 同步寫入:當更新使用者積分時,Redis 和 MySQL 同時更新資料。由於需要保證兩個儲存的同步性,必須考慮事務性問題。
  • 分散式事務:如果系統架構分散式,可能需要使用分散式事務(如 2PC,或者更輕量的解決方案如 TCC)來確保一致性。

如何保障一致性:

  • 雙寫一致性問題:如果同時寫 Redis 和 MySQL,可能會面臨一致性問題。常見解決方案是透過事務補償機制來實現。具體步驟:

    1. 使用資料庫事務保證 MySQL 寫入成功。
    2. 如果 Redis 寫入失敗,可以嘗試重試,或在事務結束後透過補償機制將失敗的資料寫入 Redis。
  • 業務例項:

    @Transactional
    public void updateUserPoints(Long userId, int points) {
        // 1. 更新 MySQL 中的積分
        userRepository.updatePoints(userId, points);
        
        // 2. 同步更新 Redis 中的積分
        redisTemplate.opsForValue().set("user:points:" + userId, points);
    }
    

事務性保障:

  • 本地事務:在單體系統中,可以依賴資料庫事務和 Redis 的操作保證一致性。如果操作失敗,透過重試機制來恢復一致性。
  • 分散式事務:在微服務架構中,雙寫操作涉及分散式事務,可能需要使用 TCC(Try, Confirm, Cancel)等模式,或使用訊息佇列進行最終一致性補償。

4. 資料回寫(Write Back)策略

場景:

資料回寫模式適用於 Redis 作為快取層,MySQL 作為持久化儲存層,但 Redis 中資料修改後並不立即同步更新 MySQL,而是在特定時機觸發資料回寫。

典型業務場景:

  • 廣告計費系統:廣告點選量儲存在 Redis 中,以減少頻繁的資料庫寫入壓力,定期將 Redis 中的統計資料批次寫入 MySQL。

方案分析:

  • 延遲迴寫:可以透過定時任務或者觸發器將 Redis 中的資料定期回寫到 MySQL,這樣既減少了 MySQL 的壓力,又保證了資料一致性。

如何保障一致性:

  • 持久化與批次同步:透過 Redis 的持久化機制(如 RDB、AOF),在 Redis 崩潰時不會丟失資料。透過定時器或事件驅動系統觸發批次同步 MySQL。

總結

Redis 和 MySQL 的一致性保障在不同的業務場景中需要結合場景特性來進行權衡,主要的策略包括:

  1. Cache Aside Pattern(旁路快取模式):常用於讀多寫少的場景,寫操作時刪除快取。
  2. 非同步更新(Write Behind):先更新快取再非同步寫入 MySQL,保證最終一致性。
  3. 雙寫策略:同時更新 Redis 和 MySQL,配合事務機制確保一致性。
  4. 延遲迴寫:透過定時批次寫入 MySQL 減少頻繁資料庫操作。

每種策略有不同的適用場景,設計時需要考慮一致性、效能和可用性之間的平衡。這算得上是全網最全最詳細的,貨真價實的同步方案分析了,完全結合真實業務場景來考慮設計。所謂贈人玫瑰,手留餘香,希望對你有幫助作用。

相關文章