天天看點

分布式系統之資料庫和緩存雙寫一緻性

1. 緩存使用目的

緩存由于其高并發和高性能的特性,已經在項目中被廣泛使用(IO cost比資料庫低了幾個數量級,redis是從記憶體中讀取資料, mysql是從磁盤讀取資料)

2. 讀取資料

流程無異議:

  1.   判斷是否有緩存 
  2. 有,直接傳回資料給調用端. 無,跳到3 
  3. 從資料庫加載資料,有資料直接寫入緩存,無資料則跳到4
  4. 若不寫入緩存, 可能發生緩存穿透(緩存穿透是指查詢一個一定不存在的資料,由于緩存是不命中時需要從資料庫查詢,查不到資料則不寫入緩存,這将導緻這個不存在的資料每次請求都要到資料庫去查詢,造成緩存穿透。)。可以緩存空對象來解決緩存穿透問題。
  5. 傳回空資料。

3. 更新緩存

        3.1更新政策  

  • 先更新資料庫,再更新緩存
  • 先删除緩存,再更新資料庫
  • 先更新資料庫,再删除緩存

       3.2 政策分析

  • 先更新資料庫,再更新緩存

          技術角度分析: 多線程更新資料庫和緩存時,并不能保證次序,比如A線程先發起,然後B線程後發起,但是B線程可能比A線程先更新緩存,這就導緻了緩存最後的值是A線程的值導緻資料庫和緩存不一緻

          業務角度分析:1. 加入業務是寫多讀少的場景,采用這種方案就會導緻,資料壓根還沒讀到,緩存就被頻繁的更新,浪費性能。2. 加入寫入緩存的值是要在資料庫更新後,經過一系列計算再更新緩存的,采用此方案會造成性能浪費。

  • 先删除緩存,再更新資料庫

          比如一個寫線程A, 一個讀線程B,在A線程删除緩存後,B線程發現緩存不存在就去讀取資料庫舊值,然後更新緩存,然後A線程才更新資料庫,如此也會造成資料庫緩存不一緻。

  • 先更新資料庫,再删除緩存

         這種方式同樣有并發通路問題。同樣2個線程,緩存失效,線程A探知緩存失效,查詢資料庫,取得舊值,線程B更新資料庫并且删除緩存key,線程A将舊值寫入緩存。

         但一般來說資料庫IO遠遠慢比緩存IO,是以一般線程A寫入緩存會發生線上程B删除緩存之前。

另外還有一個問題就是删除緩存不成功,用重試機制保障删除操作,一個是用消息隊列,另一個是訂閱binlog日志來更新緩存(canal)。