戰(zhàn)進(jìn)階:從文檔模型設(shè)計(jì)到集群調(diào)優(yōu)的完整指南)
1. 項(xiàng)目概述從“操作”到“駕馭”的轉(zhuǎn)變“操作MongoDB數(shù)據(jù)庫(kù)”這個(gè)標(biāo)題聽(tīng)起來(lái)像是一份簡(jiǎn)單的說(shuō)明書(shū)但真正干過(guò)這行的朋友都知道這背后遠(yuǎn)不止幾個(gè)CRUD命令那么簡(jiǎn)單。它意味著你要從一個(gè)數(shù)據(jù)庫(kù)的使用者轉(zhuǎn)變?yōu)橐粋€(gè)能真正理解其特性、駕馭其性能、并能在復(fù)雜業(yè)務(wù)場(chǎng)景下做出合理選擇的架構(gòu)參與者。我接觸MongoDB快十年了從早期的2.x版本一路跟到現(xiàn)在的7.x親眼看著它從一個(gè)“非主流”的文檔數(shù)據(jù)庫(kù)成長(zhǎng)為如今支撐海量互聯(lián)網(wǎng)應(yīng)用的核心基礎(chǔ)設(shè)施之一。今天我就以一個(gè)過(guò)來(lái)人的身份和你聊聊“操作”MongoDB這件事它絕不僅僅是安裝、連接、增刪改查更是一套關(guān)于數(shù)據(jù)建模、性能調(diào)優(yōu)和運(yùn)維保障的完整方法論。為什么是MongoDB在關(guān)系型數(shù)據(jù)庫(kù)一統(tǒng)天下的年代MongoDB的出現(xiàn)解決了一個(gè)核心痛點(diǎn)靈活應(yīng)對(duì)快速變化的需求。當(dāng)你的產(chǎn)品經(jīng)理天天改需求表結(jié)構(gòu)恨不得一周變?nèi)螘r(shí)你就會(huì)懷念文檔模型那種“塞進(jìn)去就行”的灑脫。但這份灑脫背后也藏著不少“坑”如何設(shè)計(jì)文檔結(jié)構(gòu)才能高效查詢(xún)?nèi)绾伪WC分布式集群的數(shù)據(jù)一致性索引到底該怎么建這些問(wèn)題都是“操作”二字背后需要深挖的細(xì)節(jié)。本文的目標(biāo)就是幫你越過(guò)簡(jiǎn)單的命令操作層深入到設(shè)計(jì)、優(yōu)化和運(yùn)維的層面讓你不僅能“用”MongoDB更能“用好”它。無(wú)論你是正在評(píng)估技術(shù)選型的架構(gòu)師還是每天需要和MongoDB打交道的開(kāi)發(fā)工程師或是負(fù)責(zé)保障數(shù)據(jù)庫(kù)穩(wěn)定的運(yùn)維同學(xué)這里面的經(jīng)驗(yàn)教訓(xùn)或許都能給你一些啟發(fā)。2. 核心設(shè)計(jì)理解文檔模型的優(yōu)勢(shì)與代價(jià)2.1 文檔模型 vs. 關(guān)系模型思維模式的根本轉(zhuǎn)換上手MongoDB第一道坎往往不是語(yǔ)法而是思維模式的轉(zhuǎn)換。我們習(xí)慣了關(guān)系型數(shù)據(jù)庫(kù)那種規(guī)整的、通過(guò)外鍵關(guān)聯(lián)的二維表格世界。而MongoDB的文檔模型鼓勵(lì)你將關(guān)聯(lián)緊密的數(shù)據(jù)嵌套在同一個(gè)文檔中。這不僅僅是技術(shù)上的差異更是業(yè)務(wù)建模思路的不同。舉個(gè)例子一個(gè)博客系統(tǒng)。在關(guān)系型數(shù)據(jù)庫(kù)里你很可能有users、posts、comments三張表通過(guò)user_id、post_id這些外鍵關(guān)聯(lián)。查詢(xún)一篇帖子及其作者、評(píng)論需要做多表連接JOIN。而在MongoDB里一種常見(jiàn)的建模方式是將評(píng)論作為子文檔直接嵌入到帖子文檔中甚至可以把作者的關(guān)鍵信息如用戶(hù)名、頭像也冗余存儲(chǔ)在這篇帖子文檔里。// MongoDB 中文檔結(jié)構(gòu)示例 { “_id”: ObjectId(“507f1f77bcf86cd799439011”), “title”: “我的第一篇博客” “content”: “...”, “author”: { “id”: 123, “name”: “張三” “avatar”: “url_to_avatar” }, “comments”: [ { “user”: “李四” “text”: “好文” “created_at”: ISODate(“...”) }, { “user”: “王五” “text”: “學(xué)習(xí)了” “created_at”: ISODate(“...”) } ], “tags”: [“技術(shù)” “MongoDB”], “created_at”: ISODate(“...”) }這種模型的優(yōu)勢(shì)非常明顯讀取性能極高。一次查詢(xún)就能拿到帖子、作者、評(píng)論所有信息沒(méi)有連接操作。對(duì)于博客詳情頁(yè)這種場(chǎng)景性能提升是立竿見(jiàn)影的。同時(shí)模式靈活增加一個(gè)view_count字段或者修改comments的結(jié)構(gòu)對(duì)已有數(shù)據(jù)完全沒(méi)有影響。但是代價(jià)是什么呢首先是數(shù)據(jù)冗余。作者信息在每篇他寫(xiě)的帖子中都被存儲(chǔ)了一遍如果作者改名了你需要更新他所有的帖子文檔這很麻煩。其次是文檔大小限制。MongoDB單個(gè)文檔不能超過(guò)16MB。如果一個(gè)帖子的評(píng)論有幾十萬(wàn)條這種嵌入模型就不可行了。最后是事務(wù)支持。在早期版本中MongoDB對(duì)多文檔事務(wù)的支持很弱雖然現(xiàn)在已大大加強(qiáng)但在設(shè)計(jì)時(shí)仍需謹(jǐn)慎考慮跨文檔的數(shù)據(jù)一致性。實(shí)操心得不要走極端。完全嵌入或完全引用像關(guān)系數(shù)據(jù)庫(kù)一樣都不是最佳實(shí)踐。我的經(jīng)驗(yàn)法則是對(duì)于“一對(duì)少”且子數(shù)據(jù)總是隨父數(shù)據(jù)一同被訪(fǎng)問(wèn)的關(guān)系如訂單和訂單項(xiàng)優(yōu)先考慮嵌入對(duì)于“一對(duì)多”或“多對(duì)多”如用戶(hù)和帖子或者子數(shù)據(jù)獨(dú)立訪(fǎng)問(wèn)頻繁的情況使用引用存儲(chǔ)_id。同時(shí)可以適當(dāng)進(jìn)行反規(guī)范化將高頻查詢(xún)需要的、不常變的字段冗余存儲(chǔ)用空間換時(shí)間。2.2_id字段的智慧不僅僅是主鍵每個(gè)MongoDB文檔都有一個(gè)_id字段作為主鍵。如果你不提供MongoDB會(huì)自動(dòng)生成一個(gè)ObjectId。這個(gè)ObjectId可不是隨便的隨機(jī)數(shù)它包含了時(shí)間戳、機(jī)器標(biāo)識(shí)、進(jìn)程ID和自增計(jì)數(shù)器這帶來(lái)了一個(gè)隱藏福利文檔大致按插入時(shí)間排序。基于_id的范圍查詢(xún)很多時(shí)候就等價(jià)于按時(shí)間范圍查詢(xún)效率很高。但自動(dòng)生成的ObjectId對(duì)人類(lèi)不友好。在很多業(yè)務(wù)場(chǎng)景下我們更希望使用有業(yè)務(wù)意義的ID比如用戶(hù)ID、訂單號(hào)。這時(shí)你可以用業(yè)務(wù)字段作為_(kāi)id。但務(wù)必注意_id必須是唯一且不可變的。一旦設(shè)置就不能修改。我曾見(jiàn)過(guò)有團(tuán)隊(duì)用郵箱作為_(kāi)id后來(lái)用戶(hù)要改郵箱直接傻眼。另一個(gè)高級(jí)技巧是利用_id的復(fù)合值。雖然_id通常是一個(gè)值但它其實(shí)可以是一個(gè)文檔。例如對(duì)于一個(gè)全球性的應(yīng)用你可以設(shè)計(jì)_id為{ shard_key: “asia”, auto_increment: 123456 }這樣既能包含分片鍵又能保證全局唯一。但這會(huì)顯著增加索引大小和復(fù)雜度需權(quán)衡利弊。2.3 索引策略速度與成本的平衡藝術(shù)沒(méi)有索引的數(shù)據(jù)庫(kù)查詢(xún)就像在圖書(shū)館里找一本沒(méi)編號(hào)的書(shū)——只能全館掃描。MongoDB的索引原理和關(guān)系數(shù)據(jù)庫(kù)類(lèi)似B樹(shù)但玩法更多樣。單字段索引是最基礎(chǔ)的。在哪個(gè)字段上建索引遵循一個(gè)原則為查詢(xún)條件filter、排序sort和覆蓋查詢(xún)projection中頻繁使用的字段建立索引。通過(guò)explain()命令可以分析查詢(xún)執(zhí)行計(jì)劃看到是否使用了索引IXSCAN還是全表掃描COLLSCAN。復(fù)合索引是性能優(yōu)化的關(guān)鍵。順序至關(guān)重要MongoDB的復(fù)合索引遵循“最左前綴匹配”原則。如果你有一個(gè)查詢(xún)是db.collection.find({status: “active” category: “tech”}).sort({created_at: -1})那么最優(yōu)的復(fù)合索引應(yīng)該是{status: 1, category: 1, created_at: -1}。這個(gè)索引能完美支持等值過(guò)濾和排序。如果把順序搞錯(cuò)比如{created_at: -1, status: 1, category: 1}那么這個(gè)查詢(xún)就無(wú)法利用索引進(jìn)行排序可能導(dǎo)致內(nèi)存排序非常消耗資源。多鍵索引用于數(shù)組字段。如果你經(jīng)常根據(jù)標(biāo)簽tags數(shù)組查詢(xún)那么在tags上建立多鍵索引是有效的。但要注意一個(gè)文檔中數(shù)組元素過(guò)多會(huì)顯著增加索引大小。文本索引和地理空間索引是MongoDB的特色。全文搜索和“附近的人”這類(lèi)功能用專(zhuān)門(mén)的索引效率遠(yuǎn)超自己手動(dòng)實(shí)現(xiàn)。踩坑記錄索引不是越多越好。每個(gè)索引都會(huì)降低寫(xiě)操作插入、更新、刪除的速度因?yàn)閿?shù)據(jù)庫(kù)需要維護(hù)索引結(jié)構(gòu)。同時(shí)索引占用磁盤(pán)和內(nèi)存。我曾經(jīng)維護(hù)過(guò)一個(gè)集合有十幾個(gè)索引寫(xiě)操作慢如蝸牛。后來(lái)通過(guò)分析查詢(xún)模式合并和刪除了冗余索引寫(xiě)性能提升了數(shù)倍。定期使用db.collection.aggregate([{ $indexStats: {} }])查看索引使用情況干掉那些“僵尸索引”。3. 核心操作詳解超越基礎(chǔ)的增刪改查3.1 增刪改查的“高級(jí)玩法”基本的insertOnefindupdateOnedeleteOne大家都會(huì)。我們來(lái)看看那些容易忽略但極其重要的細(xì)節(jié)。插入批量插入 (insertMany) 的性能遠(yuǎn)高于循環(huán)插入單條文檔。在導(dǎo)入數(shù)據(jù)或批量處理時(shí)務(wù)必使用批量操作。同時(shí)注意writeConcern參數(shù)它決定了寫(xiě)操作需要多少個(gè)節(jié)點(diǎn)確認(rèn)才返回成功。對(duì)于日志類(lèi)不重要的數(shù)據(jù)可以設(shè)置{w: 0}無(wú)確認(rèn)以獲得最高吞吐對(duì)于核心訂單數(shù)據(jù)可能需要{w: “majority”}以保證數(shù)據(jù)安全。查詢(xún)find方法的第二個(gè)參數(shù)是投影projection用于指定返回哪些字段。務(wù)必只查詢(xún)需要的字段特別是要排除那些大的、不需要的字段如文章內(nèi)容、Base64圖片。這能減少網(wǎng)絡(luò)傳輸和客戶(hù)端內(nèi)存消耗。{ field: 1 }表示包含{ field: 0 }表示排除_id字段默認(rèn)總是返回除非顯式排除{ _id: 0 }。更新updateOne和updateMany的區(qū)別不言而喻。重點(diǎn)在于更新操作符$set設(shè)置字段值。最常用。$unset刪除字段。$inc原子性增加。用于計(jì)數(shù)器、庫(kù)存等場(chǎng)景完美避免并發(fā)沖突。$push/$addToSet向數(shù)組添加元素。$addToSet能避免重復(fù)。$pull從數(shù)組移除匹配元素。$rename重命名字段。更新選項(xiàng)upsert是一個(gè)神器。{ upsert: true }意味著“如果文檔存在則更新不存在則插入”。這在初始化配置、記錄首次訪(fǎng)問(wèn)等場(chǎng)景下非常方便無(wú)需先查詢(xún)判斷是否存在。刪除deleteOne刪除匹配的第一條deleteMany刪除所有匹配的。刪除操作不可逆生產(chǎn)環(huán)境執(zhí)行刪除前尤其是deleteMany強(qiáng)烈建議先執(zhí)行一個(gè)同條件的find操作確認(rèn)要?jiǎng)h除的數(shù)據(jù)范圍。對(duì)于重要數(shù)據(jù)更推薦使用“軟刪除”增加一個(gè)is_deleted字段通過(guò)更新將其置為true查詢(xún)時(shí)過(guò)濾掉已刪除的數(shù)據(jù)。3.2 聚合框架MongoDB的“數(shù)據(jù)分析引擎”如果說(shuō)find是瑞士軍刀那聚合管道Aggregation Pipeline就是一套完整的機(jī)床。它能完成復(fù)雜的數(shù)據(jù)轉(zhuǎn)換、分組、統(tǒng)計(jì)是進(jìn)行數(shù)據(jù)分析、生成報(bào)表的利器。一個(gè)聚合管道由多個(gè)階段stage組成文檔像流水線(xiàn)一樣依次通過(guò)各個(gè)階段。一個(gè)典型的例子統(tǒng)計(jì)每個(gè)分類(lèi)下?tīng)顟B(tài)為“已發(fā)布”的文章數(shù)量并按數(shù)量降序排列。db.articles.aggregate([ { $match: { status: “published” } }, // 階段1過(guò)濾數(shù)據(jù) { $group: { _id: “$category” // 按分類(lèi)分組 count: { $sum: 1 } // 對(duì)每組計(jì)數(shù) } }, { $sort: { count: -1 } }, // 階段3按計(jì)數(shù)排序 { $project: { category: “$_id” total: “$count” _id: 0 } } // 階段4重塑輸出文檔 ])常用階段解析$match過(guò)濾文檔相當(dāng)于find。盡可能早地使用$match以減少后續(xù)階段要處理的文檔數(shù)量。$group分組是聚合的核心。可以配合$sum$avg$max$min$push等累加器使用。$sort排序。如果數(shù)據(jù)量大在$sort前使用$match和$limit能極大提升性能。$project重塑文檔選擇、重命名、計(jì)算字段。$lookup實(shí)現(xiàn)左連接left outer join。這是解決跨集合關(guān)聯(lián)查詢(xún)的終極武器但性能開(kāi)銷(xiāo)較大需謹(jǐn)慎使用。$unwind將數(shù)組字段拆分成多條文檔。常用于分析數(shù)組內(nèi)容。性能提示聚合管道可以非常復(fù)雜也可能非常慢。使用explain()功能分析管道執(zhí)行計(jì)劃。為$match和$sort階段用到的字段建立索引能極大提升性能。另外MongoDB 4.2 支持了聚合管道的更新$merge可以將聚合結(jié)果直接寫(xiě)入另一個(gè)集合非常適合做物化視圖。3.3 事務(wù)在多文檔操作中保證ACID在MongoDB 4.0之前多文檔事務(wù)是不支持的。現(xiàn)在對(duì)于副本集和分片集群都提供了多文檔事務(wù)支持語(yǔ)法上類(lèi)似于傳統(tǒng)數(shù)據(jù)庫(kù)。const session db.getMongo().startSession(); session.startTransaction(); try { const usersColl session.getDatabase(‘mydb’).users; const ordersColl session.getDatabase(‘mydb’).orders; usersColl.updateOne({ _id: userId }, { $inc: { balance: -100 } }); ordersColl.insertOne({ userId: userId, amount: 100, status: ‘paid’ }); session.commitTransaction(); } catch (error) { session.abortTransaction(); throw error; } finally { session.endSession(); }但是事務(wù)不是銀彈。MongoDB的事務(wù)有性能開(kāi)銷(xiāo)并且默認(rèn)超時(shí)時(shí)間較短60秒。濫用事務(wù)會(huì)導(dǎo)致嚴(yán)重的性能問(wèn)題。設(shè)計(jì)時(shí)應(yīng)優(yōu)先考慮通過(guò)優(yōu)化數(shù)據(jù)模型如嵌入式文檔來(lái)避免跨文檔事務(wù)。只有在確實(shí)無(wú)法避免如銀行轉(zhuǎn)賬時(shí)才使用事務(wù)。同時(shí)確保事務(wù)內(nèi)操作涉及的文檔都有合適的索引以縮短事務(wù)執(zhí)行時(shí)間。4. 性能調(diào)優(yōu)與運(yùn)維實(shí)戰(zhàn)4.1 連接管理與連接池很多性能問(wèn)題根子出在連接上。你的應(yīng)用不應(yīng)該為每次數(shù)據(jù)庫(kù)操作都創(chuàng)建和關(guān)閉連接這會(huì)產(chǎn)生巨大的開(kāi)銷(xiāo)。必須使用連接池。幾乎所有MongoDB驅(qū)動(dòng)如Node.js的mongoose Python的pymongo都內(nèi)置了連接池。關(guān)鍵配置參數(shù)最大連接數(shù) (maxPoolSize)默認(rèn)通常是100。這不是越大越好。每個(gè)連接都會(huì)消耗服務(wù)端和客戶(hù)端的內(nèi)存。你需要根據(jù)應(yīng)用服務(wù)器如Nginx、應(yīng)用Pod的數(shù)量和負(fù)載來(lái)估算。一個(gè)經(jīng)驗(yàn)公式(應(yīng)用實(shí)例數(shù) * maxPoolSize) MongoDB服務(wù)端最大可用連接數(shù)。服務(wù)端的最大連接數(shù)由net.maxIncomingConnections參數(shù)控制默認(rèn)取決于內(nèi)存。最小連接數(shù) (minPoolSize)維持一個(gè)常備連接池避免突發(fā)請(qǐng)求時(shí)創(chuàng)建連接的開(kāi)銷(xiāo)。連接超時(shí)和Socket超時(shí)設(shè)置合理的超時(shí)時(shí)間避免網(wǎng)絡(luò)閃斷導(dǎo)致線(xiàn)程長(zhǎng)時(shí)間掛起。運(yùn)維經(jīng)驗(yàn)監(jiān)控MongoDB實(shí)例的當(dāng)前連接數(shù)db.serverStatus().connections。如果連接數(shù)持續(xù)接近上限應(yīng)用會(huì)出現(xiàn)獲取連接超時(shí)的錯(cuò)誤。此時(shí)需要分析是maxPoolSize設(shè)置過(guò)小還是存在連接泄漏比如操作完成后沒(méi)有正確釋放連接回池。在應(yīng)用重啟或發(fā)布時(shí)連接池的建立也會(huì)產(chǎn)生一個(gè)小高峰。4.2 監(jiān)控與慢查詢(xún)分析“我的數(shù)據(jù)庫(kù)怎么突然慢了” 沒(méi)有監(jiān)控這個(gè)問(wèn)題就無(wú)法回答。基礎(chǔ)監(jiān)控關(guān)注以下幾個(gè)核心指標(biāo)操作計(jì)數(shù)器db.serverStatus().opcounters查看增刪改查等操作的速率。突然的激增可能意味著被攻擊或程序BUG。隊(duì)列長(zhǎng)度db.serverStatus().globalLock.currentQueue查看讀寫(xiě)操作排隊(duì)情況。隊(duì)列長(zhǎng)表示數(shù)據(jù)庫(kù)正在滿(mǎn)負(fù)荷運(yùn)轉(zhuǎn)。內(nèi)存使用db.serverStatus().mem。MongoDB會(huì)盡可能利用內(nèi)存緩存數(shù)據(jù)和索引。確保resident常駐內(nèi)存接近或等于virtual虛擬內(nèi)存且mapped映射內(nèi)存遠(yuǎn)小于物理內(nèi)存總量。如果頻繁發(fā)生缺頁(yè)錯(cuò)誤說(shuō)明內(nèi)存不足。磁盤(pán)IO使用iostat等系統(tǒng)命令。高磁盤(pán)IO等待是性能殺手通常意味著索引沒(méi)命中或內(nèi)存不足。慢查詢(xún)?nèi)罩具@是定位性能問(wèn)題的金鑰匙。在mongod配置文件中設(shè)置operationProfiling: mode: slowOp slowOpThresholdMs: 100 # 定義慢查詢(xún)閾值單位毫秒 rateLimit: 100 # 采樣率開(kāi)啟后所有執(zhí)行時(shí)間超過(guò)slowOpThresholdMs的操作都會(huì)被記錄到日志或system.profile集合中。通過(guò)分析這些慢查詢(xún)你可以找到需要優(yōu)化的查詢(xún)語(yǔ)句和缺失的索引。使用explain()對(duì)于特定的查詢(xún)直接在Shell中執(zhí)行db.collection.find(...).explain(“executionStats”)。重點(diǎn)關(guān)注executionStats.executionTimeMillis查詢(xún)執(zhí)行時(shí)間。executionStats.totalDocsExamined掃描的文檔數(shù)。理想情況下這個(gè)數(shù)應(yīng)該等于nReturned返回的文檔數(shù)。如果遠(yuǎn)大于說(shuō)明索引效率低或沒(méi)走索引。executionStats.executionStages.stage執(zhí)行階段。看到COLLSCAN全表掃描就要警惕了。4.3 備份與恢復(fù)策略數(shù)據(jù)無(wú)價(jià)。備份是最后一道防線(xiàn)。邏輯備份 (mongodump/mongorestore)導(dǎo)出為BSON/JSON格式。優(yōu)點(diǎn)是可讀性強(qiáng)可以單集合恢復(fù)版本兼容性好。缺點(diǎn)是速度慢對(duì)數(shù)據(jù)庫(kù)性能有影響全庫(kù)鎖或影響從節(jié)點(diǎn)不適合超大型數(shù)據(jù)庫(kù)。適用于日常小規(guī)模備份和遷移特定集合。物理備份文件系統(tǒng)快照在文件系統(tǒng)層面如LVM EBS快照對(duì)MongoDB的數(shù)據(jù)目錄進(jìn)行快照。優(yōu)點(diǎn)是速度快幾乎瞬時(shí)對(duì)業(yè)務(wù)影響極小。缺點(diǎn)是需要底層存儲(chǔ)支持恢復(fù)時(shí)需要整個(gè)實(shí)例或整個(gè)卷回滾靈活性差。這是生產(chǎn)環(huán)境首選的備份方式。副本集自身作為備份一個(gè)配置良好的副本集一主兩從本身提供了數(shù)據(jù)冗余。你可以將其中一個(gè)從節(jié)點(diǎn)設(shè)置為hidden節(jié)點(diǎn)專(zhuān)門(mén)用于備份任務(wù)這樣備份操作不會(huì)影響線(xiàn)上業(yè)務(wù)。甚至可以延遲這個(gè)從節(jié)點(diǎn)如延遲1小時(shí)用于應(yīng)對(duì)“誤操作刪除數(shù)據(jù)”這種場(chǎng)景。血淚教訓(xùn)備份一定要定期恢復(fù)測(cè)試我見(jiàn)過(guò)太多團(tuán)隊(duì)備份做得勤快但真到出事時(shí)發(fā)現(xiàn)備份文件是壞的或者恢復(fù)流程根本跑不通。至少每季度做一次恢復(fù)演練。備份策略遵循“3-2-1”原則至少3份副本用2種不同介質(zhì)存儲(chǔ)其中1份異地保存。5. 集群架構(gòu)副本集與分片5.1 副本集高可用與數(shù)據(jù)冗余的基石單點(diǎn)MongoDB實(shí)例只能用于開(kāi)發(fā)測(cè)試。生產(chǎn)環(huán)境必須使用副本集Replica Set。一個(gè)副本集由多個(gè)節(jié)點(diǎn)組成其中一個(gè)為主節(jié)點(diǎn)Primary負(fù)責(zé)所有寫(xiě)操作和默認(rèn)的讀操作其余為從節(jié)點(diǎn)Secondary異步復(fù)制主節(jié)點(diǎn)的數(shù)據(jù)可以提供讀操作需要配置讀偏好readPreference。選舉與故障轉(zhuǎn)移當(dāng)主節(jié)點(diǎn)宕機(jī)或失聯(lián)時(shí)剩余的從節(jié)點(diǎn)會(huì)發(fā)起一次選舉投票選出新的主節(jié)點(diǎn)。這個(gè)過(guò)程通常是自動(dòng)的在幾秒到十幾秒內(nèi)完成期間集群不可寫(xiě)。確保你的應(yīng)用驅(qū)動(dòng)配置了重試機(jī)制以平滑度過(guò)故障轉(zhuǎn)移期。讀寫(xiě)分離通過(guò)設(shè)置readPreference可以將讀請(qǐng)求路由到從節(jié)點(diǎn)分擔(dān)主節(jié)點(diǎn)壓力。可選值有primary默認(rèn)只從主節(jié)點(diǎn)讀。primaryPreferred優(yōu)先從主節(jié)點(diǎn)讀不可用時(shí)從從節(jié)點(diǎn)讀。secondary只從從節(jié)點(diǎn)讀。secondaryPreferred優(yōu)先從從節(jié)點(diǎn)讀。nearest從網(wǎng)絡(luò)延遲最低的節(jié)點(diǎn)讀無(wú)論主從。注意從節(jié)點(diǎn)的數(shù)據(jù)是異步復(fù)制的存在延遲通常很小但在網(wǎng)絡(luò)或負(fù)載壓力下可能增大。因此對(duì)于需要強(qiáng)一致性的讀操作如讀剛寫(xiě)入的數(shù)據(jù)必須使用primary或primaryPreferred。對(duì)于可以接受最終一致性的讀如報(bào)表、時(shí)間線(xiàn)可以使用secondary。部署建議至少3個(gè)節(jié)點(diǎn)且分布在不同的物理機(jī)或可用區(qū)AZ上。奇數(shù)個(gè)節(jié)點(diǎn)有利于選舉投票避免平票。可以增加一個(gè)仲裁節(jié)點(diǎn)Arbiter它不存儲(chǔ)數(shù)據(jù)只參與投票成本低用于湊奇數(shù)。5.2 分片集群應(yīng)對(duì)海量數(shù)據(jù)的水平擴(kuò)展當(dāng)單個(gè)副本集無(wú)法承受數(shù)據(jù)量或讀寫(xiě)吞吐時(shí)就需要分片Sharding。分片集群將數(shù)據(jù)水平拆分分布到多個(gè)副本集稱(chēng)為分片上。核心概念分片鍵Shard Key選擇哪個(gè)或哪幾個(gè)字段作為數(shù)據(jù)分發(fā)的依據(jù)。這是分片設(shè)計(jì)中最重要、最不可逆的決策。一旦集合被分片分片鍵就幾乎不能更改。塊Chunk數(shù)據(jù)遷移的基本單位。每個(gè)分片包含多個(gè)塊。配置服務(wù)器Config Server存儲(chǔ)集群的元數(shù)據(jù)如數(shù)據(jù)分布信息。路由節(jié)點(diǎn)Mongos無(wú)狀態(tài)的路由進(jìn)程應(yīng)用連接它它根據(jù)分片鍵將請(qǐng)求轉(zhuǎn)發(fā)到正確的分片。分片鍵選擇策略哈希分片對(duì)分片鍵值計(jì)算哈希再按哈希值分布。優(yōu)點(diǎn)是數(shù)據(jù)分布非常均勻。缺點(diǎn)是范圍查詢(xún)效率低下因?yàn)橄噜彽臄?shù)據(jù)可能分布在任何分片上。適用于寫(xiě)負(fù)載極高、沒(méi)有范圍查詢(xún)需求的場(chǎng)景如日志、事件流。范圍分片按分片鍵值的自然范圍分布數(shù)據(jù)。優(yōu)點(diǎn)是支持高效的范圍查詢(xún)。缺點(diǎn)是容易導(dǎo)致數(shù)據(jù)分布不均數(shù)據(jù)熱點(diǎn)比如按時(shí)間戳分片最新的數(shù)據(jù)永遠(yuǎn)寫(xiě)到一個(gè)分片上。適用于有明顯范圍查詢(xún)需求的場(chǎng)景但需要精心設(shè)計(jì)分片鍵以避免熱點(diǎn)。如何選擇分片鍵一個(gè)好的分片鍵應(yīng)該具備基數(shù)高取值盡可能多分布均勻。寫(xiě)分布均勻避免所有新寫(xiě)入都集中到一個(gè)分片。匹配查詢(xún)模式你的大部分查詢(xún)都應(yīng)該包含分片鍵這樣查詢(xún)可以直接定位到單個(gè)分片定向查詢(xún)否則查詢(xún)會(huì)廣播到所有分片分散-聚集查詢(xún)性能很差。一個(gè)經(jīng)典的“組合拳”是使用復(fù)合分片鍵例如{ user_id: 1, _id: 1 }。user_id保證了與用戶(hù)相關(guān)的查詢(xún)能定向到特定分片_id作為后綴保證了在同一個(gè)用戶(hù)下的寫(xiě)入也能均勻分布。終極建議不要過(guò)早分片。分片帶來(lái)了巨大的運(yùn)維復(fù)雜性。優(yōu)先通過(guò)升級(jí)硬件更快的CPU、更大的內(nèi)存、SSD、優(yōu)化索引和查詢(xún)、使用副本集讀寫(xiě)分離來(lái)提升性能。只有當(dāng)數(shù)據(jù)量預(yù)計(jì)將遠(yuǎn)超單機(jī)容量或?qū)懲掏逻_(dá)到單機(jī)上限時(shí)再考慮分片。在決定分片前務(wù)必用真實(shí)數(shù)據(jù)和工作負(fù)載進(jìn)行充分的測(cè)試和模擬。