數(shù)據(jù)庫(kù)緩存重建不容忽視
本文對(duì)傳統(tǒng)的分布式Cache系統(tǒng)進(jìn)行了分析,指出了其在緩存重建中會(huì)對(duì)數(shù)據(jù)庫(kù)產(chǎn)生巨大壓力的問(wèn)題。并分析了MongoDB的mmap方案是如何規(guī)避這一問(wèn)題的。
如下圖的架構(gòu),在數(shù)據(jù)庫(kù)前端加上分布式的Cache(比如我們常用的Memcached),讓客戶端在訪問(wèn)時(shí)先查找Cache,Cache不命中再讀數(shù)據(jù)庫(kù)并將結(jié)構(gòu)緩存在Cache中。這是目前比較常用的一種分擔(dān)讀壓力的方法。
但是這個(gè)方法存在一個(gè)問(wèn)題,如果前端的Cache掛掉,或者比較極端的整個(gè)機(jī)房斷電了,那么在機(jī)器重啟后,原來(lái)Cache機(jī)器在內(nèi)存中的緩存會(huì)全部清空,在客戶端訪問(wèn)過(guò)程中,會(huì)百分之百的不命中,這樣數(shù)據(jù)庫(kù)會(huì)在瞬間接受巨大的讀壓力。
試想如果一個(gè)64GB的緩存失效了,在其重建時(shí),假設(shè)與數(shù)據(jù)庫(kù)連接的千兆網(wǎng)卡,假設(shè)其以極限速度100M每秒從數(shù)據(jù)庫(kù)取數(shù)據(jù)過(guò)來(lái)重建緩存,那么也需要 10分鐘才能建完。更何況這是理想情況,對(duì)于客戶端觸發(fā)式的隨機(jī)緩存重建,可能會(huì)花掉更長(zhǎng)的時(shí)間。這還是在數(shù)據(jù)庫(kù)能提供100M每秒的數(shù)據(jù)讀請(qǐng)求的前提下。
我們經(jīng)常看到一些網(wǎng)站掛掉后又恢復(fù),恢復(fù)后又掛掉,如此反復(fù)幾次才能真正恢復(fù),原因就在于其第一次Cache倒了,數(shù)據(jù)庫(kù)無(wú)法承受相應(yīng)的讀壓力,在緩存重建了一小部分后被壓死。相當(dāng)于數(shù)據(jù)庫(kù)每重啟一次,可以恢復(fù)部分緩存,直到緩存的非命中率到達(dá)數(shù)據(jù)庫(kù)可承受的壓力時(shí),才能夠真正恢復(fù)服務(wù)。
這個(gè)問(wèn)題可以用一些可以提供持久化功能的緩存來(lái)實(shí)現(xiàn),比如Redis,在未開(kāi)啟aof的情況下,其定期dump出來(lái)的rdb文件出能自動(dòng)恢復(fù)出絕大部分?jǐn)?shù)據(jù),當(dāng)然,在有的時(shí)候這可能導(dǎo)致緩存和數(shù)據(jù)庫(kù)數(shù)據(jù)不一致的情況,需要根據(jù)應(yīng)用場(chǎng)景選擇性的使用。
上面是對(duì)分布式Cache的問(wèn)題,而對(duì)于很多數(shù)據(jù)庫(kù)存儲(chǔ),實(shí)際上也幾乎都是將熱數(shù)據(jù)盡量放在內(nèi)存中的。但很多數(shù)據(jù)庫(kù)在實(shí)現(xiàn)上是自己在內(nèi)存中實(shí)現(xiàn)了 Cache機(jī)制,這樣在數(shù)據(jù)庫(kù)重啟(非操作系統(tǒng)重啟)時(shí),這些Cache可能也就隨之被清空了,對(duì)于數(shù)據(jù)庫(kù)來(lái)說(shuō),也需要重建緩存,而數(shù)據(jù)庫(kù)這時(shí)所有的操作可能都落在磁盤(pán)IO上,帶來(lái)了同樣的問(wèn)題。
而MongoDB與上面的方式不太一樣,MongoDB采用mmap來(lái)將數(shù)據(jù)文件映射到內(nèi)存中,所以當(dāng)MongoDB重啟時(shí),這些映射的內(nèi)存并不會(huì)清掉,因?yàn)樗鼈兪怯刹僮飨到y(tǒng)維護(hù)的(所以當(dāng)操作系統(tǒng)重啟時(shí),MongoDB才會(huì)有相同問(wèn)題)。相對(duì)于其它一些自己維護(hù)Cache的數(shù)據(jù)庫(kù),MongoDB在重啟后并不需要進(jìn)行緩存重建與預(yù)熱。
另外,新浪微博的timyang也曾經(jīng)提出過(guò)一種緩存重建加鎖的方式,也能部分解決此問(wèn)題。簡(jiǎn)單來(lái)說(shuō)就是緩存重建時(shí),當(dāng)多個(gè)客戶端對(duì)同一個(gè)緩存數(shù)據(jù)發(fā)起請(qǐng)求時(shí),會(huì)在客戶端采用加鎖等待的方式,對(duì)同一個(gè)Cache的重建需要獲取到相應(yīng)的鎖才行,只有一個(gè)客戶端能拿到鎖,并且只有拿到鎖的客戶端才能訪問(wèn)數(shù)據(jù)庫(kù)重建緩存,其它的客戶端都需要等待這個(gè)拿到鎖的客戶端重建好緩存后直接讀緩存,其結(jié)果是對(duì)同一個(gè)緩存數(shù)據(jù),只進(jìn)行一次數(shù)據(jù)庫(kù)重建訪問(wèn)。但是如果訪問(wèn)分散比較嚴(yán)重,還是會(huì)瞬間對(duì)數(shù)據(jù)庫(kù)造成非常大的壓力。
下面是幾點(diǎn)比較實(shí)用的知識(shí):
- 無(wú)論使用哪個(gè)存儲(chǔ),都最好先搞清楚其緩存重建的過(guò)程,如果一次重啟就可能導(dǎo)致數(shù)據(jù)庫(kù)崩潰,還是小心為好,最好把重啟時(shí)間選在訪問(wèn)量比較小的時(shí)候。
- 重啟MongoDB不會(huì)導(dǎo)致MongoDB的緩存失效(除非重啟服務(wù)器)
- 當(dāng)你重新mount磁盤(pán)時(shí),文件系統(tǒng)的緩存會(huì)失效,這和重啟機(jī)器時(shí)一樣,MongoDB也無(wú)法避免
- 一個(gè)使用MongoDB的小技巧,當(dāng)MongoDB服務(wù)器剛啟動(dòng)時(shí),你可以將其所有文件copy到/dev/null中,這會(huì)觸發(fā)操作系統(tǒng)對(duì)這些文件的讀操作,從而在內(nèi)存允許的條件下,會(huì)將盡可能多的MongoDB數(shù)據(jù)文件映射到物理內(nèi)存中。當(dāng)然,如果在MongoDB運(yùn)行過(guò)程中,你能夠判斷哪些文件保存的數(shù)據(jù)是熱數(shù)據(jù),也可以將這些文件copy到/dev/null 來(lái)為其爭(zhēng)取更多的物理內(nèi)存。
原文出處:http://blog.nosqlfan.com/html/3097.html
【編輯推薦】