mongodb 對記憶體的嚴重占用以及解決方法【轉載】
剛開始使用mongodb的時候,不太注意mongodb的記憶體使用,但通過查資料發現mongodb對記憶體的占用是巨大的,在本地測試伺服器中,8g的記憶體居然被占用了45%。汗呀。
本文就來剖析一下mongodb對記憶體的具體使用方法,以及生産環境針對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
或者 先top後,然後 shift+m 把目前進場按占用記憶體的多少排序。看看你的mongodb能占用多少記憶體。
先了解一下linux對記憶體的管理方式:
在linux裡(别的系統也差不多),記憶體有實體記憶體和虛拟記憶體之說,實體記憶體是什麼自然無需解釋,虛拟記憶體實際是實體記憶體的抽象,多數情況下,出于友善性的考慮,程式通路的都是虛拟記憶體位址,然後作業系統會把它翻譯成實體記憶體位址。
很多人會把虛拟記憶體和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算法淘汰冷資料。除了cached,還有一個buffers,它和cached類似,也是可回收的,不過它的側重點在于緩解不同裝置的操作速度不一緻造成的阻塞,這裡就不多做解釋了。
知道了原理,我們就可以推算出系統可用的記憶體是free + buffers + cached:
shell> echo "2723 + 239 + 25880" | bc -l
28842
至于系統實際使用的記憶體是used – buffers – cached:
shell> echo "29377 - 239 - 25880" | bc -l
3258
除了free指令,還可以使用sar指令:
shell> sar -r
kbmemfree kbmemused %memused kbbuffers kbcached
3224392 29647732 90.19 246116 26070160
3116324 29755800 90.52 245992 26157372
2959520 29912604 91.00 245556 26316396
2792248 30079876 91.51 245680 26485672
2718260 30153864 91.73 245684 26563540
shell> sar -w
pswpin/s pswpout/s
0.00 0.00
希望你沒有被%memused吓到,如果不幸言中,請參考free指令的解釋。
接着咱們分析一下mongodb是怎麼使用記憶體的:
目前,mongodb使用的是記憶體映射存儲引擎,它會把磁盤io操作轉換成記憶體操作,如果是讀操作,記憶體中的資料起到緩存的作用,如果是寫操作,記憶體還可以把随機的寫操作轉換成順序的寫操作,總之可以大幅度提升性能。mongodb并不幹涉記憶體管理工作,而是把這些工作留給作業系統的虛拟緩存管理器去處理,這樣的好處是簡化了mongodb的工作,但壞處是你沒有方法很友善的控制mongodb占多大記憶體,事實上mongodb會占用所有能用的記憶體,是以最好不要把别的服務和mongodb放一起。
有時候,即便mongodb使用的是64位作業系統,也可能會遭遇臭名昭著的oom問題,出現這種情況,多半是因為限制了虛拟記憶體的大小所緻,可以這樣檢視目前值:
shell> ulimit -a | grep 'virtual'
多數作業系統預設都是把它設定成unlimited的,如果你的作業系統不是,可以這樣修改:
shell> ulimit -v unlimited
不過要注意的是,ulimit的使用是有上下文的,最好放在mongodb的啟動腳本裡。
有時候,出于某些原因,你可能想釋放掉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,估計很難找到這麼大記憶體,此時保證記憶體能裝下熱資料即可,至于熱資料有多少,這就是個比例問題了,取決于具體的應用。如此一來記憶體大小就明确了:記憶體 > 索引 + 熱資料。
根據以上的分析我們可以得出幾點結論:
1. mongodb 直接用作業系統的記憶體管理器來管理記憶體。而作業系統采用的是lru算法淘汰冷資料。
2. mongodb可以用重新開機服務、調整核心參數以及mongodb内部的文法去清理mongodb對記憶體的緩存。可能存在的問題是:這幾種清理方式都是全部清理,這樣的話mongodb的記憶體緩存就失效了。
3. mongodb 對記憶體的使用是可以被監控的,在生産環境中要定時的去監控這些資料。
4. mongodb 對記憶體這種占用方式使其盡量的和其他占用記憶體的業務分開部署,例如memcahe,sphinx,mysql等。
5. 作業系統中的交換分區swap 如果操作頻繁的話,會嚴重降低系統效率。要解決可以禁用交換分區,以及增加記憶體以及做分布式。
6. 生産環境中mongodb所在的主機應該盡量的大記憶體。