MongoDB核心貢獻(xiàn)者:不是不行,是你不懂!
近期MongoDB在Hack News上是頻繁中槍。許多人更是聲稱恨上了MongoDB,David mytton就在他的博客中揭露了MongoDB許多現(xiàn)存問題。然而恨的人有之偏愛的也同樣很多,作為回?fù)簦篟ussell Smith帶來了多年工作經(jīng)驗的總結(jié)。Russell Smith曾擔(dān)任Ops和大型網(wǎng)站縮放顧問并且?guī)椭^Guardian、Experian等多家公司,MongoDB London User Group的聯(lián)合創(chuàng)始人。作為MongoDB Master(MongoDB官方認(rèn)可的MongoDB核心貢獻(xiàn)者組織,并通過社區(qū)分享自己的專業(yè)技術(shù)),其參與工作的基礎(chǔ)設(shè)施單服務(wù)器每秒查詢超過3萬次,每天活躍數(shù)據(jù)更在1TB以上。
下面來看Russell對MongoDB一些常見及生僻的問題做出分析:
32位 vs 64位
現(xiàn)在大多數(shù)的服務(wù)器都對32位操作系統(tǒng)實現(xiàn)支持,更有許多新型硬件支持著允許更多RAM的64位操作系統(tǒng)。
MongoDB也同時發(fā)布了32位及64位兩個版本的數(shù)據(jù)庫。歸結(jié)于MongoDB使用的內(nèi)存映射文件,32位版本只支持2G數(shù)據(jù)的存儲。對于標(biāo)準(zhǔn)的Replica Set,MongoDB只擁有單一的處理策略 —— mongod。如果你想在未來儲存2G以上的數(shù)據(jù),請使用64位版本的MongoDB。如果擁有分片安裝,那么32位版本同樣可以使用。
總結(jié):使用64位版本或者理解32位版本的限制。
文件大小限制
不同于RDBMS把數(shù)據(jù)儲存在行與列中,MongoDB的數(shù)據(jù)是儲存在文件中的。這些文件使用二進(jìn)制存儲形式,其格式為類似JSON格式的BSON格式。
和其它的數(shù)據(jù)庫一樣,單個文件的儲存大小是有限制的。在舊版本的MongoDB中,單個文件都限制在4M以內(nèi)。而新版本的MongoDB單文件已經(jīng)支持到16M大小。這樣的限制也許是令人厭煩的,但是10gen的意見是:如果這項設(shè)置不停的困擾到你,那么是否你的設(shè)計模式存在著問題;或者你可以使用文件無大小限制的GridFS。
這種情況通常的建議是避免存儲過大的文件,不定期的更新數(shù)據(jù)庫中存儲的各種對象。而像Amazon S3或者Rackspace Cloudfiles這樣的服務(wù)通??赡軙歉玫倪x擇,而非必要情況下***別讓基礎(chǔ)設(shè)施陷入過載。
總結(jié):把每個文件保持在16M以下,那么一切都好。
寫入失敗
MongoDB在默認(rèn)的情況下允許高速的寫入和更新,而付出的代價就是沒有明確的錯誤通知。默認(rèn)情況下多數(shù)的驅(qū)動都在做異步、“不安全”寫入 —— 這就意味著驅(qū)動程序不能立即反饋錯誤信息,類似于MySQL的INSERT DELAYED。如果你想知道某個事情是否成功,你必須使用getLastError手動的檢查錯誤信息。
某些情況下如果你需要在錯誤發(fā)生后立刻得到錯誤信息,即:大多數(shù)的驅(qū)動中都很容易實現(xiàn)同步“安全”查詢。這將謀殺掉MongoDB不同于傳統(tǒng)數(shù)據(jù)庫的優(yōu)點。
如果對比“完全安全”的同步寫入你需要多一點性能,同時還想要一定程度的安全,那么你可以使用getLastError with‘j’讓MongoDB只到一份日志提交后再發(fā)出錯誤報告通知。那么日志將以100毫秒一次的速度輸出到磁盤,而不是60秒。
總結(jié):如果必須要寫入確認(rèn),你可以使用安全寫入或getLastError。
數(shù)據(jù)結(jié)構(gòu)模型的弱化不等于沒有數(shù)據(jù)結(jié)構(gòu)模型
RDBMS一般都擁有一個預(yù)定義的數(shù)據(jù)結(jié)構(gòu)模型:表格的行和列,每個字段都擁有名稱和數(shù)據(jù)類型。如果你想給其中一行加一列,那么你必須給整個表格都添加一列。
MongoDB則是移除了這個設(shè)置,對于Collection和文件沒有強制的模型限定。這有益于快速開發(fā)及簡易修改。
當(dāng)然這不意味著你就可以無視結(jié)構(gòu)模型的設(shè)計,一個合適的結(jié)構(gòu)模型可以讓你獲得MongoDB的***性能。趕快閱讀MongoDB文檔,或者觀看這些結(jié)構(gòu)模型設(shè)計的相關(guān)視頻吧!
總結(jié):設(shè)計結(jié)構(gòu)模型并充分利用MongoDB的特色。
默認(rèn)情況下修改語句修改的只是單個文件
在傳統(tǒng)的RDBMS中除非使用LIMIT子句,修改語句作用的將是所有匹配的地方。然而MongoDB每個查詢上都默認(rèn)使用等價“LIMIT 1”的設(shè)置。雖然無法做到“LIMIT 5”,但是你可以通過下面的語句整個的移除限制:
db.people.update({age: {$gt: 30}}, {$set: {past_it: true}}, false, true)
同樣在官方的驅(qū)動中還有類似的選項 —— ‘multi’。
總結(jié):可以通過指定多個文件的multi為true來完成多文件修改
查詢區(qū)分大小寫
字符串的查詢可能不按預(yù)期的那樣發(fā)展 —— 這歸結(jié)于MongoDB默認(rèn)區(qū)分大小寫。
例如:db.people.find({name: ‘Russell’})與db.people.find({name: ‘ russell‘})是不同的。在這里最理想的解決方案就是對需要查詢數(shù)據(jù)進(jìn)行確認(rèn)。你也可以通過正則表達(dá)式進(jìn)行查詢,比如:db.people.find({name:/Russell/i}),但是這樣會影響到性能。
總結(jié):查詢是區(qū)分大小寫的,在犧牲速度的情況下可以利用正則表達(dá)式。
對輸入的數(shù)據(jù)無容錯性
當(dāng)你嘗試向傳統(tǒng)數(shù)據(jù)庫插入錯誤類型的數(shù)據(jù),傳統(tǒng)的數(shù)據(jù)庫一般會把數(shù)據(jù)轉(zhuǎn)換成預(yù)定義的類型。然而這在MongoDB中是行不通的,因為MongoDB的文件是沒有預(yù)定義數(shù)據(jù)模型的。這樣的話MongoDB會插入你輸入的任何數(shù)據(jù)。
總結(jié):使用準(zhǔn)確的數(shù)據(jù)類型。
關(guān)于鎖
當(dāng)資源被代碼的多個部分所共享時,需要確信鎖必須要確保這處資源只能在一個地方被操作。
舊版本的MongoDB (pre 2.0)擁有一個全局的寫入鎖。這就意味貫穿整個服務(wù)器中只有一個地方做寫操作。這就可能導(dǎo)致數(shù)據(jù)庫因為某個地方鎖定超負(fù)載而停滯。這個問題在2.0版本中的得到了顯著的改善,并且在當(dāng)前2.2版本中得到了進(jìn)一步的加強。MongoDB 2.2使用數(shù)據(jù)庫級別的鎖在這個問題上邁進(jìn)了一大步。同樣值得期待的Collection級別的鎖也計劃在下一個版本中推出。
盡管如此,Russell還是認(rèn)為:大多數(shù)受此限制的應(yīng)用程序于其說是受MongoDB影響,還不如說是程序本身的問題來的更直接。
總結(jié):使用***的穩(wěn)定版本才能獲得***的性能。
關(guān)于包
在類Ubuntu和Debian系統(tǒng)上安裝時,許多人都出現(xiàn)過“過時版本”這樣的問題。解決方案很簡單:使用10gen官方庫,那么在Ubuntu和Debian上安裝也會像在Fedora和Centos上安裝一樣流暢。
總結(jié):使用擁有大多數(shù)***版本的官方包。
使用偶數(shù)個Replica Set成員
Replica Set是增加冗余及提升MongoDB數(shù)據(jù)集群性能的有效途徑。數(shù)據(jù)在所有的節(jié)點中被復(fù)制,并選出一個作為主節(jié)點。假如主節(jié)點出故障,那么會在其他的節(jié)點中票選一個作為新的主節(jié)點。
在同一個Replica Set中使用兩臺機器是很有誘惑的,它比3臺機器來的便宜并且也是RDBMS的標(biāo)準(zhǔn)行事風(fēng)格。
但是到了MongoDB這里,同一個Replica Set中的成員數(shù)量只能是奇數(shù)個。假如你使用了偶數(shù)個成員,那么當(dāng)主節(jié)點發(fā)生故障時那么其它的節(jié)點都會變成只讀。發(fā)生這種情況是因為剩下待選節(jié)點的數(shù)目不滿足票選主節(jié)點的規(guī)定。
如果你想節(jié)約成本,同時還希望支持故障轉(zhuǎn)移和冗余的增強,那么你可以使用Arbiter。Arbiter是一種特殊的Replica Set成員,它不儲存任何用戶數(shù)據(jù)(這就意味著他們可以使用非常小的服務(wù)器)。
總結(jié):只可以使用偶數(shù)個Replica Set成員,但是可以使用Arbitter來削減成本。
沒有join語句
MongoDB不支持join:如果你想在多個Collection中檢索數(shù)據(jù),那么你必須做多次的查詢。
如果你覺得你手動做的查詢太多了,你可以重設(shè)計你的數(shù)據(jù)模型來減少整體查詢的數(shù)量。MongoDB中的文件可以是任何類型,那么可以輕易的對數(shù)據(jù)進(jìn)行De-Normalize。這樣就可以讓它始終和你的應(yīng)用程序保持一致。
總結(jié):沒有join不妨看一下如何設(shè)計數(shù)據(jù)結(jié)構(gòu)模型。
Journaling
MongoDB使用內(nèi)存映射文件并且每60秒向磁盤輸出一次通知,這就意味著***程度上你可能丟失60秒加上向硬盤輸出通知這段時間內(nèi)所有的數(shù)據(jù)。
為了避免數(shù)據(jù)丟失,MongoDB從2.0版本起就添加了Journaling(默認(rèn)情況下開啟)。Journaling把時間從60秒更改為100ms。如果數(shù)據(jù)庫意外的停機,在啟動之前它將會被重啟用以確保數(shù)據(jù)庫處于一致狀態(tài)。這也是MongoDB與傳統(tǒng)數(shù)據(jù)庫最接近的地方。
當(dāng)然Journaling會輕微的影響到性能,大約5%。但是對于多數(shù)人來說額外帶來的安全性肯定是物有所值的。
總結(jié):***別關(guān)閉Journaling。
#p#
默認(rèn)情況下沒有身份認(rèn)證
MongoDB在默認(rèn)設(shè)置下并沒有身份驗證。MongoDB會認(rèn)為自身處在一個擁有防火墻的信任網(wǎng)絡(luò)。但是這不代表它不支持身份驗證,如果需要可以輕松的開啟。
總結(jié):MongoDB的安全性可以通過使用防火墻和綁定正確的接口來保證,當(dāng)然也可以開啟身份驗證。
Replica Set中損失的數(shù)據(jù)
使用Replica Set是提高系統(tǒng)可靠性及易維護的有效途徑。這樣的話,弄清節(jié)點間故障的發(fā)生及轉(zhuǎn)移機制就變得至關(guān)重要。
Replica Set中的成員一般通過oplog(記錄了數(shù)據(jù)中發(fā)生增、刪、改等操作的列表)來傳遞信息,當(dāng)其中一個成員發(fā)生變化修改oplog后,其他的成員也將按照oplog來執(zhí)行。如果你負(fù)責(zé)處理新數(shù)據(jù)的節(jié)點在出錯后恢復(fù)運行,它將會被回滾至***一個oplog公共點。然而在這個過程中:丟失的“新數(shù)據(jù)”已經(jīng)被MongoDB從數(shù)據(jù)庫中轉(zhuǎn)移并存放到你的數(shù)據(jù)目錄‘rollback’里面等待被手動恢復(fù)。如果你不知道這個特性,你可能就會認(rèn)為數(shù)據(jù)被弄丟了。所以每當(dāng)有成員從出錯中恢復(fù)過來都必須要檢查這個目錄。而通過MongoDB發(fā)布的標(biāo)準(zhǔn)工具來恢復(fù)這些數(shù)據(jù)是件很容易的事情。查看官方文檔以了解更多相關(guān)信息。
總結(jié):故障恢復(fù)中丟失的數(shù)據(jù)將會出現(xiàn)在rollback目錄里面。
分片太遲
分片是把數(shù)據(jù)拆分到多臺機器上,通常被用于Replica Set運行過慢時進(jìn)行性能提升。MongoDB支持自動分片。然而如果你讓分片進(jìn)行太遲的話,問題就產(chǎn)生了。因為對數(shù)據(jù)的拆分和塊的遷移需要時間和資源,所以如果當(dāng)服務(wù)器資源基本上耗盡時很可能會導(dǎo)致在你最需要分片時卻分不了片。
解決的方法很簡單,使用一個工具對MongoDB進(jìn)行監(jiān)視。對你的服務(wù)器做最準(zhǔn)確的評估,并且在占整體性能的80%前進(jìn)行分片。類似的監(jiān)視工具有:MMS、Munin(+Mongo Plugin)和CloudWatch。
如果你確定從一開始就要分片處理,那么更好的建議會是選用AWS或者類似的云服務(wù)進(jìn)行分片。而在小型服務(wù)器上,關(guān)機或者是調(diào)整機器明顯比轉(zhuǎn)移成千上萬條數(shù)據(jù)塊來的更直接一點。
總結(jié):盡早的分片才能有效的避免問題。
不可以更改文件中的shard key
對于分片設(shè)置,shard key是MongoDB用來識別分塊對應(yīng)文件的憑證。當(dāng)你插入一個文件后,你就不可以對文件的shard key進(jìn)行更改。而這里的解決方案是把文檔刪除然后重新建立,這樣就允許把它指定到對應(yīng)的分塊了。
總結(jié):shard key不可以修改,必要的時候可以刪除文件重新建立。
不可以對256G以上的Collection進(jìn)行分片
重新回到分片太遲的問題上來 —— MongoDB不允許對增長到256G以上的Collection進(jìn)行分片,之前版本的設(shè)置還沒有256G。這個限定在以后肯定會被移除,而這里也沒有更好的解決方案。只能進(jìn)行重編譯或者把大小控制在256G以下。
總結(jié):在Collection達(dá)到256G以前進(jìn)行分片。
唯一性索引與共享
索引的唯一性約束只能通過shard key來保證。
選擇了錯誤的shard key
MongDB需要你選擇一個shard key來將數(shù)據(jù)分片。如果選擇了錯誤的shard key,更改起來將是件很麻煩的事情。
總結(jié):選擇shard key之前先閱讀這個文檔。
與MongoDB通信的未經(jīng)加密
與MongoDB的連接默認(rèn)情況下都是非加密的,這就意味你的數(shù)據(jù)可能被第三方記錄和使用。如果你的MongoDB是在自己的非廣域網(wǎng)下使用,那么這種情況是不可能發(fā)生的。
然而如果你是通過公網(wǎng)訪問MongoDB的話,那么你肯定會希望你的通信是經(jīng)過加密的。公版的MongoDB是不支持SSL的。慶幸的是可以非常簡單的定制自己的版本。10gen的用戶則擁有特別定制的加密版本。幸運的是大部分的官方驅(qū)動都支持SSL,但是小麻煩同樣是不可避免的。點擊查看文檔。
總結(jié):當(dāng)用公網(wǎng)連接時,要注意和MongoDB的通信是未加密的。
事務(wù)
不像MySQL這些支持多行數(shù)據(jù)原子操作的傳統(tǒng)數(shù)據(jù)庫,MongoDB只支持單文件的原子性修改。解決這個問題的方法之一是在應(yīng)用程序中使用異步提交的方式;另一個是:建立一個以上的數(shù)據(jù)存儲。雖然***種方法并不適用于所有情況,但是很顯然比第二個來的要好。
總結(jié):不支持對多文件事務(wù)。
日志預(yù)分配慢
MongDB可能會告訴你已經(jīng)準(zhǔn)備就緒,但事實上它還在對日志進(jìn)行分配。如果你選擇了讓機器自行分配,而恰巧你的文件系統(tǒng)和磁盤速度又很慢,那么煩惱的事情發(fā)生了。通常情況下這不會成為問題,但是一旦出現(xiàn)了可以使用undocumented flag –nopreallocj來關(guān)閉預(yù)分配。
總結(jié):如果機器文件系統(tǒng)和磁盤過慢的話,那么日志的預(yù)分配也可能很慢。
NUMA + Linux +MongoDB
Linux、NUMA與MongoDB遇到一起的時候運行總是不會很好。如果你在NUMA硬件上運行MongoDB的話,這里建議是直接關(guān)掉。因為各種奇怪的問題隨之而來,比如:速度會階段性或者在CPU占用率很高的時候大幅下降。
總結(jié):禁NUMA。
Linux里面的進(jìn)程限制
如果你在MongoDB未滿載的時候出過SEGMENTATION FAULT錯誤,你可能會發(fā)現(xiàn)這是因為使用了過低或者默認(rèn)的打開文件或用戶進(jìn)程限制。10gen建議把限制設(shè)置在4K+,然而設(shè)置的大小該取決具體情況。閱讀ulimit了解更多。
總結(jié):長久的為MongoDB在Linux加上軟或硬的打開文件或用戶進(jìn)程限制。





























