天天看點

不可忽略的資料庫緩存重建

本文的主要内容來源于MongoDB官方部落格,由NoSQLFan補充說明,本文對傳統的分布式Cache系統進行了分析,指出了其在緩存重建中會對資料庫産生巨大壓力的問題。并分析了MongoDB的mmap方案是如何規避這一問題的。

  如下圖的架構,在資料庫前端加上分布式的Cache(比如我們常用的Memcached),讓用戶端在通路時先查找Cache,Cache不命中再讀資料庫并将結構緩存在Cache中。這是目前比較常用的一種分擔讀壓力的方法。

不可忽略的資料庫緩存重建

  但是這個方法存在一個問題,如果前端的Cache挂掉,或者比較極端的整個機房斷電了,那麼在機器重新開機後,原來Cache機器在記憶體中的緩存會全部清空,在用戶端通路過程中,會百分之百的不命中,這樣資料庫會在瞬間接受巨大的讀壓力。

  試想如果一個64GB的緩存失效了,在其重建時,假設與資料庫連接配接的千兆網卡,假設其以極限速度100M每秒從資料庫取資料過來重建緩存,那麼也需要10分鐘才能建完。更何況這是理想情況,對于用戶端觸發式的随機緩存重建,可能會花掉更長的時間。這還是在資料庫能提供100M每秒的資料讀請求的前提下。

  我們經常看到一些網站挂掉後又恢複,恢複後又挂掉,如此反複幾次才能真正恢複,原因就在于其第一次Cache倒了,資料庫無法承受相應的讀壓力,在緩存重建了一小部分後被壓死。相當于資料庫每重新開機一次,可以恢複部分緩存,直到緩存的非命中率到達資料庫可承受的壓力時,才能夠真正恢複服務。

  這個問題可以用一些可以提供持久化功能的緩存來實作,比如Redis,在未開啟aof的情況下,其定期dump出來的rdb檔案出能自動恢複出絕大部分資料,當然,在有的時候這可能導緻緩存和資料庫資料不一緻的情況,需要根據應用場景選擇性的使用。

  上面是對分布式Cache的問題,而對于很多資料庫存儲,實際上也幾乎都是将熱資料盡量放在記憶體中的。但很多資料庫在實作上是自己在記憶體中實作了Cache機制,這樣在資料庫重新開機(非作業系統重新開機)時,這些Cache可能也就随之被清空了,對于資料庫來說,也需要重建緩存,而資料庫這時所有的操作可能都落在磁盤IO上,帶來了同樣的問題。

  而MongoDB與上面的方式不太一樣,MongoDB采用mmap來将資料檔案映射到記憶體中,是以當MongoDB重新開機時,這些映射的記憶體并不會清掉,因為它們是由作業系統維護的(是以當作業系統重新開機時,MongoDB才會有相同問題)。相對于其它一些自己維護Cache的資料庫,MongoDB在重新開機後并不需要進行緩存重建與預熱。

  另外,新浪微網誌的timyang也曾經提出過一種緩存重建加鎖的方式,也能部分解決此問題。簡單來說就是緩存重建時,當多個用戶端對同一個緩存資料發起請求時,會在用戶端采用加鎖等待的方式,對同一個Cache的重建需要擷取到相應的鎖才行,隻有一個用戶端能拿到鎖,并且隻有拿到鎖的用戶端才能通路資料庫重建緩存,其它的用戶端都需要等待這個拿到鎖的用戶端重建好緩存後直接讀緩存,其結果是對同一個緩存資料,隻進行一次資料庫重建通路。但是如果通路分散比較嚴重,還是會瞬間對資料庫造成非常大的壓力。

  下面是幾點比較實用的知識:

  • 無論使用哪個存儲,都最好先搞清楚其緩存重建的過程,如果一次重新開機就可能導緻資料庫崩潰,還是小心為好,最好把重新開機時間選在通路量比較小的時候。
  • 重新開機MongoDB不會導緻MongoDB的緩存失效(除非重新開機伺服器)
  • 當你重新mount磁盤時,檔案系統的緩存會失效,這和重新開機機器時一樣,MongoDB也無法避免
  • 一個使用MongoDB的小技巧,當MongoDB伺服器剛啟動時,你可以将其所有檔案copy到/dev/null中,這會觸發作業系統對這些檔案的讀操作,進而在記憶體允許的條件下,會将盡可能多的MongoDB資料檔案映射到實體記憶體中。當然,如果在MongoDB運作過程中,你能夠判斷哪些檔案儲存的資料是熱資料,也可以将這些檔案copy到/dev/null 來為其争取更多的實體記憶體。