MongoDB 分片鍵的選擇與案例

pursuer.chen發表於2018-05-31

MongoDB版本:3.6 

一、分片鍵類別

1.升序片鍵

升序片鍵例如:日期時間欄位、自增欄位。

2.隨機分發片鍵

隨機分發片鍵例如:使用者名稱、郵件名、UUID、MD5值或者是其它的一些沒有規律的值的列。

3.基於位置的片鍵

基於位置的片鍵例如:IP、經緯度、居住地址等。

二、分片策略

1.範圍分片

建立分片時,只在主分片上建立了一個塊{ "username" : { "$minKey" : 1 } } -->> { "username" : { "$maxKey" : 1 } } on : rs-a Timestamp(1, 0)。

 

至少得3個不同的值才會進行塊切分,相同的值只會在一個分片塊中。比如對一個name欄位進行範圍分割槽,如果一直往name欄位插入"a",那麼它會一直儲存主分片的{ "username" : { "$minKey" : 1 } } -->> { "username" : { "$maxKey" : 1 } }中,直到name出現三個不同的值,比如“a”,“b”,“c”這個時候就會進行分片。當然這只是測試,現實中不會對這種粗粒度的欄位單獨做分片。

2.hashed分片

建立分片時,預設在每個分片上建立了兩個資料塊。但是當前每個塊上面是沒有資料的。

 

3.組合分片

組合分片是比較好的一種分片的選擇,好的組合分片可以同時解決熱點和隨機讀IO問題。例如:

sh.shardCollection("test.bbbb",{"username":1,"_id":1});

4.標籤分片

比如對於一些日誌非查詢文件,可以通過標籤將其只插入到某個分片中。例如

sh.addTagRange("test.log",{ "_id" : { "$minKey" : 1 }  }, { "_id" : { "$maxKey" : 1 } },"tag_rs-a");

可以在config庫中的tag文件中檢視設定的標籤資訊。

use config

db.tags.find();

三、標籤

可以通過標籤將特定範圍的資料在指定的分片中。

 

將{ "_id" : 18000 } -->> { "_id" : 26000 }範圍的資料儲存到rs-a的分片上,這部分資料跨越了兩個資料塊。

1.為分片指定tag

sh.addShardTag("rs-a","tag_rs-a");

sh.addShardTag("rs-b","tag_rs-b");

sh.addShardTag("rs-c","tag_rs-c");

2.建立規則

sh.addTagRange("test.person",{ "_id" : 18000 }, { "_id" : 26000 },"tag_rs-a");

資料{ "_id" : 18000 } -->> { "_id" : 26000 }已經被移動到了rs-a分片上。

四、分片案例

分片策略沒有絕對的好壞,針對不同的業務場景選擇不同的分片策略。

1.分片情景

1.所有的分片讀寫都均勻。

2.資料訪問均勻,而不是隨機性的訪問;由於新資料都是先在記憶體中建立,儘量避免需要從磁碟訪問新資料。

3.儘量避免由於資料塊的資料移動導致資料從磁碟載入到記憶體中從而導致熱資料被清理出記憶體。

4.組合欄位分片可能會是理想的分片方案。

 

分片鍵公式:{coarseLocality:1,search:1}

coarseLocality:應該是一個大粒度的區域性欄位。比如MONTH月份升序欄位。

search:是一個經常用來查詢的欄位。

2.分片案例

案例1.使用日期欄位、自增欄位、時間戳分片的問題

有一個網站瀏覽記錄表,表中有一個createtime欄位用來記錄每天記錄的插入時間。

對於這類文件不太適合使用createtime欄位作為分片欄位,因為讀寫可能都會集中在最新的分片上。使用自增欄位也存在同樣的問題

案例2.大粒度欄位分片問題

有一個五大洲的使用者文件表,表中有一個continent欄位儲存使用者所在洲。

如果使用continent作為分片欄位會存在以下幾個問題:

1.分片的粒度太大了,會導致最後每一個分片的資料都非常的大而且沒有再分的可能。而且也有可能會導致磁碟空間不夠的情況。

2.可能會導致某個分片在某個時間點的訪問量遠遠大於其他分片。

案例3:使用月份和使用者名稱進行組合分片

有一個使用者操作記錄集合,業務需要查詢使用者最近一個月操作記錄。集合有month,userName鍵

使用{month:1,userName:1}分片情景如下:

month保證熱資料優於記憶體。

userName:保證資料的隨機性,避免集中過熱問題。

存在的問題:對於新文件由於很多月份還不存在,會導致新資料都是往最後一個分片上面插入資料,存在熱讀寫問題,最後通過均衡器對資料塊進行移動。

資料測試

sh.shardCollection("test.news",{"month":1,"username":1 });

----插入1月資料10萬記錄

for(var i=0;i<100000;i++){db.news.insert({"_id":i,"month":"1","username":Math.random(),"createdate":new Date()})}

 

----插入2月資料10萬記錄

for(var i=100000;i<200000;i++){ db.news.insert({"_id":i,"month":"2","username":Math.random(),"createdate":new Date()})}

新資料往一直往最末尾的分片(rs-a)上插,因為這個時候"month":2在最大的分片上。{ "month" : "1", "username" : 0.9258836896982892 } -->> { "month" : { "$maxKey" : 1 }, "username" : { "$maxKey" : 1 } } on : rs-a Timestamp(3, 1)

 

資料插入完之後均衡器將rs-c上的一個塊分給了rs-a

----插入全部月份資料。

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()})}
}

 

保證每個月的資料都均勻的分佈到不同的分片上,並且隨著時間的推移舊的資料可能就不會被使用也不會被移動。

注意:這個案例比較特殊,因為對於日誌集合比較舊的資料基本上是不會被查詢的,所以藉助了month鍵作為了分片鍵保證了熱資料優先儲存於記憶體,對於整張表都是熱資料比如登入使用者集合就不適合這種分片方式,hashed會更適合。

案例4:使用佇列

佇列不僅在容災中非常的有用,而且在常規的突發流量下也非常的有用。佇列可以吸收短時間內爆發的大量請求。也可以把佇列反過來用,即快取MongoDB返回的結果。

比如:RabbitMQ

案例5:使用使用者名稱和建立時間進行組合分片

使用者名稱:保證資料的隨機性,避免熱點問題

建立時間:保證單個資料塊過大問題

五、設計集合注意事項

1.集合的鍵數量應該是固定的,包括巢狀文件的數量都應該提前規劃好。

2.儘量都是做原子更新,而不是某個鍵的值受其它鍵值更新的影響。比如num1,num2,total如果num鍵的值是經常會被更新的那麼這種設計就不好,因為total也要對應跟著變,而mongodb本身計算能力就很弱。

 

 

 

 

 

 

備註:

    作者:pursuer.chen

    部落格:http://www.cnblogs.com/chenmh

本站點所有隨筆都是原創,歡迎大家轉載;但轉載時必須註明文章來源,且在文章開頭明顯處給明連結,否則保留追究責任的權利。

《歡迎交流討論》

 

相關文章