MongoDB分片鍵的選擇和案例實(shí)例詳解
前言
分片鍵選擇不好,應(yīng)用程序就無(wú)法利用分片集群所提供的諸多優(yōu)勢(shì)。在這種情況下,插入和查詢的性能都會(huì)顯著下降。下決定時(shí)一定要嚴(yán)肅,一旦選擇了分片鍵,就必須堅(jiān)持選擇,分片鍵是不可以修改的。要讓分片鍵提供好的體驗(yàn),部分源自了解怎樣才算一個(gè)好的分片鍵。
本文將詳細(xì)介紹關(guān)于MongoDB分片鍵的選擇和案例,分享出來(lái)供大家參考學(xué)習(xí),下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧。
MongoDB版本:3.6
一、分片鍵類別
1.升序片鍵
升序片鍵例如:日期時(shí)間字段、自增字段。
2.隨機(jī)分發(fā)片鍵
隨機(jī)分發(fā)片鍵例如:用戶名、郵件名、UUID、MD5值或者是其它的一些沒有規(guī)律的值的列。
3.基于位置的片鍵
基于位置的片鍵例如:IP、經(jīng)緯度、居住地址等。
二、分片策略
1.范圍分片
創(chuàng)建分片時(shí),只在主分片上創(chuàng)建了一個(gè)塊{ "username" : { "$minKey" : 1 } } -->> { "username" : { "$maxKey" : 1 } } on : rs-a Timestamp(1, 0)
。
至少得3個(gè)不同的值才會(huì)進(jìn)行塊切分,相同的值只會(huì)在一個(gè)分片塊中。比如對(duì)一個(gè)name字段進(jìn)行范圍分區(qū),如果一直往name字段插入"a",那么它會(huì)一直存儲(chǔ)主分片的{ "username" : { "$minKey" : 1 } } -->> { "username" : { "$maxKey" : 1 } }
中,直到name出現(xiàn)三個(gè)不同的值,比如“a”,“b”,“c”這個(gè)時(shí)候就會(huì)進(jìn)行分片。當(dāng)然這只是測(cè)試,現(xiàn)實(shí)中不會(huì)對(duì)這種粗粒度的字段單獨(dú)做分片。
2.hashed分片
創(chuàng)建分片時(shí),默認(rèn)在每個(gè)分片上創(chuàng)建了兩個(gè)數(shù)據(jù)塊。但是當(dāng)前每個(gè)塊上面是沒有數(shù)據(jù)的。
3.組合分片
組合分片是比較好的一種分片的選擇,好的組合分片可以同時(shí)解決熱點(diǎn)和隨機(jī)讀IO問題。例如:
sh.shardCollection("test.bbbb",{"username":1,"_id":1});
4.標(biāo)簽分片
比如對(duì)于一些日志非查詢文檔,可以通過(guò)標(biāo)簽將其只插入到某個(gè)分片中。例如
sh.addTagRange("test.log",{ "_id" : { "$minKey" : 1 } }, { "_id" : { "$maxKey" : 1 } },"tag_rs-a");
可以在config庫(kù)中的tag文檔中查看設(shè)置的標(biāo)簽信息。
use config db.tags.find();
三、標(biāo)簽
可以通過(guò)標(biāo)簽將特定范圍的數(shù)據(jù)在指定的分片中。
將{ "_id" : 18000 } -->> { "_id" : 26000 }
范圍的數(shù)據(jù)保存到rs-a的分片上,這部分?jǐn)?shù)據(jù)跨越了兩個(gè)數(shù)據(jù)塊。
1.為分片指定tag
sh.addShardTag("rs-a","tag_rs-a"); sh.addShardTag("rs-b","tag_rs-b"); sh.addShardTag("rs-c","tag_rs-c");
2.創(chuàng)建規(guī)則
sh.addTagRange("test.person",{ "_id" : 18000 }, { "_id" : 26000 },"tag_rs-a");
數(shù)據(jù){ "_id" : 18000 } -->> { "_id" : 26000 }
已經(jīng)被移動(dòng)到了rs-a分片上。
四、分片案例
分片策略沒有絕對(duì)的好壞,針對(duì)不同的業(yè)務(wù)場(chǎng)景選擇不同的分片策略。
1.分片情景
1.所有的分片讀寫都均勻。
2.數(shù)據(jù)訪問均勻,而不是隨機(jī)性的訪問;由于新數(shù)據(jù)都是先在內(nèi)存中創(chuàng)建,盡量避免需要從磁盤訪問新數(shù)據(jù)。
3.盡量避免由于數(shù)據(jù)塊的數(shù)據(jù)移動(dòng)導(dǎo)致數(shù)據(jù)從磁盤加載到內(nèi)存中從而導(dǎo)致熱數(shù)據(jù)被清理出內(nèi)存。
4.組合字段分片可能會(huì)是理想的分片方案。
分片鍵公式: {coarseLocality:1,search:1}
coarseLocality:應(yīng)該是一個(gè)大粒度的局部字段。比如MONTH月份升序字段。
search:是一個(gè)經(jīng)常用來(lái)查找的字段。
2.分片案例
案例1.使用日期字段、自增字段、時(shí)間戳分片的問題
有一個(gè)網(wǎng)站瀏覽記錄表,表中有一個(gè)createtime字段用來(lái)記錄每天記錄的插入時(shí)間。
對(duì)于這類文檔不太適合使用createtime字段作為分片字段,因?yàn)樽x寫可能都會(huì)集中在最新的分片上。使用自增字段也存在同樣的問題
案例2.大粒度字段分片問題
有一個(gè)五大洲的用戶文檔表,表中有一個(gè)continent字段存儲(chǔ)用戶所在洲。
如果使用continent作為分片字段會(huì)存在以下幾個(gè)問題:
1.分片的粒度太大了,會(huì)導(dǎo)致最后每一個(gè)分片的數(shù)據(jù)都非常的大而且沒有再分的可能。而且也有可能會(huì)導(dǎo)致磁盤空間不夠的情況。
2.可能會(huì)導(dǎo)致某個(gè)分片在某個(gè)時(shí)間點(diǎn)的訪問量遠(yuǎn)遠(yuǎn)大于其他分片。
案例3:使用月份和用戶名進(jìn)行組合分片
有一個(gè)用戶操作記錄集合,業(yè)務(wù)需要查詢用戶最近一個(gè)月操作記錄。集合有month,userName鍵
使用{month:1,userName:1}
分片情景如下:
month保證熱數(shù)據(jù)優(yōu)于內(nèi)存。
userName:保證數(shù)據(jù)的隨機(jī)性,避免集中過(guò)熱問題。
存在的問題:對(duì)于新文檔由于很多月份還不存在,會(huì)導(dǎo)致新數(shù)據(jù)都是往最后一個(gè)分片上面插入數(shù)據(jù),存在熱讀寫問題,最后通過(guò)均衡器對(duì)數(shù)據(jù)塊進(jìn)行移動(dòng)。
數(shù)據(jù)測(cè)試
sh.shardCollection("test.news",{"month":1,"username":1 });
----插入1月數(shù)據(jù)10萬(wàn)記錄
for(var i=0;i<100000;i++){db.news.insert({"_id":i,"month":"1","username":Math.random(),"createdate":new Date()})}
----插入2月數(shù)據(jù)10萬(wàn)記錄
for(var i=100000;i<200000;i++){ db.news.insert({"_id":i,"month":"2","username":Math.random(),"createdate":new Date()})}
新數(shù)據(jù)往一直往最末尾的分片(rs-a)上插,因?yàn)檫@個(gè)時(shí)候"month":2在最大的分片上。 { "month" : "1", "username" : 0.9258836896982892 } -->> { "month" : { "$maxKey" : 1 }, "username" : { "$maxKey" : 1 } } on : rs-a Timestamp(3, 1)
數(shù)據(jù)插入完之后均衡器將rs-c上的一個(gè)塊分給了rs-a
----插入全部月份數(shù)據(jù)。
for(var a=1;a<13;a++) { for(var i=0;i<20000;i++){ db.news.insert({"month":a,"username":Math.random(),"createdate":new Date()})} }
保證每個(gè)月的數(shù)據(jù)都均勻的分布到不同的分片上,并且隨著時(shí)間的推移舊的數(shù)據(jù)可能就不會(huì)被使用也不會(huì)被移動(dòng)。
注意:這個(gè)案例比較特殊,因?yàn)閷?duì)于日志集合比較舊的數(shù)據(jù)基本上是不會(huì)被查詢的,所以借助了month鍵作為了分片鍵保證了熱數(shù)據(jù)優(yōu)先存儲(chǔ)于內(nèi)存,對(duì)于整張表都是熱數(shù)據(jù)比如登入用戶集合就不適合這種分片方式,hashed會(huì)更適合。
案例4:使用隊(duì)列
隊(duì)列不僅在容災(zāi)中非常的有用,而且在常規(guī)的突發(fā)流量下也非常的有用。隊(duì)列可以吸收短時(shí)間內(nèi)爆發(fā)的大量請(qǐng)求。也可以把隊(duì)列反過(guò)來(lái)用,即緩存MongoDB返回的結(jié)果。
比如:RabbitMQ
案例5:使用用戶名和創(chuàng)建時(shí)間進(jìn)行組合分片
用戶名:保證數(shù)據(jù)的隨機(jī)性,避免熱點(diǎn)問題
創(chuàng)建時(shí)間:保證單個(gè)數(shù)據(jù)塊過(guò)大問題
五、設(shè)計(jì)集合注意事項(xiàng)
1.集合的鍵數(shù)量應(yīng)該是固定的,包括嵌套文檔的數(shù)量都應(yīng)該提前規(guī)劃好。
2.盡量都是做原子更新,而不是某個(gè)鍵的值受其它鍵值更新的影響。比如num1,num2,total如果num鍵的值是經(jīng)常會(huì)被更新的那么這種設(shè)計(jì)就不好,因?yàn)閠otal也要對(duì)應(yīng)跟著變,而mongodb本身計(jì)算能力就很弱。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
mongodb 修改器($inc/$set/$unset/$push/$pop/upsert)
對(duì)于文檔的更新除替換外,針對(duì)某個(gè)或多個(gè)文檔只需要部分更新可使用原子的更新修改器,能夠高效的進(jìn)行文檔更新。更新修改器是中特殊的鍵2017-04-04Linux下MongoDB數(shù)據(jù)庫(kù)實(shí)現(xiàn)自動(dòng)備份詳解
這篇文章主要給大家介紹了在Linux系統(tǒng)下下MongoDB數(shù)據(jù)庫(kù)實(shí)現(xiàn)自動(dòng)備份的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來(lái)學(xué)習(xí)學(xué)習(xí)吧。2017-06-06詳解MongoDB中創(chuàng)建集合與刪除集合的操作方法
因?yàn)镸ongoDB屬于NoSQL,所以集合collection相當(dāng)于關(guān)系型數(shù)據(jù)庫(kù)中的表table,這里我們就來(lái)詳解MongoDB中創(chuàng)建集合與刪除集合的操作方法:2016-06-06MySQL和MongoDB設(shè)計(jì)實(shí)例對(duì)比分析
MySQL是關(guān)系型數(shù)據(jù)庫(kù)中的明星,MongoDB是文檔型數(shù)據(jù)庫(kù)中的翹楚。2011-07-07MongoDB固定集合(capped collection)的知識(shí)小結(jié)
固定集合指的是事先創(chuàng)建,并且大小固定的集合。下面這篇文章主要給大家總結(jié)介紹了MongoDB固定集合(capped collection)的知識(shí),文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10mongoDB數(shù)據(jù)庫(kù)索引快速入門指南
索引是一種特殊的數(shù)據(jù)結(jié)構(gòu),存儲(chǔ)設(shè)置在一個(gè)易于遍歷形式的數(shù)據(jù)的一小部分。索引存儲(chǔ)一個(gè)特定的字段或一組字段的值,在索引中指定的值的字段排列的,對(duì)mongoDB索引相關(guān)知識(shí)感興趣的朋友跟隨小編一起學(xué)習(xí)下吧2022-03-03Navicat Premium連接mongodb詳細(xì)教程
這篇文章主要介紹了Navicat Premium連接mongodb詳細(xì)教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03