你应该知道的 MongoDB 最佳实践
https://mp.weixin.qq.com/s/zWS1ifKsdh16hV6yT_sXow
前言
作為MongoDB的一名方案架構(gòu)師,我的大部分時(shí)間都是在和MongoDB的客戶和用戶交互。在這里,我希望通過(guò)一個(gè)不斷更新的活文章的方式來(lái)為大家收集整理一下MongoDB開(kāi)發(fā)及維護(hù)時(shí)候值得了解或者遵從的一些最佳實(shí)踐。我非常真切地希望您也可以參與進(jìn)來(lái),共同維護(hù)這個(gè)文檔,讓更多的用戶受惠(可以通過(guò)文末微信號(hào)聯(lián)系我)
本文包括以下幾個(gè)方面:
- 安全措施 - 部署架構(gòu) - 系統(tǒng)優(yōu)化 - 監(jiān)控與備份 - 索引 - 開(kāi)發(fā)與模式如果您正在打算上線或者已經(jīng)上線基于MongoDB的核心業(yè)務(wù)應(yīng)用,請(qǐng)和MongoDB 大中華區(qū)團(tuán)隊(duì)聯(lián)絡(luò)(team-china@mongodb.com)以獲得來(lái)自官方權(quán)威顧問(wèn)團(tuán)隊(duì)的專業(yè)建議。
關(guān)于安全
為MongoDB集群?jiǎn)⒂谜J(rèn)證鑒權(quán)
MongoDB服務(wù)器在默認(rèn)安裝下不啟用鑒權(quán)。這意味著每個(gè)人都可以直接連接到mongod實(shí)例并執(zhí)行任意數(shù)據(jù)庫(kù)操作。建議按照文檔啟用鑒權(quán) http://docs.mongoing.com/manual-zh/tutorial/enable-authentication.html
為不同用戶分配不同的角色權(quán)限
MongoDB支持按角色定義的權(quán)限系統(tǒng)。你應(yīng)該基于“最少權(quán)限”準(zhǔn)則,顯式的為用戶分配僅需要的相應(yīng)權(quán)限。
使用中央鑒權(quán)服務(wù)器
盡可能使用LDAP、Kerbero之類的中央鑒權(quán)服務(wù)器,并使用強(qiáng)口令策略。
為需要訪問(wèn)MongoDB的應(yīng)用服務(wù)器創(chuàng)建白名單(防火墻配置)
如果你的服務(wù)器有多個(gè)網(wǎng)卡,建議只在內(nèi)網(wǎng)的IP上監(jiān)聽(tīng)服務(wù)。
對(duì)敏感數(shù)據(jù)使用加密引擎
MongoDB企業(yè)版支持存儲(chǔ)加密,對(duì)涉及到客戶的敏感數(shù)據(jù)應(yīng)該使用加密引擎來(lái)保護(hù)數(shù)據(jù)。
關(guān)于部署
至少使用3個(gè)數(shù)據(jù)節(jié)點(diǎn)的復(fù)制集
MongoDB的建議最小部署是3個(gè)數(shù)據(jù)節(jié)點(diǎn)構(gòu)成的復(fù)制集。復(fù)制集可以提供以下優(yōu)點(diǎn):
- 系統(tǒng)99.999% 高可用
- 自動(dòng)故障切換
- 數(shù)據(jù)冗余
- 容災(zāi)部署
- 讀寫分離
不用太早分片
分片可以用來(lái)擴(kuò)展你系統(tǒng)的讀寫能力,但是分片也會(huì)帶來(lái)不少新的挑戰(zhàn)比如說(shuō)管理上的復(fù)雜度,成本的增加,選擇合適片鍵的挑戰(zhàn)性等等。一般來(lái)說(shuō),你應(yīng)該先窮盡了其他的性能調(diào)優(yōu)的選項(xiàng)以后才開(kāi)始考慮分片,比如說(shuō),索引優(yōu)化,模式優(yōu)化,代碼優(yōu)化,硬件資源優(yōu)化,IO優(yōu)化等。
選擇合適的分片數(shù)
分片的一些觸發(fā)條件為:
-
數(shù)據(jù)總量太大,無(wú)法在一臺(tái)服務(wù)器上管理
-
并發(fā)量太高,一臺(tái)服務(wù)器無(wú)法及時(shí)處理
-
磁盤IO壓力太大
-
單機(jī)系統(tǒng)內(nèi)存不夠大,無(wú)法裝下熱數(shù)據(jù)
-
服務(wù)器網(wǎng)卡處理能力達(dá)到瓶頸
-
多地部署情況下希望支持本地化讀寫
取決于你分片的觸發(fā)條件,你可以按照總的需求 然后除以每一臺(tái)服務(wù)器的能力來(lái)確定所需的分片數(shù)。
為每個(gè)分片部署足夠的復(fù)制集成員
分片之間的數(shù)據(jù)互相不復(fù)制。每個(gè)分片的數(shù)據(jù)必須在分片內(nèi)保證高可用。因此,對(duì)每一個(gè)分片MongoDB要求至少部署3個(gè)數(shù)據(jù)節(jié)點(diǎn)來(lái)保證該分片在絕大部分時(shí)間都不會(huì)因?yàn)橹鞴?jié)點(diǎn)宕機(jī)而造成數(shù)據(jù)不可用。
選擇合適的片鍵
在分片場(chǎng)景下, 最重要的一個(gè)考量是選擇合適的片鍵。選擇片鍵需要考慮到應(yīng)用的讀寫模式。通常來(lái)說(shuō)一個(gè)片鍵要么是對(duì)寫操作優(yōu)化,要么是對(duì)讀操作優(yōu)化。要根據(jù)哪種操作更加頻繁而進(jìn)行相應(yīng)的權(quán)衡。
-
片鍵值應(yīng)該具有很高的基數(shù),或者說(shuō),這個(gè)片鍵在集合內(nèi)有很多不同的值,例如_id就是一個(gè)基數(shù)很高的片鍵因?yàn)開(kāi)id值不會(huì)重復(fù)
-
片鍵一般不應(yīng)該是持續(xù)增長(zhǎng)的,比如說(shuō)timestamp就是個(gè)持續(xù)增長(zhǎng)的片鍵。此類片鍵容易造成熱分片現(xiàn)象,即新的寫入集中到某一個(gè)分片上
-
好的片鍵應(yīng)該會(huì)讓查詢定向到某一個(gè)(或幾個(gè))分片上從而提高查詢效率。一般來(lái)說(shuō)這個(gè)意味著片鍵應(yīng)該包括最常用查詢用到的字段
-
好的片鍵應(yīng)該足夠分散,讓新的插入可以分布到多個(gè)分片上從而提高并發(fā)寫入率。
-
可以使用幾個(gè)字段的組合來(lái)組成片鍵,以達(dá)到幾個(gè)不同的目的(基數(shù),分散性,及查詢定向等)
關(guān)于系統(tǒng)
使用SSD 或RAID10 來(lái)提高存儲(chǔ)IOPS能力
MongoDB是一個(gè)高性能高并發(fā)的數(shù)據(jù)庫(kù),其大部分的IO操作為隨機(jī)更新。一般來(lái)說(shuō)本機(jī)自帶的SSD是最佳的存儲(chǔ)方案。如果使用普通的硬盤,建議使用RAID10條帶化來(lái)提高IO通道的并發(fā)能力。
為Data和Journal/log使用單獨(dú)的物理卷
MongoDB很多的性能瓶頸和IO相關(guān)。建議為日志盤(Journal和系統(tǒng)日志)單獨(dú)設(shè)定一個(gè)物理卷,減少對(duì)數(shù)據(jù)盤IO的資源占用。
系統(tǒng)日志可以直接在命令行或者配置文件參數(shù)內(nèi)指定。Journal日志不支持直接指定到另外的目錄,可以通過(guò)對(duì)Journal目錄創(chuàng)建symbol link的方式來(lái)解決。
使用XFS 文件系統(tǒng)
MongoDB在WiredTiger存儲(chǔ)引擎下建議使用XFS文件系統(tǒng)。Ext4最為常見(jiàn),但是由于ext文件系統(tǒng)的內(nèi)部journal和WiredTiger有所沖突,所以在IO壓力較大情況下表現(xiàn)不佳。
WiredTiger下謹(jǐn)慎使用超大緩存
WiredTiger 對(duì)寫操作的落盤是異步發(fā)生的。默認(rèn)是60秒做一次checkpoint。做checkpoint需要對(duì)內(nèi)存內(nèi)所有臟數(shù)據(jù)遍歷以便整理然后把這些數(shù)據(jù)寫入硬盤。如果緩存超大(如大于128G),那么這個(gè)checkpoint時(shí)間就需要較長(zhǎng)時(shí)間。在checkpoint期間數(shù)據(jù)寫入性能會(huì)受到影響。目前建議實(shí)際緩存設(shè)置在64GB或以下。
關(guān)閉 Transparent Huge Pages
Transparent Huge Pages (THP) 是Linux的一種內(nèi)存管理優(yōu)化手段,通過(guò)使用更大的內(nèi)存頁(yè)來(lái)減少Translation Lookaside Buffer(TLB)的額外開(kāi)銷。 MongoDB數(shù)據(jù)庫(kù)大部分是比較分散的小量數(shù)據(jù)讀寫,THP對(duì)MongoDB這種工況會(huì)有負(fù)面的影響所以建議關(guān)閉。
http://docs.mongoing.com/manual-zh/tutorial/transparent-huge-pages.html
啟用Log Rotation
防止MongoDB 的log文件無(wú)限增大,占用太多磁盤空間。好的實(shí)踐是啟用log rotation并及時(shí)清理歷史日志文件。
http://docs.mongoing.com/manual-zh/tutorial/rotate-log-files.html
分配足夠的Oplog空間
足夠的Oplog空間可以保證有足夠的時(shí)間讓你從頭恢復(fù)一個(gè)從節(jié)點(diǎn),或者對(duì)從節(jié)點(diǎn)執(zhí)行一些比較耗時(shí)的維護(hù)操作。假設(shè)你最長(zhǎng)的下線維護(hù)操作需要H小時(shí),那么你的Oplog 一般至少要保證可以保存 H?2 或者 H3 小時(shí)的oplog。
如果你的MongoDB部署的時(shí)候未設(shè)置正確的Oplog 大小,可以參照下述鏈接來(lái)調(diào)整:
http://docs.mongoing.com/manual-zh/tutorial/change-oplog-size.html
關(guān)閉數(shù)據(jù)庫(kù)文件的 atime
禁止系統(tǒng)對(duì)文件的訪問(wèn)時(shí)間更新會(huì)有效提高文件讀取的性能。這個(gè)可以通過(guò)在 /etc/fstab 文件中增加 noatime 參數(shù)來(lái)實(shí)現(xiàn)。例如:
/dev/xvdb /data ext4 noatime 0 0修改完文件后重新 mount就可以:
# mount -o remount /data提高默認(rèn)文件描述符和進(jìn)程/線程數(shù)限制
Linux默認(rèn)的文件描述符數(shù)和最大進(jìn)程數(shù)對(duì)于MongoDB來(lái)說(shuō)一般會(huì)太低。建議把這個(gè)數(shù)值設(shè)為64000。因?yàn)镸ongoDB服務(wù)器對(duì)每一個(gè)數(shù)據(jù)庫(kù)文件以及每一個(gè)客戶端連接都需要用到一個(gè)文件描述符。如果這個(gè)數(shù)字太小的話在大規(guī)模并發(fā)操作情況下可能會(huì)出錯(cuò)或無(wú)法響應(yīng)。 你可以通過(guò)以下命令來(lái)修改這些值:
ulimit -n 64000 ulimit -u 64000禁止 NUMA
在一個(gè)使用NUMA技術(shù)的多處理器Linux 系統(tǒng)上,你應(yīng)該禁止NUMA的使用。MongoDB在NUMA環(huán)境下運(yùn)行性能有時(shí)候會(huì)可能變慢,特別是在進(jìn)程負(fù)載很高的情況下。
預(yù)讀值(readahead)設(shè)置
預(yù)讀值是文件操作系統(tǒng)的一個(gè)優(yōu)化手段,大致就是在程序請(qǐng)求讀取一個(gè)頁(yè)面的時(shí)候,文件系統(tǒng)會(huì)同時(shí)讀取下面的幾個(gè)頁(yè)面并返回。這原因是因?yàn)楹芏鄷r(shí)候IO最費(fèi)時(shí)的磁盤尋道。通過(guò)預(yù)讀,系統(tǒng)可以提前把緊接著的數(shù)據(jù)同時(shí)返回。假設(shè)程序是在做一個(gè)連續(xù)讀的操作,那么這樣可以節(jié)省很多磁盤尋道時(shí)間。
MongoDB很多時(shí)候會(huì)做隨機(jī)訪問(wèn)。對(duì)于隨機(jī)訪問(wèn),這個(gè)預(yù)讀值應(yīng)該設(shè)置的較小為好.一般來(lái)說(shuō)32是一個(gè)不錯(cuò)的選擇。
你可以使用下述命令來(lái)顯示當(dāng)前系統(tǒng)的預(yù)讀值:
sudo blockdev --report要更改預(yù)讀值,可以用以下命令:
sudo blockdev --setra 32把?換成合適的存儲(chǔ)設(shè)備。
使用NTP時(shí)間服務(wù)器
在使用MongoDB復(fù)制集或者分片集群的時(shí)候,注意一定要使用NTP時(shí)間服務(wù)器。這樣可以保證MongoDB集群成原則之間正確同步。
關(guān)于監(jiān)控及備份
對(duì)重要的數(shù)據(jù)庫(kù)指標(biāo)進(jìn)行監(jiān)控及告警
關(guān)鍵的指標(biāo)包括:
- Disk Space 磁盤空間 - CPU - RAM 使用率 - Ops Counter 增刪改查 - Replication Lag 復(fù)制延遲 - Connections 連接數(shù) - Oplog Window對(duì)慢查詢?nèi)罩具M(jìn)行監(jiān)控
默認(rèn)情況下MongoDB會(huì)在日志文件中(mongod.log)記錄超過(guò)100ms的數(shù)據(jù)庫(kù)操作。
關(guān)于索引
為你的每一個(gè)查詢建立合適的索引
這個(gè)是針對(duì)于數(shù)據(jù)量較大比如說(shuō)超過(guò)幾十上百萬(wàn)(文檔數(shù)目)數(shù)量級(jí)的集合。如果沒(méi)有索引MongoDB需要把所有的Document從盤上讀到內(nèi)存,這會(huì)對(duì)MongoDB服務(wù)器造成較大的壓力并影響到其他請(qǐng)求的執(zhí)行。
創(chuàng)建合適的組合索引,不要依賴于交叉索引
如果你的查詢會(huì)使用到多個(gè)字段,MongoDB有兩個(gè)索引技術(shù)可以使用:交叉索引和組合索引。交叉索引就是針對(duì)每個(gè)字段單獨(dú)建立一個(gè)單字段索引,然后在查詢執(zhí)行時(shí)候使用相應(yīng)的單字段索引進(jìn)行索引交叉而得到查詢結(jié)果。交叉索引目前觸發(fā)率較低,所以如果你有一個(gè)多字段查詢的時(shí)候,建議使用組合索引能夠保證索引正常的使用。
例如,如果應(yīng)用需要查找所有年齡小于30歲的深圳市馬拉松運(yùn)動(dòng)員:
db.athelets.find({sport: "marathon", location: "sz", age: {$lt: 30}}})那么你可能需要這樣的一個(gè)索引:
db.athelets.ensureIndex({sport:1, location:1, age:1});組合索引字段順序:匹配條件在前,范圍條件在后(Equality First, Range After)
以上文為例子,在創(chuàng)建組合索引時(shí)如果條件有匹配和范圍之分,那么匹配條件(sport: “marathon”) 應(yīng)該在組合索引的前面。范圍條件(age: <30)字段應(yīng)該放在組合索引的后面。
盡可能使用覆蓋索引(Covered Index)
有些時(shí)候你的查詢只需要返回很少甚至只是一個(gè)字段,例如,希望查找所有虹橋機(jī)場(chǎng)出發(fā)的所有航班的目的地。已有的索引是:
{origin: 1, dest: 1}如果正常的查詢會(huì)是這樣(只需要返回目的地機(jī)場(chǎng)):
db.flights.find({origin:"hongqiao"}, {dest:1});這樣的查詢默認(rèn)會(huì)包含_id 字段,所以需要掃描匹配的文檔并取回結(jié)果。相反,如果使用這個(gè)查詢語(yǔ)句:
db.flights.find({origin:"hongqiao"}, {_id:0, dest:1});MongoDB則可以直接從索引中取得所有需要返回的值,而無(wú)需掃描實(shí)際文檔(文檔可能需要從硬盤里調(diào)入到內(nèi)存)
建索引要在后臺(tái)運(yùn)行
在對(duì)一個(gè)集合創(chuàng)建索引時(shí),該集合所在的數(shù)據(jù)庫(kù)將不接受其他讀寫操作。對(duì)數(shù)據(jù)量的集合建索引,建議使用后臺(tái)運(yùn)行選項(xiàng) {background: true}
關(guān)于開(kāi)發(fā)
模式設(shè)計(jì)
不要按照關(guān)系型來(lái)設(shè)計(jì)表結(jié)構(gòu)
MongoDB可以讓你像關(guān)系型數(shù)據(jù)庫(kù)一樣設(shè)計(jì)表結(jié)構(gòu),但是它不支持外鍵,也不支持復(fù)雜的Join!如果你的程序發(fā)現(xiàn)有大量實(shí)用JOIN的地方,那你的設(shè)計(jì)可能需要重新來(lái)過(guò)。參照以下相關(guān)模式設(shè)計(jì)建議。
數(shù)據(jù)庫(kù)集合(collection)的數(shù)量不宜太多
MongoDB的模式設(shè)計(jì)基于靈活豐富的JSON文檔模式。在很多情況下,一個(gè)MongoDB應(yīng)用的數(shù)據(jù)庫(kù)內(nèi)的集合(表)的數(shù)量應(yīng)該遠(yuǎn)遠(yuǎn)小于使用關(guān)系數(shù)據(jù)庫(kù)的同類型應(yīng)用。MongoDB表設(shè)計(jì)不遵從第三范式。MongoDB的數(shù)據(jù)模型非常接近于對(duì)象模型,所以基本上就是按照主要的Domain object的數(shù)量來(lái)建相應(yīng)的集合。根據(jù)經(jīng)驗(yàn),一般小型應(yīng)用的集合數(shù)量通常在幾個(gè)之內(nèi),中大型的應(yīng)用會(huì)在10多個(gè)或者最多幾十個(gè)。
不要害怕數(shù)據(jù)冗余
MongoDB模式設(shè)計(jì)不能按照第三范式,很多時(shí)候允許數(shù)據(jù)在多個(gè)文檔中重復(fù),比如說(shuō),在每一個(gè)員工的文檔中重復(fù)他的部門名字,就是一個(gè)可以接受的做法。如果部門名字改了,可以執(zhí)行一個(gè)update({},{}, {multi:true}) 的多文檔更新來(lái)一次性把部門名字更新掉。
適合和不適合冗余的數(shù)據(jù)類型
一般來(lái)說(shuō),如果某個(gè)字段的數(shù)據(jù)值經(jīng)常會(huì)變,則不太適合被大量冗余到別的文檔或者別的集合里面去。舉例來(lái)說(shuō),如果我們是在做一些股票類型資產(chǎn)管理, 可能有很多人都購(gòu)買了Apple的股票,但是如果把經(jīng)常變動(dòng)的股價(jià)冗余到客戶的文檔里,由于股票價(jià)格變動(dòng)頻繁,會(huì)導(dǎo)致有大量的更新操作。從另外一個(gè)角度來(lái)說(shuō),如果是一些不經(jīng)常變的字段,如客戶的姓名,地址,部門等,則可以盡管進(jìn)行冗余shi’yang
對(duì) 1:N(一些)的關(guān)系使用全部?jī)?nèi)嵌
對(duì)于一對(duì)多的關(guān)系,如一個(gè)人有幾個(gè)聯(lián)系方式,一本書有10幾個(gè)章節(jié),等等,建議使用內(nèi)嵌方式,把N的數(shù)據(jù)以數(shù)組形式來(lái)描述,如:
? ?> db.person.findOne(){user_id: 'tjworks',name: 'TJ Tang', ? ? ? ? ?contact : [{ type: 'mobile', number: '1856783691' },{ type: 'wechat', number: 'tjtang826'}]}對(duì) 1: NN (很多) 的關(guān)系使用ID內(nèi)嵌
有些時(shí)候這個(gè)一對(duì)多的多端數(shù)量較大, 比如說(shuō),一個(gè)部門內(nèi)有多少員工。在華為一個(gè)三級(jí)部門可能有數(shù)千員工,這個(gè)時(shí)候如果把所有員工信息直接內(nèi)嵌到部門內(nèi)肯定不是個(gè)好的選擇,有可能會(huì)超出16MB的文檔限制。這個(gè)時(shí)候可以采用引用ID的方式:
> db.departments.findOne() {name : 'Enterprise BG',president: 'Zhang San',employees : [ ? ? // array of references to Employee colletionObjectID('AAAA'), ? ?ObjectID('F17C'), ? ?ObjectID('D2AA'),// etc] }如果需要查詢部門下員工相關(guān)信息,你可以使用$lookup聚合操作符來(lái)把員工信息進(jìn)行關(guān)聯(lián)并返回。
對(duì) 1: NNN (很多) 的關(guān)系使用
如果一對(duì)多情況下,這個(gè)多端數(shù)量無(wú)限大并會(huì)頻繁增長(zhǎng),比如說(shuō),一個(gè)測(cè)量?jī)x的每分鐘讀數(shù),一年下來(lái)有幾十萬(wàn)條,這個(gè)時(shí)候即使是把ID放到數(shù)組里都會(huì)管理不便,這個(gè)時(shí)候就應(yīng)該把多端的數(shù)據(jù)創(chuàng)建一個(gè)集合,并在那個(gè)集合的文檔里加入對(duì)主文檔的連接引用,如:
? ?> db.sensors.findOne(){_id : ObjectID('AAAB'),name : 'engine temperature',vin : '4GD93039GI239',engine_id: '20394802',manuafacture: 'First Motor',production_date: '2014-02-01'...}>db.readings.findOne(){time : ISODate("2014-03-28T09:42:41.382Z"),sensor: ObjectID('AAAB'),reading: 67.4 ? ? ? ? ? ?}把二進(jìn)制大文件和元數(shù)據(jù)分集合存放
如果你有需要把PDF文件,圖片,甚至小視頻等二進(jìn)制文件需要管理,建議使用MongoDB 的GridFS API 或者自己手動(dòng)分集合來(lái)分開(kāi)管理二進(jìn)制數(shù)據(jù)和元數(shù)據(jù)。
經(jīng)常更新的數(shù)據(jù)不要放在嵌套數(shù)組內(nèi)
數(shù)組是用來(lái)表達(dá) 1對(duì)多關(guān)系的利器,但是MongoDB對(duì)嵌套的數(shù)組內(nèi)元素缺乏直接更新能力。比如說(shuō):
{name: "Annice",courses: [{ name: "English", score: 97 },{ name: "Math", score: 89 },{ name: "Physics", score: 95 }] }這樣設(shè)計(jì)沒(méi)有嵌套數(shù)組,我們可以直接對(duì) Math的score 修改為99:
db.students.update({name: "Annice", "courses.name":"Math"}, {$set:{"courses.$.score": 99 }})注意數(shù)組定位符 $ 的用法,$ 表示當(dāng)前匹配的第一個(gè)數(shù)組元素的在數(shù)組內(nèi)的索引。
但是下面這種情況就涉及到了數(shù)組嵌套:
? ?{name: "Annice",courses: [{ name: "Math", scores: [ {term: 1, score: 80} ,{term: 2, score: 90}] },{ name: "Physics", score: 95 }]}這個(gè)時(shí)候如果你想對(duì)Math course的term 1的Score進(jìn)行修改,你就需要把 scores 這個(gè)數(shù)組整個(gè)調(diào)到內(nèi)存然后在代碼里對(duì)這個(gè)嵌套數(shù)組的元素進(jìn)行修改。這是因?yàn)镸ongoDB的數(shù)組定位符 $ 只對(duì)第一層數(shù)組有效。
當(dāng)然,如果你的模型不需要修改嵌套的數(shù)組內(nèi)元素,那么這條就不適用。
程序配置
設(shè)定合適的MongoDB連接池大小 (Connections Per Host)
Java驅(qū)動(dòng)的默認(rèn)連接池大小是100。建議按照應(yīng)用的實(shí)際情況做調(diào)整。對(duì)壓力較小的應(yīng)用可以適當(dāng)調(diào)小減少對(duì)應(yīng)用服務(wù)器的資源占用。
正確使用寫關(guān)注設(shè)置(Write Concern)
MongoDB的建議最小部署是一個(gè)復(fù)制集,包含3個(gè)數(shù)據(jù)節(jié)點(diǎn)。默認(rèn)情況下應(yīng)用的寫操作(更新,插入或者刪除)在主節(jié)點(diǎn)上完成后就會(huì)立即返回。寫操作則通過(guò)OPLOG方式在后臺(tái)異步方式復(fù)制到其他節(jié)點(diǎn)。在極端情況下,這些寫操作可能還未在復(fù)制到從節(jié)點(diǎn)的時(shí)候主節(jié)點(diǎn)就出現(xiàn)宕機(jī)。這個(gè)時(shí)候發(fā)生主備節(jié)點(diǎn)切換,原主節(jié)點(diǎn)的寫操作會(huì)被回滾到文件而對(duì)應(yīng)用不可見(jiàn)。為防止這種情況出現(xiàn),MongoDB建議對(duì)重要的數(shù)據(jù)使用 {w: “marjority”} 的選項(xiàng)。{w: “majority”} 可以保證數(shù)據(jù)在復(fù)制到多數(shù)節(jié)點(diǎn)后才返回成功結(jié)果。使用該機(jī)制可以有效防止數(shù)據(jù)回滾的發(fā)生。
另外你可以使用 {j:1} (可以和 w:”majrotiy” 結(jié)合使用) 來(lái)指定數(shù)據(jù)必須在寫入WAL日志之后才向應(yīng)用返回成功確認(rèn)。這個(gè)會(huì)導(dǎo)致寫入性能有所下降,但是對(duì)于重要的數(shù)據(jù)可以考慮使用。
正確使用讀選項(xiàng)設(shè)置(Read Preference)
MongoDB由于是一個(gè)分布式系統(tǒng),一份數(shù)據(jù)會(huì)在多個(gè)節(jié)點(diǎn)上進(jìn)行復(fù)制。從哪個(gè)節(jié)點(diǎn)上讀數(shù)據(jù),要根據(jù)應(yīng)用讀數(shù)據(jù)的需求而定。以下是集中可以配置的讀選項(xiàng):
-
primary: 默認(rèn),在主節(jié)點(diǎn)上讀數(shù)據(jù)
-
priaryPreferred: 先從主節(jié)點(diǎn)上讀,如果為成功再到任意一臺(tái)從節(jié)點(diǎn)上讀
-
secondary: 在從節(jié)點(diǎn)上讀數(shù)據(jù)(當(dāng)有多臺(tái)節(jié)點(diǎn)的時(shí)候,隨機(jī)的使用某一臺(tái)從節(jié)點(diǎn))
-
secondaryPreferred: 首先從從節(jié)點(diǎn)上讀,如果從節(jié)點(diǎn)由于某種原因不能提供服務(wù),則從主節(jié)點(diǎn)上進(jìn)行讀
-
nearest: 從距離最近的節(jié)點(diǎn)來(lái)讀。距離由ping操作的時(shí)間來(lái)決定。
除第一個(gè)選項(xiàng)之外,其他讀選項(xiàng)都存在讀到的數(shù)據(jù)不是最新的可能。原因是數(shù)據(jù)的復(fù)制是后臺(tái)異步完成的。
不要實(shí)例化多個(gè)MongoClient
MongoClient是個(gè)線程安全的類,自帶線程池。通常在一個(gè)JVM內(nèi)不要實(shí)例化多個(gè)MongoClient實(shí)例,避免連接數(shù)過(guò)多和資源的不必要浪費(fèi)。
對(duì)寫操作使用Retry機(jī)制
MongoDB使用復(fù)制集技術(shù)可以實(shí)現(xiàn)99.999%的高可用。當(dāng)一臺(tái)主節(jié)點(diǎn)不能寫入時(shí),系統(tǒng)會(huì)自動(dòng)故障轉(zhuǎn)移到另一臺(tái)節(jié)點(diǎn)。轉(zhuǎn)移可能會(huì)耗時(shí)幾秒鐘,在這期間應(yīng)用應(yīng)該捕獲相應(yīng)的Exception并執(zhí)行重試操作。重試應(yīng)該有backoff機(jī)制,例如,分別在1s,2s,4s,8s等時(shí)候進(jìn)行重試。
避免使用太長(zhǎng)的字段名
MongoDB 沒(méi)有表結(jié)構(gòu)定義。每個(gè)文檔的結(jié)構(gòu)由每個(gè)文檔內(nèi)部的字段決定。所有字段名會(huì)在每個(gè)文檔內(nèi)重復(fù)。使用太長(zhǎng)的字段名字會(huì)導(dǎo)致對(duì)內(nèi)存、網(wǎng)絡(luò)帶寬更多的需求。(由于壓縮技術(shù),長(zhǎng)字段名對(duì)硬盤上的存儲(chǔ)不會(huì)有太多占用)
使用有規(guī)律的命名方式
如: School, Course, StudentRecord
或者:school, course, stuent_record
正確使用更新語(yǔ)句
不要把MongoDB和普通的鍵值型數(shù)據(jù)庫(kù)(KV)視為等同。MongoDB支持和關(guān)系型數(shù)據(jù)庫(kù)update語(yǔ)句類似的in place update。你只需要在update語(yǔ)句中指定需要更新的字段,而不是整個(gè)文檔對(duì)象。
舉例來(lái)說(shuō),加入我想把用戶的名字從TJ改為Tang Jianfa.
不建議的做法:
? ?user = db.users.findOne({_id: 101});user.name="Tang Jianfa"db.users.save(user);建議的做法:
? ?user = db.users.findOne({_id: 101}); ? ? ? ?// do certain thingsdb.users.update({_id:101}, {$set: {name: "Tang Jianfa"}});使用投射 (projection)來(lái)減少返回的內(nèi)容
MongoDB 支持類似于SQL語(yǔ)句里面的select,可以對(duì)返回的字段進(jìn)行過(guò)濾。使用Projection可以減少返回的內(nèi)容,降低網(wǎng)絡(luò)傳輸?shù)牧亢痛a中轉(zhuǎn)化成對(duì)象所需的時(shí)間。
使用TTL來(lái)自動(dòng)刪除過(guò)期的數(shù)據(jù)
很多時(shí)候我們用MongoDB來(lái)存儲(chǔ)一些時(shí)效性的數(shù)據(jù),如7天的監(jiān)控?cái)?shù)據(jù)。與其自己寫個(gè)后臺(tái)腳本定期清理過(guò)期數(shù)據(jù),你可以使用TTL索引來(lái)讓MongoDB自動(dòng)刪除過(guò)期數(shù)據(jù):
db.data.ensureIndex({create_time:1}, {expireAfterSeconds: 7*24*3600})使用execute命令來(lái)實(shí)現(xiàn)upsert
有些時(shí)候你不知道一條文檔數(shù)據(jù)是否已經(jīng)在庫(kù)里存在。這個(gè)時(shí)候你要么先查詢一下,要么就是使用upsert語(yǔ)句。在SpringData下面upsert語(yǔ)句需要你把每個(gè)字段的值都在upsert語(yǔ)句中格式化出來(lái)。字段多的時(shí)候未免有些繁瑣。SpringData MongoDB里面的MongoTemplate有個(gè)execute方法可以用來(lái)實(shí)現(xiàn)一個(gè)DB調(diào)用,也不用繁瑣的把所有字段羅列出來(lái)的例子。
? ?public boolean persistEmployee(Employee employee) throws Exception {BasicDBObject dbObject = new BasicDBObject();mongoTemplate.getConverter().write(employee, dbObject);mongoTemplate.execute(Employee.class, new CollectionCallback<Object>() {public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException {collection.update(new Query(Criteria.where("name").is(employee.getName())).getQueryObject(),dbObject,true, ?// means upsert - truefalse ?// multi update – false);return null;}});return true;}刪除SpringData MongoDB下面的_class 字段
SpringData MongoDB默認(rèn)會(huì)在MongoDB文檔中添加一個(gè)_class字段,里面保存的是fully qualified class name, 如”com.mongodb.examples.Customer”。對(duì)于有些小文檔來(lái)說(shuō),這個(gè)字段可能會(huì)占據(jù)不小一部分的存儲(chǔ)空間。如果你不希望SpringData 自動(dòng)加入這個(gè)字段,你可以:
1) 自定義MongoTypeMapper
@Bean public MongoTemplate mongoTemplate() throws UnknownHostException {MappingMongoConverter mappingMongoConverter = ?new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory()), newMongoMappingContext());mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));return new MongoTemplate(mongoDbFactory(), mappingMongoConverter ); }2) 在使用find語(yǔ)句時(shí),顯式地指定類的名字/類型:
? ?MongoTemplate.find(new Query(), Inventory.class))?
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/10173520.html
總結(jié)
以上是生活随笔為你收集整理的你应该知道的 MongoDB 最佳实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: spring-session-data-
- 下一篇: RxJava在闲鱼系统吞吐量提升上的实践