基本思想
將MongoDB用作內(nèi)存數(shù)據(jù)庫(kù)(in-memory database),也即,根本就不讓MongoDB把數(shù)據(jù)保存到磁盤(pán)中的這種用法,引起了越來(lái)越多的人的興趣。這種用法對(duì)于以下應(yīng)用場(chǎng)合來(lái)講,超實(shí)用:
- 置于慢速RDBMS系統(tǒng)之前的寫(xiě)操作密集型高速緩存
- 嵌入式系統(tǒng)
- 無(wú)需持久化數(shù)據(jù)的PCI兼容系統(tǒng)
- 需要輕量級(jí)數(shù)據(jù)庫(kù)而且?guī)熘袛?shù)據(jù)可以很容易清除掉的單元測(cè)試(unit testing)
如果這一切可以實(shí)現(xiàn)就真是太優(yōu)雅了:我們就能夠巧妙地在不涉及磁盤(pán)操作的情況下利用MongoDB的查詢(xún)/檢索功能。可能你也知道,在99%的情況下,磁盤(pán)IO(特別是隨機(jī)IO)是系統(tǒng)的瓶頸,而且,如果你要寫(xiě)入數(shù)據(jù)的話(huà),磁盤(pán)操作是無(wú)法避免的。
MongoDB有一個(gè)非??岬脑O(shè)計(jì)決策,就是她可以使用內(nèi)存影射文件(memory-mapped file)來(lái)處理對(duì)磁盤(pán)文件中數(shù)據(jù)的讀寫(xiě)請(qǐng)求。這也就是說(shuō),MongoDB并不對(duì)RAM和磁盤(pán)這兩者進(jìn)行區(qū)別對(duì)待,只是將文件看作一個(gè)巨大的數(shù)組,然后按照字節(jié)為單位訪(fǎng)問(wèn)其中的數(shù)據(jù),剩下的都交由操作系統(tǒng)(OS)去處理!就是這個(gè)設(shè)計(jì)決策,才使得MongoDB可以無(wú)需任何修改就能夠運(yùn)行于RAM之中。
實(shí)現(xiàn)方法
這一切都是通過(guò)使用一種叫做tmpfs的特殊類(lèi)型文件系統(tǒng)實(shí)現(xiàn)的。在Linux中它看上去同常規(guī)的文件系統(tǒng)(FS)一樣,只是它完全位于RAM中(除非其大小超過(guò)了RAM的大小,此時(shí)它還可以進(jìn)行swap,這個(gè)非常有用?。?。我的服務(wù)器中有32GB的RAM,下面讓我們創(chuàng)建一個(gè)16GB的 tmpfs:
復(fù)制代碼 代碼如下:
# mkdir /ramdata
# mount -t tmpfs -o size=16000M tmpfs /ramdata/
# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/xvde1 5905712 4973924 871792 86% /
none 15344936 0 15344936 0% /dev/shm
tmpfs 16384000 0 16384000 0% /ramdata
接下來(lái)要用適當(dāng)?shù)脑O(shè)置啟動(dòng)MongoDB。為了減小浪費(fèi)的RAM數(shù)量,應(yīng)該把smallfiles和noprealloc設(shè)置為true。既然現(xiàn)在是基于RAM的,這么做完全不會(huì)降低性能。此時(shí)再使用journal就毫無(wú)意義了,所以應(yīng)該把nojournal設(shè)置為true。
復(fù)制代碼 代碼如下:
dbpath=/ramdata
nojournal = true
smallFiles = true
noprealloc = true
MongoDB啟動(dòng)之后,你會(huì)發(fā)現(xiàn)她運(yùn)行得非常好,文件系統(tǒng)中的文件也正如期待的那樣出現(xiàn)了:
復(fù)制代碼 代碼如下:
# mongo
MongoDB shell version: 2.3.2
connecting to: test
> db.test.insert({a:1})
> db.test.find()
{ "_id" : ObjectId("51802115eafa5d80b5d2c145"), "a" : 1 }
# ls -l /ramdata/
total 65684
-rw-------. 1 root root 16777216 Apr 30 15:52 local.0
-rw-------. 1 root root 16777216 Apr 30 15:52 local.ns
-rwxr-xr-x. 1 root root 5 Apr 30 15:52 mongod.lock
-rw-------. 1 root root 16777216 Apr 30 15:52 test.0
-rw-------. 1 root root 16777216 Apr 30 15:52 test.ns
drwxr-xr-x. 2 root root 40 Apr 30 15:52 _tmp
現(xiàn)在讓我們添加一些數(shù)據(jù),證實(shí)一下其運(yùn)行完全正常。我們先創(chuàng)建一個(gè)1KB的document,然后將它添加到MongoDB中4百萬(wàn)次:
復(fù)制代碼 代碼如下:
> str = ""
> aaa = "aaaaaaaaaa"
aaaaaaaaaa
> for (var i = 0; i 100; ++i) { str += aaa; }
> for (var i = 0; i 4000000; ++i) { db.foo.insert({a: Math.random(), s: str});}
> db.foo.stats()
{
"ns" : "test.foo",
"count" : 4000000,
"size" : 4544000160,
"avgObjSize" : 1136.00004,
"storageSize" : 5030768544,
"numExtents" : 26,
"nindexes" : 1,
"lastExtentSize" : 536600560,
"paddingFactor" : 1,
"systemFlags" : 1,
"userFlags" : 0,
"totalIndexSize" : 129794000,
"indexSizes" : {
"_id_" : 129794000
},
"ok" : 1
}
可以看出,其中的document平均大小為1136字節(jié),數(shù)據(jù)總共占用了5GB的空間。_id之上的索引大小為130MB?,F(xiàn)在我們需要驗(yàn)證一件 非常重要的事情:RAM中的數(shù)據(jù)有沒(méi)有重復(fù),是不是在MongoDB和文件系統(tǒng)中各保存了一份?還記得MongoDB并不會(huì)在她自己的進(jìn)程內(nèi)緩存任何數(shù)據(jù),她的數(shù)據(jù)只會(huì)緩存到文件系統(tǒng)的緩存之中。那我們來(lái)清除一下文件系統(tǒng)的緩存,然后看看RAM中還有有什么數(shù)據(jù):
復(fù)制代碼 代碼如下:
# echo 3 > /proc/sys/vm/drop_caches
# free
total used free shared buffers cached
Mem: 30689876 6292780 24397096 0 1044 5817368
-/+ buffers/cache: 474368 30215508
Swap: 0 0 0
可以看到,在已使用的6.3GB的RAM中,有5.8GB用于了文件系統(tǒng)的緩存(緩沖區(qū),buffer)。為什么即使在清除所有緩存之后,系統(tǒng)中仍然還有5.8GB的文件系統(tǒng)緩存??其原因是,Linux非常聰明,她不會(huì)在tmpfs和緩存中保存重復(fù)的數(shù)據(jù)。太棒了!這就意味著,你在RAM只有一份數(shù)據(jù)。下面我們?cè)L問(wèn)一下所有的document,并驗(yàn)證一下,RAM的使用情況不會(huì)發(fā)生變化:
復(fù)制代碼 代碼如下:
> db.foo.find().itcount()
4000000
# free
total used free shared buffers cached
Mem: 30689876 6327988 24361888 0 1324 5818012
-/+ buffers/cache: 508652 30181224
Swap: 0 0 0
# ls -l /ramdata/
total 5808780
-rw-------. 1 root root 16777216 Apr 30 15:52 local.0
-rw-------. 1 root root 16777216 Apr 30 15:52 local.ns
-rwxr-xr-x. 1 root root 5 Apr 30 15:52 mongod.lock
-rw-------. 1 root root 16777216 Apr 30 16:00 test.0
-rw-------. 1 root root 33554432 Apr 30 16:00 test.1
-rw-------. 1 root root 536608768 Apr 30 16:02 test.10
-rw-------. 1 root root 536608768 Apr 30 16:03 test.11
-rw-------. 1 root root 536608768 Apr 30 16:03 test.12
-rw-------. 1 root root 536608768 Apr 30 16:04 test.13
-rw-------. 1 root root 536608768 Apr 30 16:04 test.14
-rw-------. 1 root root 67108864 Apr 30 16:00 test.2
-rw-------. 1 root root 134217728 Apr 30 16:00 test.3
-rw-------. 1 root root 268435456 Apr 30 16:00 test.4
-rw-------. 1 root root 536608768 Apr 30 16:01 test.5
-rw-------. 1 root root 536608768 Apr 30 16:01 test.6
-rw-------. 1 root root 536608768 Apr 30 16:04 test.7
-rw-------. 1 root root 536608768 Apr 30 16:03 test.8
-rw-------. 1 root root 536608768 Apr 30 16:02 test.9
-rw-------. 1 root root 16777216 Apr 30 15:52 test.ns
drwxr-xr-x. 2 root root 40 Apr 30 16:04 _tmp
# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/xvde1 5905712 4973960 871756 86% /
none 15344936 0 15344936 0% /dev/shm
tmpfs 16384000 5808780 10575220 36% /ramdata
果不其然! :)
復(fù)制(replication)呢?
既然服務(wù)器在重啟時(shí)RAM中的數(shù)據(jù)都會(huì)丟失,所以你可能會(huì)想使用復(fù)制。采用標(biāo)準(zhǔn)的副本集(replica set)就能夠獲得自動(dòng)故障轉(zhuǎn)移(failover),還能夠提高數(shù)據(jù)讀取能力(read capacity)。如果有服務(wù)器重啟了,它就可以從同一個(gè)副本集中另外一個(gè)服務(wù)器中讀取數(shù)據(jù)從而重建自己的數(shù)據(jù)(重新同步,resync)。即使在大量數(shù)據(jù)和索引的情況下,這個(gè)過(guò)程也會(huì)足夠快,因?yàn)樗饕僮鞫际窃赗AM中進(jìn)行的 :)
有一點(diǎn)很重要,就是寫(xiě)操作會(huì)寫(xiě)入一個(gè)特殊的叫做oplog的collection,它位于local數(shù)據(jù)庫(kù)之中。缺省情況下,它的大小是總數(shù)據(jù)量的5%。在我這種情況下,oplog會(huì)占有16GB的5%,也就是800MB的空間。在拿不準(zhǔn)的情況下,比較安全的做法是,可以使用oplogSize這個(gè)選項(xiàng)為oplog選擇一個(gè)固定的大小。如果備選服務(wù)器宕機(jī)時(shí)間超過(guò)了oplog的容量,它就必須要進(jìn)行重新同步了。要把它的大小設(shè)置為1GB,可以這樣:
復(fù)制代碼 代碼如下:
oplogSize = 1000
分片(sharding)呢?
既然擁有了MongoDB所有的查詢(xún)功能,那么用它來(lái)實(shí)現(xiàn)一個(gè)大型的服務(wù)要怎么弄?你可以隨心所欲地使用分片來(lái)實(shí)現(xiàn)一個(gè)大型可擴(kuò)展的內(nèi)存數(shù)據(jù)庫(kù)。配置服務(wù)器(保存著數(shù)據(jù)塊分配情況)還還是用過(guò)采用基于磁盤(pán)的方案,因?yàn)檫@些服務(wù)器的活動(dòng)數(shù)量不大,老從頭重建集群可不好玩。
注意事項(xiàng)
RAM屬稀缺資源,而且在這種情況下你一定想讓整個(gè)數(shù)據(jù)集都能放到RAM中。盡管tmpfs具有借助于磁盤(pán)交換(swapping)的能力,但其性能下降將非常顯著。為了充分利用RAM,你應(yīng)該考慮:
- 使用usePowerOf2Sizes選項(xiàng)對(duì)存儲(chǔ)bucket進(jìn)行規(guī)范化
- 定期運(yùn)行compact命令或者對(duì)節(jié)點(diǎn)進(jìn)行重新同步(resync)
- schema的設(shè)計(jì)要相當(dāng)規(guī)范化(以避免出現(xiàn)大量比較大的document)
結(jié)論
寶貝,你現(xiàn)在就能夠?qū)ongoDB用作內(nèi)存數(shù)據(jù)庫(kù)了,而且還能使用她的所有功能!性能嘛,應(yīng)該會(huì)相當(dāng)驚人:我在單線(xiàn)程/核的情況下進(jìn)行測(cè)試,可以達(dá)到每秒20K個(gè)寫(xiě)入的速度,而且增加多少個(gè)核就會(huì)再增加多少倍的寫(xiě)入速度。
您可能感興趣的文章:- 解決MongoDB占用內(nèi)存過(guò)大頻繁死機(jī)的方法詳解
- python監(jiān)控linux內(nèi)存并寫(xiě)入mongodb(推薦)
- 解決MongoDB 排序超過(guò)內(nèi)存限制的問(wèn)題
- cgroup限制mongodb進(jìn)程內(nèi)存大小
- MongoDB 內(nèi)存使用情況分析
- MongoDB 內(nèi)存管理相關(guān)總結(jié)