基本思想
將MongoDB用作內存數據庫(in-memory database),也即,根本就不讓MongoDB把數據保存到磁盤中的這種用法,引起了越來越多的人的興趣。這種用法對於以下應用場合來講,超實用:
如果這一切可以實現就真是太優雅了:我們就能夠巧妙地在不涉及磁盤操作的情況下利用MongoDB的查詢/檢索功能。可能你也知道,在99%的情況下,磁盤IO(特別是隨機IO)是系統的瓶頸,而且,如果你要寫入數據的話,磁盤操作是無法避免的。
MongoDB有一個非常酷的設計決策,就是她可以使用內存影射文件(memory-mapped file)來處理對磁盤文件中數據的讀寫請求。這也就是說,MongoDB並不對RAM和磁盤這兩者進行區別對待,只是將文件看作一個巨大的數組,然後按照字節為單位訪問其中的數據,剩下的都交由操作系統(OS)去處理!就是這個設計決策,才使得MongoDB可以無需任何修改就能夠運行於RAM之中。
實現方法
這一切都是通過使用一種叫做tmpfs的特殊類型文件系統實現的。在Linux中它看上去同常規的文件系統(FS)一樣,只是它完全位於RAM中(除非其大小超過了RAM的大小,此時它還可以進行swap,這個非常有用!)。我的服務器中有32GB的RAM,下面讓我們創建一個16GB的 tmpfs:
復制代碼 代碼如下:# 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
接下來要用適當的設置啟動MongoDB。為了減小浪費的RAM數量,應該把smallfiles和noprealloc設置為true。既然現在是基於RAM的,這麼做完全不會降低性能。此時再使用journal就毫無意義了,所以應該把nojournal設置為true。
復制代碼 代碼如下:dbpath=/ramdata
nojournal = true
smallFiles = true
noprealloc = true
MongoDB啟動之後,你會發現她運行得非常好,文件系統中的文件也正如期待的那樣出現了:
復制代碼 代碼如下:# 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
現在讓我們添加一些數據,證實一下其運行完全正常。我們先創建一個1KB的document,然後將它添加到MongoDB中4百萬次:
復制代碼 代碼如下:> 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字節,數據總共占用了5GB的空間。_id之上的索引大小為130MB。現在我們需要驗證一件 非常重要的事情:RAM中的數據有沒有重復,是不是在MongoDB和文件系統中各保存了一份?還記得MongoDB並不會在她自己的進程內緩存任何數據,她的數據只會緩存到文件系統的緩存之中。那我們來清除一下文件系統的緩存,然後看看RAM中還有有什麼數據:
復制代碼 代碼如下:# 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用於了文件系統的緩存(緩沖區,buffer)。為什麼即使在清除所有緩存之後,系統中仍然還有5.8GB的文件系統緩存??其原因是,Linux非常聰明,她不會在tmpfs和緩存中保存重復的數據。太棒了!這就意味著,你在RAM只有一份數據。下面我們訪問一下所有的document,並驗證一下,RAM的使用情況不會發生變化:
復制代碼 代碼如下:> 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
果不其然! :)
復制(replication)呢?
既然服務器在重啟時RAM中的數據都會丟失,所以你可能會想使用復制。采用標准的副本集(replica set)就能夠獲得自動故障轉移(failover),還能夠提高數據讀取能力(read capacity)。如果有服務器重啟了,它就可以從同一個副本集中另外一個服務器中讀取數據從而重建自己的數據(重新同步,resync)。即使在大量數據和索引的情況下,這個過程也會足夠快,因為索引操作都是在RAM中進行的 :)
有一點很重要,就是寫操作會寫入一個特殊的叫做oplog的collection,它位於local數據庫之中。缺省情況下,它的大小是總數據量的5%。在我這種情況下,oplog會占有16GB的5%,也就是800MB的空間。在拿不准的情況下,比較安全的做法是,可以使用oplogSize這個選項為oplog選擇一個固定的大小。如果備選服務器宕機時間超過了oplog的容量,它就必須要進行重新同步了。要把它的大小設置為1GB,可以這樣:
復制代碼 代碼如下:oplogSize = 1000
分片(sharding)呢?
既然擁有了MongoDB所有的查詢功能,那麼用它來實現一個大型的服務要怎麼弄?你可以隨心所欲地使用分片來實現一個大型可擴展的內存數據庫。配置服務器(保存著數據塊分配情況)還還是用過采用基於磁盤的方案,因為這些服務器的活動數量不大,老從頭重建集群可不好玩。
注意事項
RAM屬稀缺資源,而且在這種情況下你一定想讓整個數據集都能放到RAM中。盡管tmpfs具有借助於磁盤交換(swapping)的能力,但其性能下降將非常顯著。為了充分利用RAM,你應該考慮:
結論
寶貝,你現在就能夠將MongoDB用作內存數據庫了,而且還能使用她的所有功能!性能嘛,應該會相當驚人:我在單線程/核的情況下進行測試,可以達到每秒20K個寫入的速度,而且增加多少個核就會再增加多少倍的寫入速度。