在很多系統中重要資料通常都是寫入關聯式資料庫如mysql中,為了實現讀寫分離,提高系統負載能力,縮短響應時間通常還需要用到快取。
背景介紹
一般使用快取(本文中的快取不特指某一種分布式快取或本地快取)的方式為在讀資料時首先讀取快取,如果快取沒有則讀資料庫然後將資料寫入快取最後返回;寫資料時首先清除快取內的資料,然後寫資料庫。
這種方式在資料庫配置了主從庫時會遇到資料不一致的問題,首先來看一下這種實現的具體流程如下圖:
圖1 主從資料不一致
在讀資料時如果快取中沒有資料則讀取從庫的資料,然後寫入快取中並返回;在寫資料時先清除快取中的資料,然後將資料寫入主庫,主庫資料會被同步到從庫。
這種實現方式的主要問題在於當資料寫入主庫後,快取沒有資料,這時讀請求會讀取從庫的資料。此時如果發生主從延遲,主庫的資料還沒有寫到從庫,則應用伺服器會將從庫讀到的髒資料寫入快取伺服器中,如果寫入的資料沒有加有效期或有效期很長就會造成資料不一致,如果主從延遲時間較長可能會導致大面積的資料不一致。下面將介紹幾種解決資料一致性問題的方案。
加有效期
給快取中的資料增加有效期是解決一致性問題最簡單的確保資料最終一致性的方法,這種方法在快取中沒有資料需要查詢資料庫時將查詢結果放入快取的時候設定乙個有效期(更新資料時仍然先清除快取資料),非常適用於更新頻率較低的資料,例如商品資訊。
但是單純給資料加上有效期也存在一些明顯的問題,如果有效期較長就會出現上面提到的資料不一致的問題,如果有效期較短就會出現快取效率不高經常讀庫的情況。在使用這種方法的時候就需要我們根據資料的更新頻率確定合適的有效期時間,當冷熱資料並存時這種方案就顯得難以兼顧。那麼什麼策略既能確保資料的最終一致性又能充分利用快取呢?這就要提到業內使用最多的雙淘汰了。
雙淘汰
雙淘汰與本文第乙個方案相比在讀取資料時是相同的,區別在於更新資料的流程。在更新資料時仍然首先清除快取的資料,然後將資料寫入到資料庫中,然後將資料記錄在乙個延遲佇列或雜湊表中,同時另乙個執行緒不斷讀取延遲佇列或者雜湊表,根據資料存入的時間也預先設定的延遲時間再次清除快取了的資料。
可以看出預先設定的延遲時間應該大於資料庫主從同步較慢情況下的同步時間,這樣就能確保在主從延遲的情況下快取中的髒資料也能被清除保證了資料一致性。流程如下圖,c語言中可以使用雜湊表實現。
雖然雙淘汰保證了資料的最終一致性並提高了快取的使用率,但在兩次「淘汰」之間讀取的資料仍然有可能是髒資料,這種情況會在主從延遲較長的情況下尤為明顯。對於某些對實時一致性要求較高的系統如何獲得更好的讀一致性呢,這裡需要提到雙淘汰的另一種變型。
圖2 雙淘汰
另一種雙淘汰
為了提高讀到資料的準確性這種方法在更新資料時首先清除快取資料並在快取中存入這個資料對應的標記,在寫入資料庫成功後再將資料寫入到乙個延遲佇列或雜湊表中。在讀取資料時從快取讀取資料,如果存在直接返回,如果不存在則讀取資料對應的標記,如果標記存在則讀主庫否則讀從庫,最後將資料寫入快取中。
同時另乙個執行緒不斷讀取延遲佇列或者雜湊表,根據資料存入的時間也預先設定的延遲時間再次清除快取了的資料。整個讀寫過程如下圖。可以看出這種方法通過在快取增加乙個標記將部分讀請求分流到了主庫,這個標記可以是資料的主鍵或其他唯一標識,通過犧牲一部分主庫的效能提高了讀請求的資料一致性。
圖3 雙淘汰2
目前為止沒有哪一種快取策略是萬能的,基本上我們仍需要根據具體的業務場景和資料型別選擇合適的快取策略。資料量越大資料情況也複雜通常就需要越複雜的快取策略,希望本文介紹的幾個方案對讀者今後的開發有所幫助。
快取與資料庫資料一致性
1 無論是雙寫模式還是失效模式,都會導致快取的不一致問題。即多個例項同時更新會出事。怎麼辦?解決方案 a 如果是使用者緯度資料 訂單資料 使用者資料 這種併發機率非常小,不用考慮這個問題,快取資料加上過期時間,每隔一段時間觸發讀的主動更新即可 b 如果是選單,商品介紹等基礎資料,也可以去使用cana...
資料庫 快取資料一致性
1 不推薦更新快取 建議刪除快取 2 採用延時雙刪 先刪快取,後更新資料庫,延時再刪一次快取 但是會存在問題 改庫後延時時間內的資料可能是舊資料 如果業務場景要求,改庫成功就不能使用舊資料,可以採用如下優化方案 新增乙個快取型別,記錄key是否被更新過,並且設定自動過期時間 在訪問的時候,先查詢ke...
Redis快取與資料庫資料一致性
寫流程 先刪除快取,刪除之後再更新db,再非同步將資料刷回快取。如果先更新資料庫再更新快取,更新資料庫時,程式訪問快取時還是舊的資料。讀流程 先讀快取,如果快取沒讀到,則去讀db,之後再非同步將資料刷回快取。缺點 容災不足 第一步del快取失敗 如果繼續執行,那麼從 更新完db 到非同步 重新整理快...