快取與資料庫一致性

2022-06-14 08:51:13 字數 3534 閱讀 7437

此時系統的讀寫流量很小,這個時候所有的讀寫操作都在主庫;此時,從庫的角色只是作為災備。

風險分析:從資料一致性的角度來看沒有任何問題,所有讀寫操作都在主庫

隨著業務的前進和流量的激增,會出現大表和資料庫寫入效能下降的問題。我們可以通過分庫的方式,提公升資料庫單機的qps壓下來;通過分表的方式,降低單錶的資料量,提公升查詢效能

風險分析:從資料一致性的角度來看沒有任何風險,所有操作仍然走主庫

隨著業務的進一步發展,在階段2.1時我們已經解決了寫效能的問題;但業務發展到此時,讀問題也會逐漸成為效能瓶頸。這時,我們需要把從庫利用起來;進行讀寫分離,寫走主庫,讀走從庫

風險分析:讀寫分離意味著,讀到的資料很有可能不是最新的。這對實時性要求較高的交易類場景是不合適的,但是足以應對90%的業務場景;關於資料庫主從同步的延時問題,我們之後再進行深入討論。

對於大多數業務場景來說,我們都面臨著讀多寫少的情況;核心交易類應用,更多是面臨寫多讀少的場景(此時將核心流程全部走寫庫,避免了為了處理資料一致性的複雜性)。而資料庫的資源是很寶貴的,瘋狂增加從庫節點,會帶來資源成本激增;同時,分庫分表也會帶來系統設計的複雜度上公升和資料遷移的困難。

所以,此時我們會通過新增快取的方式,來緩解資料庫的讀寫壓力。

風險分析:資料庫與快取資料一致性問題。

上面的問題,引發出之後的思考與討論的問題:快取與資料庫一致性如何保證?在提出方案之前,我們需要分析自己業務系統的架構與設計方案,可以接受什麼粒度的資料不一致?不同的方案,實現難度和設計是不一樣的

只有我們自己清楚了現狀、知道自己想要什麼,才能設計出最合理快取資料庫一致性方案。

當我們更新快取key時,首先做刪除快取操作;當刪除完成之後再更新資料庫;最後再進行非同步(或同步)重新整理快取操作

首先讀取快取的key,如果存在即返回;否則讀取db,再非同步(或同步)重新整理快取

這個。。沒什麼好說的,邏輯簡單,**實現也不會很複雜

我們試想一下,將上述的寫流程做一下改造:

資料變化後,首先更新快取,再更新db。如果快取更新成功,資料庫更新失敗,會導致db中的資料完全是錯誤的,這個錯誤是絕大多數業務系統完全不能接受的。業務系統也許可以接收資料的延遲,但是絕對不能接收資料的錯誤。

資料變化後,不更新快取,先更新db,最後更新快取。如果快取更新失敗,會導致快取中的資料一直是舊資料(其實也是一種錯誤),而且資料無法達到最終一致性的要求

所以,我們首先將快取淘汰,更新db後再重新整理快取的方案,是比較合理的。

在大多數業務系統中,快取是做輔助工作而不是完全做儲存角色。所以在很多場景中,快取的讀寫失敗不能影響到主流程。其實我們可以在每次寫或者讀操作後,同步重新整理快取;但非同步重新整理,可以進一步在寫流程的步驟1(del快取)失敗後的補償,保證資料一致性。

如果del快取失敗,整個流程是否還需要繼續?這個需要針對每乙個不同場景進行不同的考量;如果非同步重新整理的過程失敗,會導致快取中的資料一致保持乙個舊狀態,這個問題就相對比較嚴重了

寫寫併發:

如上述的流程,server a和server b在寫流程先後更新資料庫記錄;之後重新整理快取,因為在分布式場景下,我們沒有辦法保證順序(無論是同步重新整理還是非同步重新整理)。這時,如果server b優先於server a完成快取的更新,會造成最後快取中的資料是server a的舊資料。也就是,不能排除先更新的db操作,反而會很晚重新整理快取,這時,資料也是錯的。

讀寫併發:

tserver a(寫操作)

server b(讀操作)

t1del 快取

t2查詢快取key,不存在

t3查詢資料庫

t4更新資料庫

t5重新整理快取成功

t6重新整理快取成功

如上述的流程,如果讀操作查詢的時間早於寫操作、重新整理快取的時間晚於寫操作。會造成最終寫入到快取的資料仍然是舊資料。

本章節介紹的方案,適合絕大部分業務場景,實現起來也比較簡單。適用於併發量、一致性要求都不是很高的情況。但是這個方案最大的問題在於更新可能會失敗,失敗的話快取中的資料一直是錯誤的,不能保證最終一致性;在併發量較高的時候,資料也很難保證一致性。

是否可以解決?答案是肯定的,我們在下一章節繼續設計與分析。

在分布式系統中,我們經常使用最終一致性的方案來解決資料一致性問題。我們可以基於mq,將讀資料庫+寫資料的流程序列化,進而解決併發問題和實現資料的最終一致性。

第一步先刪除快取,刪除之後再更新資料庫;將資料標識寫入mq,接下來consumer消費mq,從讀庫中查詢資料並重新整理到快取。(在本文中,我們預設主從同步延時忽略)

mq實現訊息順序化,可以參考rocketmq中,同乙個queue中的訊息是有序的;kafka中,同乙個分割槽的訊息是有序的機制來實現。

首先讀取快取,如果快取中不存在,則讀取db主庫(或從庫)。之後與寫操作一致,下傳標誌位到mq,consumer消費mq,從讀庫中重新整理資料到快取中。

該方案將「讀資料庫+重新整理快取」的過程序列化,這樣就不存在老資料覆蓋重新整理新資料的問題

經過前面由淺入深的討論,我們已經實現了「最終一致性」。這個方案的優點還是比較明顯的,解決了我們之前方案的「容災問題」和「併發問題」,整體思路也是將並行的問題序列化解決。保證了快取和資料庫中的資料在最後是一致的。如果你的業務只需要達到最終一致性的話,這個方案已經是比較合理得了。

轉至元資料起始

經過前面三篇文章的討論,我們已經實現了快取與資料庫的「最終一致性」。在本文中,我們再進一步,在上文的基礎上實現「最終一致性」。

什麼是強一致性?

首先,我們先分析一下,「強一致性」和「最終一致性」的區別在**?關鍵點在於「時間差」

「強一致性」=「最終一致性」+「時間差」

那我們的工作,就是在「最終一致性」的基礎上,加上「時間差」。實現方式:我們增加乙個快取key值,將近期要被修改的資料進行標記鎖定(類似於分布式鎖);讀取的時候,如果資料處於被標記的狀態,則強行走db;沒有鎖定的話,則先走快取。

當我們更新資料時,首先寫入快取標記位以鎖定該記錄。如果標記成功,則流程繼續往下走即可。如果標記失敗,則放棄本次修改。

如何標記鎖定?

比如你可以設定乙個有效期為10s的key,key存在即為鎖定。一般來說,10s對於後面的同步操作時間基本夠用。這個時間可以根據業務系統的忍耐度來配置

先讀快取鎖定標誌位,看一下要讀取的記錄是否已經鎖定。如果多鎖定,則直接查詢資料庫(我們預設主從延遲忽略,查詢走從庫);如果沒有被標記,則流程繼續往下走。

1. 容災完備

我們一步一步來分析,該方案如何容災完備:

寫流程容災分析:

讀流程容災分析:

2.無併發問題

所有流程基本全部序列化,不存在老資料覆蓋新資料的問題

1.增加對快取標記鎖定位的強依賴

其實這個問題是沒有辦法的,實現強一致性,一定要犧牲一些效能和穩定性的。畢竟架構是一門妥協的藝術。如同分布式系統的cap定律,無法三者同時滿足一樣。

但是呢,可以用熱點key的思路來解決這個問題。將快取標記位分片至多個節點上,即使部分節點掛了,也只有很少的流量進入到資料庫查詢。

2.複雜度增加

畢竟引入了這麼多邏輯,程式設計複雜度一定會上公升的。世界上沒有完美的事情

至此,「快取與資料庫」的強一致性已經實現。本文也是基於在可實現和盡量簡單的基礎上,完成了這麼一次架構方案的設計。如果大家有更好的思路,可以一起交流。

快取與資料庫一致性保證

本文主要討論這麼幾個問題 1 啥時候資料庫和快取中的資料會不一致 2 不一致優化思路 3 如何保證資料庫與快取的一致性 當資料發生變化時,先淘汰快取,再修改資料庫 這個點是大家討論的最多的。上篇文章得出這個結論的依據是,由於操作快取與運算元據庫不是原子的,非常有可能出現執行失敗。假設先寫資料庫,再淘...

快取與資料庫一致性系列

一般來說,對於乙個新的業務,一般會經歷這幾個階段 讀寫流量都比較小,這個時候所有的讀寫操作都在主庫就ok了 這個時候,從庫可能只是用來災備 風險分析 從資料一致性角度來說沒有風險,全走主庫美滋滋 階段2.1 單庫扛不住了,這個時候就會考慮到分庫分表了,通過增加資料庫的方式,把單庫的qps降下來 風險...

快取與資料庫的一致性

快取就是資料交換的緩衝區,針對服務物件的不同 本質就是不同的硬體 都可以構建快取。目的是,把讀寫速度慢的介質的資料儲存在讀寫速度快的介質中,從而提高讀寫速度,減少時間消耗。例如 磁碟快取 磁碟快取其實就把常用的磁碟資料儲存在記憶體中,記憶體讀寫速度也是遠高於磁碟的。讀資料時,從記憶體讀取。寫資料時,...