概要:
1秉氧、緩存是為了加速數(shù)據(jù)訪問址儒,在數(shù)據(jù)庫之上添加的一層機制僻焚。
2允悦、典型的緩存模式:Cache Aside、Read/Write Through 和 Write Behind Caching 以及它們各自的優(yōu)缺點虑啤。
3隙弛、緩存設計的重點,性能之外咐旧,分布式架構(gòu)下和公網(wǎng)環(huán)境下驶鹉,對緩存集群、一致性铣墨、LRU 的鎖競爭室埋、爬蟲等多方面都需要考慮。
分布式系統(tǒng)中最耗性能就是數(shù)據(jù)庫了。小心維護好姚淆,三個寫操作 insert孕蝉、update 和 delete 不太會出現(xiàn)性能問題(insert 一般不會有性能問題,update 和 delete 一般會有主鍵腌逢,所以也不會太慢)降淮。除非索引建得太多,數(shù)據(jù)又太多搏讶,這三個操作才會變慢佳鳖。
select 是性能問題最大的地方: join、group媒惕、order系吩、like 非常耗性能的;大多數(shù)都是讀多寫少妒蔚,加劇了慢查詢的問題穿挨。
遠程調(diào)用有網(wǎng)絡開銷,也會耗很多性能肴盏,會導致整體的響應時間下降科盛。使用緩存是非常必要的事情。
二菜皂、緩存三種模式
1贞绵、Cache Aside 更新模式
最常用的設計模式了,其具體邏輯如下恍飘。
失效:先從 cache 取數(shù)據(jù)但壮,沒有則從數(shù)據(jù)庫中取數(shù)據(jù),成功后常侣,放到緩存中。
命中:從 cache 中取數(shù)據(jù)弹渔,返回胳施。
更新:存到數(shù)據(jù)庫中,成功后肢专,緩存失效舞肆。
為什么不是寫完數(shù)據(jù)庫后更新緩存?怕兩個并發(fā)的寫操作導致臟數(shù)據(jù)博杖。
Cache Aside 并發(fā)問題椿胯,比如,一個是讀操作剃根,沒有命中緩存哩盲,到數(shù)據(jù)庫中取數(shù)據(jù)。此時來了一個寫操作,寫完數(shù)據(jù)庫后廉油,讓緩存失效惠险,然后之前的那個讀操作再把老的數(shù)據(jù)放進去,所以會造成臟數(shù)據(jù)抒线。
實際出現(xiàn)概率低班巩,需要發(fā)生在讀緩存時緩存失效,而且有并發(fā)的寫操作嘶炭。數(shù)據(jù)庫的寫比讀操作慢得多抱慌,還要鎖表,讀在寫前操作眨猎,又晚于寫更新緩存抑进,這些條件都具備的概率并不大。
所以宵呛,這也就是 Quora 上的那個答案里說的单匣,要么通過 2PC 或是 Paxos 協(xié)議保證一致性,要么就是拼命地降低并發(fā)時臟數(shù)據(jù)的概率宝穗。而 Facebook 使用了這個降低概率的玩法户秤,因為 2PC 太慢,而 Paxos 太復雜逮矛。當然鸡号,最好還是為緩存設置好過期時間。
2须鼎、Read/Write Through 更新模式
Cache Aside需要維護兩個數(shù)據(jù)存儲鲸伴,緩存(cache)和數(shù)據(jù)庫(repository)。
Read/Write Through 把更新數(shù)據(jù)庫(repository)的操作由緩存自己代理了晋控。對于應用層來說汞窗,簡單很多,應用認為后端就是一個單一的存儲赡译,而存儲自己維護自己的 Cache仲吏。
Read Through
查詢操作中更新緩存,當緩存失效的時候(過期或 LRU 換出)蝌焚,
Cache Aside :調(diào)用方把數(shù)據(jù)加載入緩存
Read Through :緩存服務自己來加載裹唆,對應用方是透明的。
Write Through
Write Through 和 Read Through 相仿只洒,更新:沒有命中緩存许帐,更新數(shù)據(jù)庫,返回毕谴。命中了則更新緩存成畦,由 Cache 自己更新數(shù)據(jù)庫(這是一個同步操作)距芬。
其中的 Memory,可以理解為我們例子里的數(shù)據(jù)庫羡鸥。
3.Write Behind Caching 更新模式
Write Behind 又叫 Write Back蔑穴。
更新數(shù)據(jù)的時候,只更新緩存惧浴,不更新數(shù)據(jù)庫存和,緩存會異步地批量更新數(shù)據(jù)庫。讓數(shù)據(jù)的 I/O 操作飛快無比(因為直接操作內(nèi)存嘛)衷旅。因為異步捐腿,還可以合并對同一個數(shù)據(jù)的多次操作,性能提高柿顶。
但數(shù)據(jù)不是強一致性的茄袖,而且可能會丟失。強一致性和高性能嘁锯,高可用和高性能是有沖突的宪祥。
實現(xiàn)邏輯復雜,需要 track哪些數(shù)據(jù)是被更新了的家乘,刷到持久層上蝗羊。僅當這個 cache 需要失效的時候,才會把它真正持久起來仁锯。比如耀找,內(nèi)存不夠了,或是進程退出了等情況业崖,這又叫 lazy write野芒。
Write Back 的流程圖
三、緩存設計的重點
用Redis 原因:(1)數(shù)據(jù)結(jié)構(gòu)豐富双炕。(2)不能在 Service 內(nèi)放 local cache狞悲,Service 有多個實例,負載均衡器會把請求隨機分布到不同的實例妇斤。緩存需要在所有的 Service 實例上都建好效诅,Service 有狀態(tài),更難管理了趟济。
分布式架構(gòu)下,需要一個外部的緩存集群咽笼,要內(nèi)存要足夠大顷编,網(wǎng)絡帶寬也要好,因為緩存本質(zhì)上是個內(nèi)存和 IO 密集型的應用剑刑。
內(nèi)存很大媳纬,要數(shù)據(jù)分片把不同的緩存分布到不同的機器上双肤。保證緩存集群可以不斷地 scale 下去。
緩存的命中率高說明緩存有效钮惠,80% 以上就算很高了茅糜。有的為了追求更高的性能,95% 以上素挽,甚至100%蔑赘。不必要的,也沒效率的预明,熱點數(shù)據(jù)只會是少數(shù)缩赛。
時間周期太長太短都不好,過期期限不宜太短撰糠,導致不斷從數(shù)據(jù)存儲檢索數(shù)據(jù)添加到緩存酥馍。不宜太長,冷數(shù)據(jù)不過期浪費內(nèi)存阅酪。
LRU 策略:內(nèi)存不夠時旨袒,清除不活躍數(shù)據(jù)。在 key-value 這樣的非順序的數(shù)據(jù)結(jié)構(gòu)中維護一個順序的數(shù)據(jù)結(jié)構(gòu)术辐,讀緩存時改變排位砚尽。所以LRU 在讀寫都加鎖(除非是單線程無并發(fā)),會導致更慢的緩存存取的時間术吗。
網(wǎng)站都會被爬蟲爬尉辑,爬蟲可能會爬到一些很古老的數(shù)據(jù),把這些數(shù)據(jù)加到緩存较屿,熱點數(shù)據(jù)被擠出去(因為機器的速度足夠快)隧魄。需要有爬蟲保護機制,引導這些人去使用我們外部 API隘蝎。針對性地做多租戶的緩存系統(tǒng)(也就是說购啄,把用戶和第三方開發(fā)者的緩存系統(tǒng)分離開來)。
你接觸到的緩存方式有哪些嘱么?怎樣權(quán)衡一致性和緩存的效率狮含?
評論1:
Read/Write Through 模式中對數(shù)據(jù)庫的操作一定要交給交給緩存代理么,如果是這樣就會帶來兩個問題:
1. 需要在緩存服務中實現(xiàn)數(shù)據(jù)庫操作的代碼曼振,主流緩存是否支持這樣的操作几迄。
2. 緩存與數(shù)據(jù)庫之間建立了依賴。
常見的做法是由應用服務操作緩存以及數(shù)據(jù)庫冰评,跟cache aside模式很像了映胁。
評論2:
redis 分片熱點問題,有沒有什么好的解決方案甲雅?建數(shù)據(jù)索引服務
評論3:
Cache aside 需要處理并發(fā)讀問題解孙,緩存失效時多個讀會打到數(shù)據(jù)庫坑填,怎么解決?