DDBS 分佈式DB與Cache一致性

上一節的文章,我們是基於單一業務(請求)來討論的,也就是一個串行的結果。實際上,業務可能不只一個,在主從同步,讀寫分離的數據庫架構下,有可能出現髒數據入緩存的情況,此時串行化方案不再適用了。

爲什麼數據會不一致

單庫情況下,服務層的併發讀寫,緩存與數據庫的操作交叉進行:


雖然只有一個DB,在上述詭異異常時序下,也可能髒數據入緩存:
1)請求A發起一個寫操作,第一步淘汰了cache,然後這個請求因爲各種原因在服務層卡住了(進行大量的業務邏輯計算,例如計算了1秒鐘),如上圖步驟1
2)請求B發起一個讀操作,讀cache,cache miss,如上圖步驟2
3)請求B繼續讀DB,讀出來一個髒數據,然後髒數據入cache,如上圖步驟3
4)請求A卡了很久後終於寫數據庫了,寫入了最新的數據,如上圖步驟4
這種情況雖然少見,但理論上是存在的, 後發起的請求B在先發起的請求A中間完成了。

在數據庫架構做了一主多從,讀寫分離時,更多的髒數據入緩存是下面這種情況:


1)請求A發起一個寫操作,第一步淘汰了cache,如上圖步驟1
2)請求A寫數據庫了,寫入了最新的數據,如上圖步驟2
3)請求B發起一個讀操作,讀cache,cache miss,如上圖步驟3
4)請求B繼續讀DB,讀的是從庫,此時主從同步還沒有完成,讀出來一個髒數據,然後髒數據入cache,如上圖步4
5)最後數據庫的主從同步完成了,如上圖步驟5

這種情況請求A和請求B的時序是完全沒有問題的,是主動同步的時延(假設延時1秒鐘)中間有讀請求讀從庫讀到髒數據導致的不一致。

不一致優化思路

出現不一致的根本原因:
(1)單庫情況下,服務層在進行1s的邏輯計算過程中,可能讀到舊數據入緩存
(2)主從庫+讀寫分離情況下,在1s鍾主從同步延時過程中,可能讀到舊數據入緩存

既然舊數據就是在那1s的間隙中入緩存的,是不是可以在寫請求完成後,再休眠1s,再次淘汰緩存,就能將這1s內寫入的髒數據再次淘汰掉呢?

答案是可以的。

寫請求的步驟由2步升級爲3步:
(1)先淘汰緩存
(2)再寫數據庫(這兩步和原來一樣)
(3)休眠1秒,再次淘汰緩存
這樣的話,1秒內有髒數據如緩存,也會被再次淘汰掉,但帶來的問題是:
所有的寫請求都阻塞了1秒,大大降低了寫請求的吞吐量,增長了處理時間,業務上是接受不了的。

這裏的1秒是爲了方便理解,實際上需要根據業務的數據量與併發量,觀察主從同步的時延來設定這個值。例如主從同步的時延爲200ms,這個異步淘汰cache設置爲258ms就是OK的。



再次分析,其實第二次淘汰緩存是“爲了保證緩存一致”而做的操作,而不是“業務要求”,所以其實無需等待,用一個異步的timer,或者利用消息總線異步的來做這個事情即可:

寫請求由2步升級爲2.5步:
(1)先淘汰緩存
(2)再寫數據庫(這兩步和原來一樣)
(2.5)不再休眠1s,而是往消息總線esb發送一個消息,發送完成之後馬上就能返回

這樣的話,寫請求的處理時間幾乎沒有增加,這個方法淘汰了緩存兩次,因此被稱爲“緩存雙淘汰”法。這個方法付出的代價是,緩存會增加1次cache miss(代價幾乎可以忽略)。

而在下游,有一個異步淘汰緩存的消費者,在接收到消息之後,asy-expire在1s之後淘汰緩存。這樣,即使1s內有髒數據入緩存,也有機會再次被淘汰掉。

上述方案有一個缺點,需要業務線的寫操作增加一個步驟,有沒有方案對業務線的代碼沒有任何入侵呢?
通過分析線下的binlog來異步淘汰緩存


業務線的代碼就不需要動了,新增一個線下的讀binlog的異步淘汰模塊,讀取到binlog中的數據,異步的淘汰緩存。

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