天天看點

MongoDB與記憶體

但凡初次接觸MongoDB的人,無不驚訝于它對記憶體的貪得無厭,至于個中緣由,我先講講Linux是如何管理記憶體的,再說說MongoDB是如何使用記憶體的,答案自然就清楚了。

據說帶着問題學習更有效,那就先看一個MongoDB伺服器的top指令結果:

shell> top -p $(pidof mongod)

Mem:  32872124k total, 30065320k used,  2806804k free,   245020k buffers

Swap:  2097144k total,      100k used,  2097044k free, 26482048k cached

VIRT  RES  SHR %MEM

1892g  21g  21g 69.6

這台MongoDB伺服器有沒有性能問題?大家可以一邊思考一邊繼續閱讀。

先講講Linux是如何管理記憶體的

在Linux裡(别的系統也差不多),記憶體有實體記憶體和虛拟記憶體之說,實體記憶體是什麼自然無需解釋,虛拟記憶體實際是實體記憶體的抽象,多數情況下,出于友善性的考慮,程式通路的都是虛拟記憶體位址,然後作業系統會通過Page Table機制把它翻譯成實體記憶體位址,詳細說明可以參考Understanding Memory和Understanding Virtual Memory,至于程式是如何使用虛拟記憶體的,可以參考Playing with Virtual Memory,這裡就不多費口舌了。

很多人會把虛拟記憶體和Swap混為一談,實際上Swap隻是虛拟記憶體引申出的一種技術而已:作業系統一旦實體記憶體不足,為了騰出記憶體空間存放新内容,就會把目前實體記憶體中的内容放到交換分區裡,稍後用到的時候再取回來,需要注意的是,Swap的使用可能會帶來性能問題,偶爾為之無需緊張,糟糕的是實體記憶體和交換分區頻繁的發生資料交換,這被稱之為Swap颠簸,一旦發生這種情況,先要明确是什麼原因造成的,如果是記憶體不足就好辦了,加記憶體就可以解決,不過有的時候即使記憶體充足也可能會出現這種問題,比如MySQL就有可能出現這樣的情況,一個可選的解決方法是限制使用Swap:

shell> sysctl -w vm.swappiness=0

檢視記憶體情況最常用的是free指令:

shell> free -m

             total       used       free     shared    buffers     cached

Mem:         32101      29377       2723          0        239      25880

-/+ buffers/cache:       3258      28842

Swap:         2047          0       2047

新手看到used一欄數值偏大,free一欄數值偏小,往往會認為記憶體要用光了。其實并非如此,之是以這樣是因為每當我們操作檔案的時候,Linux都會盡可能的把檔案緩存到記憶體裡,這樣下次通路的時候,就可以直接從記憶體中取結果,是以cached一欄的數值非常的大,不過不用擔心,這部分記憶體是可回收的,作業系統的虛拟記憶體管理器會按照LRU算法淘汰冷資料。還有一個buffers,也是可回收的,不過它是保留給塊裝置使用的。

知道了原理,我們就可以推算出系統可用的記憶體是free + buffers + cached:

shell> echo $((2723 + 239 + 25880))

28842

至于系統實際使用的記憶體是used – buffers – cached:

shell> echo $((29377 - 239 - 25880))

3258

除了free指令,還可以使用sar指令:

shell> sar -r

kbmemfree kbmemused  %memused kbbuffers  kbcached

  3224392  29647732     90.19    246116  26070160

shell> sar -W

pswpin/s pswpout/s

    0.00      0.00

希望你沒有被%memused吓到,如果不幸言中,重讀本文。

再說說MongoDB是如何使用記憶體的

目前,MongoDB使用的是記憶體映射存儲引擎,它會把資料檔案映射到記憶體中,如果是讀操作,記憶體中的資料起到緩存的作用,如果是寫操作,記憶體還可以把随機的寫操作轉換成順序的寫操作,總之可以大幅度提升性能。MongoDB并不幹涉記憶體管理工作,而是把這些工作留給作業系統的虛拟記憶體管理器去處理,這樣做的好處是簡化了MongoDB的工作,但壞處是你沒有方法很友善的控制MongoDB占多大記憶體,幸運的是虛拟記憶體管理器的存在讓我們多數時候并不需要關心這個問題。

MongoDB的記憶體使用機制讓它在緩存重建方面更有優勢,簡而言之:如果重新開機程序,那麼緩存依然有效,如果重新開機系統,那麼可以通過拷貝資料檔案到/dev/null的方式來重建緩存,更詳細的描述請參考:Cache Reheating – Not to be Ignored。

有時候,即便MongoDB使用的是64位作業系統,也可能會遭遇OOM問題,出現這種情況,多半是因為限制了記憶體的大小所緻,可以這樣檢視目前值:

shell> ulimit -a | grep memory

多數作業系統預設都是把它設定成unlimited的,如果你的作業系統不是,可以這樣修改:

shell> ulimit -m unlimited

shell> ulimit -v unlimited

注:ulimit的使用是有上下文的,最好放在MongoDB的啟動腳本裡。

有時候,MongoDB連接配接數過多的話,會拖累性能,可以通過serverStatus查詢連接配接數:

mongo> db.serverStatus().connections

每個連接配接都是一個線程,需要一個Stack,Linux下預設的Stack設定一般比較大:

shell> ulimit -a | grep stack

stack size              (kbytes, -s) 10240

至于MongoDB實際使用的Stack大小,可以用如下指令确認(機關:K):

shell> cat /proc/$(pidof mongod)/limits | grep stack | awk -F 'size' '{print int($NF)/1024}'

如果Stack過大(比如:10240K)的話沒有意義,簡單對照指令結果中的Size和Rss:

shell> cat /proc/$(pidof mongod)/smaps | grep 10240 -A 10

所有連接配接消耗的記憶體加起來會相當驚人,推薦把Stack設定小一點,比如說1024:

shell> ulimit -s 1024

注:從MongoDB1.8.3開始,MongoDB會在啟動時自動設定Stack。

有時候,出于某些原因,你可能想釋放掉MongoDB占用的記憶體,不過前面說了,記憶體管理工作是由虛拟記憶體管理器控制的,幸好可以使用MongoDB内置的closeAllDatabases指令達到目的:

mongo> use admin

mongo> db.runCommand({closeAllDatabases:1})

另外,通過調整核心參數drop_caches也可以釋放緩存:

shell> sysctl -w vm.drop_caches=1

平時可以通過mongo指令行來監控MongoDB的記憶體使用情況,如下所示:

mongo> db.serverStatus().mem:

{

    "resident" : 22346,

    "virtual" : 1938524,

    "mapped" : 962283

}

還可以通過mongostat指令來監控MongoDB的記憶體使用情況,如下所示:

shell> mongostat

mapped  vsize    res faults

  940g  1893g  21.9g      0

其中記憶體相關字段的含義是:

mapped:映射到記憶體的資料大小

visze:占用的虛拟記憶體大小

res:占用的實體記憶體大小

注:如果操作不能在記憶體中完成,結果faults列的數值不會是0,視大小可能有性能問題。

在上面的結果中,vsize是mapped的兩倍,而mapped等于資料檔案的大小,是以說vsize是資料檔案的兩倍,之是以會這樣,是因為本例中,MongoDB開啟了journal,需要在記憶體裡多映射一次資料檔案,如果關閉journal,則vsize和mapped大緻相當。

如果想驗證這一點,可以在開啟或關閉journal後,通過pmap指令來觀察檔案映射情況:

shell> pmap $(pidof mongod)

到底MongoDB配備多大記憶體合适?寬泛點來說,多多益善,如果要确切點來說,這實際取決于你的資料及索引的大小,記憶體如果能夠裝下全部資料加索引是最佳情況,不過很多時候,資料都會比記憶體大,比如本文所涉及的MongoDB執行個體:

mongo> db.stats()

    "dataSize" : 1004862191980,

    "indexSize" : 1335929664

本例中索引隻有1G多,記憶體完全能裝下,而資料檔案則達到了1T,估計很難找到這麼大記憶體,此時保證記憶體能裝下熱資料即可,至于熱資料是多少,取決于具體的應用,你也可以通過觀察faults的大小來判斷目前記憶體是否能夠裝下熱資料,如果faults持續變大,就說明目前記憶體已經不能滿足熱資料的大小了。如此一來記憶體大小就明确了:記憶體 > 索引 + 熱資料,最好有點富餘,畢竟作業系統本身正常運轉也需要消耗一部分記憶體。

關于MongoDB與記憶體的話題,大家還可以參考官方文檔中的相關介紹。

繼續閱讀