1. 程式人生 > >Redis快取維護方案-考慮資料庫與快取雙寫、redis和本地資料庫事務一致性、資料庫主從同步延遲的情況怎麼解決快取與資料庫不一致

Redis快取維護方案-考慮資料庫與快取雙寫、redis和本地資料庫事務一致性、資料庫主從同步延遲的情況怎麼解決快取與資料庫不一致

一般常用的快取方案有兩種:

第一種

  • 讀的時候,先讀快取,快取沒有的話,讀資料庫,取出資料後放入快取,同時返回響應。
  • 更新的時候,先刪除快取,在更新資料庫。

第二種

  • 讀的時候,先讀快取,快取沒有的話,讀資料庫,取出資料後放入快取,同時返回響應。
  • 更新的時候,先更新資料庫,再刪除快取。

第二種是Cache Aside Pattern的原本思路,用的比較多,第一種也有在用。為什麼會造成這兩種分歧勒?原因在於:

  • 第一種方案引入了快取-資料庫雙寫不一致的問題,即讀資料(寫快取)與修改資料(寫資料庫)併發的情況下,若修改資料資料庫事務還沒提交,但是已經把快取從redis中刪除,此時來了個讀請求,會把舊的資料刷到快取裡面,這樣就導致了快取中的資料直到下一次修改資料庫之前肯定是與資料庫不一致的。
  • 第二種方案引入了另外一個問題,在提交事務之後,若更新快取失敗,也會導致快取資料庫不一致。

facebook公司用的是第二種方案,因為在高併發的情況下,第一種方案帶來的影響肯定比第二種方案要大。因為:

  • 第一:導致更新快取失敗的情況概率是很小的,就算髮生了,那麼問題就大了,比起解決快取和資料庫不一致,更應該加強Redis架構的可用性。
  • 第二,高併發情況下第一種情況發生的概率是很高的。、

其實個人覺得在沒有讀寫分離的情況下就用第二種方案就夠了,引入redis主從架構解決redis可用性就完了,另外,我們可以為快取設定過期時間,減小第二種方案極端情況下資料庫快取不同步造成的影響。

這是不是說第一種方案完全不可以用勒,也不是,在保證雙寫序列化的情況下,我們也能夠使用第一種方案,但這種方式會犧牲一定的效能,如通過記憶體佇列的形式。比如:

讀請求沒讀到快取就往記憶體佇列丟一個訊息,去更新快取,同時自己開始輪詢快取。針對寫請求,也把資料庫更新的操作傳送到佇列裡面去。然後後臺執行緒輪詢獲取記憶體佇列元素,消費資訊。用記憶體佇列的方式將更新快取和刪除快取的操作給序列化起來。這裡可以優化的是

  • 第一: 後臺記憶體佇列可以多個,通過業務IdHash分發到不同的記憶體隊列當中,只需要保證同一業務id的雙寫是序列化的就行。
  • 第二:為了避免無意義的快取更新訊息連續,可以維護一個map,鍵為產品id,值為一個Boolean值,boolean值標記的是否需要將更新快取操作推到對佇列中(當消費刪快取訊息置為ture,當消費寫快取訊息置為false)。但這裡需要慎重,根據業務量來,如果有100萬條資料,這個map的大小會佔用到15MB。

另外也可以粗暴的加鎖,對讀和寫加鎖序列化,方案實現起來較簡單一點。

如果引入了讀寫分離

但是如果引入了讀寫分離怎麼辦勒,由於主從同步延遲,如果採取上面的兩種方案,在極端情況下,有可能導致讀請求寫入快取中的可能是舊資料。這裡根據網上的資料紙上談兵分析一下,如果嚴格要求這種情況下也要保住快取資料庫一致性的話,只有通過引入阿里的canel元件,實現針對從庫binlog日誌的消費邏輯,等到從庫更新之後再去刪除快取了。

總結

總結一下,在讀寫分離的情況下,直接使用上面的方案二就可。但如果引入了讀寫分離,可以採用上面所述的根據從庫的Binlog日誌來非同步更新快取,但沒有具體實操,可能代價有點大,如果沒有嚴格要求快取資料庫一致性,個人覺得可以不採用,實在不行直