緩存的正確打開方式(二)

上一篇文章:緩存的正確打開方式(一) 中介紹了讀取緩存時的一些細節,有讀就有寫,本篇來聊聊,當我們需要更新緩存該怎麼做?

 

當我們通過一些方式:如後臺管理系統更新了相關的數據信息,或者用戶在一些操作的時候更新了一些數據信息,如果這些信息正好也在緩存裏,那一般也需要在更新數據庫的時候,也更新緩存.

 

那更新的流程是什麼呢?很多人可能覺的很簡單,示例如下?

 

public function setData($data)
{

    //更新數據庫
    $db->update($data);

    //更新緩存
    $redis->set($key, $data);

    return true;
}

 

嗯,咋一看,沒毛病,但真的是這樣麼?

 

場景一:如果更新數據庫失敗了?

 

結果可有兩個

  1. 更新數據庫拋異常了,中斷,緩存也不影響

  2. 數據庫不拋異常,緩存繼續更新,結果會導致 數據庫和緩存不一致

  3. 緩存更新失敗,數據庫和緩存不一致

     

     

如果是第二種情況,可能就會帶來線上的bug了,這時同學可能會有如下的優化:

public function setData($data)
{
    try {
        //更新數據
        $ret = $db->update($data);
        
        if($ret) {
            //數據庫更新成功,則更新緩存
            $redis->set($key, $data);
        }

        return true;
    }catch (Throwable $e) {
        //TODO 異常處理
    }
}

 

 

針對第三種情況,好像只能不斷的重試了.

 

假設我們操作redis比較正常,但這樣就OK了麼?

 

場景二:併發更新的問題?

假如兩個請求在併發操作相同的一條數據,由於db的update和cache的set並不是原子性的,所以存在下面的時序可能性:

 

  1. db 更新了 data1 

  2. db 更新了 data2

  3. cache緩存了data2

  4. cache 緩存了 data1

 

這樣就造成了緩存裏的數據是老數據(data1),從而導致緩存與數據庫不一致

 

那怎麼處理呢?

 

有同學可能會說,我先set cache ,再 update db呢? 

 

問題或許更嚴重,db操作失敗的概率可能大於 cache 操作的概率,這樣可能導致更多數據不一致的情況

 

如果要嚴格的要求更新數據庫後,緩存能實時的一致更新 ,確實沒有完美的的方案,上述場景中,第二種屬於邏輯上的bug,碰到概率比較高,所以我們可以優化一下 ,讓不一致的情況變的更少

 

優化一:set  cache 變delete  cache

 

public function setData($data)
{
    try {
        //更新數據
        $ret = $db->update($data);
        
        if($ret) {
            //數據庫更新成功,則更新緩存
            $redis->delete($key, $data);
        }

        return true;
    }catch (Throwable $e) {
        //TODO 異常處理
    }
}

 

這樣的話,併發更新的問題就不存在了,如以下時序:

  1. db 更新了 data1 

  2. db 更新了 data2

  3. cache刪除了data2

  4. cache 刪除了 data1

 

都是刪除cache,都是會回源到db拉到最新數據

 

(另一個問題:如果先delete cache再update db, 會有什麼問題,歡迎留言)

 

那這個方式是不是就完美了呢?並不了,還存在一些極端的問題,看如下場景:
 

  1. 請求1讀取緩存

  2. 緩存失效,回源數據庫

  3. 請求2 更新db

  4. 請求2 刪除cache

  5. 請求1 設置cache 

 

這樣也導致cache是老數據,但這種場景概率還是很低的(需滿足緩存失效,讀取db比update db時間還要長)

 

優化二:異步更新

可以把緩存更新的放到一個異步對列裏,進行異步更新,這種方式會帶來幾個問題

1、邏輯變得更重

2、又引入了一個新的隊列依賴

 

如果不用消息隊列,是否可行? 

也是可行的,可以直接通過db的binlog進行更新

 

總結:

利用緩存,本身就要做好數據不一致的預期,但我們還是可以通過細節的把握,讓數據不一致的情況儘可能減少。

 

最後用一張圖對比一下:

(思考下最後一種方式帶來什麼更好的改進?)

 

下一篇我們來聊聊用redis做鎖的一些細節

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章