但凡初次接觸MongoDB的人,無不驚訝於它對記憶體的貪得無厭,至於個中緣由,我先講講Linux是如何管理記憶體的,再說說MongoDB是如何使用記憶體的,答案自然就清楚了。
據說帶著問題學習更有效,那就先看一個MongoDB伺服器的top命令結果:
shell> top -p $(pidof mongod)Mem: 32872124k total, 30065320k used, 2806804k free, 245020k buffersSwap: 2097144k total, 100k used, 2097044k free, 26482048k cached VIRT RES SHR %MEM1892g 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 cachedMem: 32101 29377 2723 0 239 25880-/+ buffers/cache: 3258 28842Swap: 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 -rkbmemfree kbmemused %memused kbbuffers kbcached 3224392 29647732 90.19 246116 26070160shell> sar -Wpswpin/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 unlimitedshell> ulimit -v unlimited
註:ulimit的使用是有內容相關的,最好放在MongoDB的啟動指令碼裡。
有時候,MongoDB串連數過多的話,會拖累效能,可以通過serverStatus查詢串連數:
mongo> db.serverStatus().connections
每個串連都是一個線程,需要一個Stack,Linux下預設的Stack設定一般比較大:
shell> ulimit -a | grep stackstack 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 adminmongo> 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> mongostatmapped 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,估計很難找到這麼大記憶體,此時保證記憶體能裝下熱資料即可,至於熱資料是多少,取決 於具體的應用。如此一來記憶體大小就明確了:記憶體 > 索引 + 熱資料,最好有點富餘,畢竟作業系統本身正常運轉也需要消耗一部分記憶體。
關於MongoDB與記憶體的話題,大家還可以參考官方文檔中的相關介紹。