上一篇文章我們詳細的說明完分片的機制後,接下來我們就要來詳細的說明片鍵的選擇,片鍵的選擇關係到你的分片執行速度與效能,並且一但建立後,要再修改幾乎是不太可能的,所以請像選老婆一樣,用心的選~
在我們開始學習片鍵的選擇前,我們要先知道,什麼樣的片鍵是最好的,最理想的,但想也知道最好的東西是不存在的,但我們還是要知道,才能給我們的選擇給個基準。
整體來說完美的片鍵有下面特性
我們先來說說第一個特性,如果沒有該項特性會發生什麼事情,假設我們的cluster
有四個分片,我們當然是希望每個分片可以處理25%
的事情,但是假設我們做那些寫的操作時(ex.新增)全部都集中在其中一個分片,那你會發覺那個分片會越來越大~越來越大~ ,而且別忘了我們上章節說的chunk
分配,它是根據數量來進行分配,不是用大小,因此你的那個分片內的chunk
不會分配到其它分片,這樣也就失去你用分片的意義了。
而至於第二點特性,mongos
在處理搜尋請求時主要會分成下述兩種的處理方式。
client
。chunk
,向相對的分片發送搜尋請求。根據上面的說明,我們知道mongos
的讀操作過程,然後我們這時在回來思考,如果這時搜尋時都集中在一個分片上,會發生什麼事,首先搜尋時不包含片鍵這種類型影響不大,但另一種就會影響到,因為原本該分散的壓力,反而都集中在一個分片,運氣不好搜尋請求過多,就爆掉了。
而至於第三點,就只是浪費資源囉~
這邊我們大概來整理一下,根據以上三點大概可以拆分成幾個良好片鍵的特性。
mongodb
更容易的均衡各分片的資料量,不容易發生過大的分片,基數越大的走容易分割資料。基數是指系統將資料分成
chunk
的能力,例如性別
欄位就是個低基數的例子,只有男與女。
這世界沒有著麼美好的事情,基本上幾乎找不到完全符合上述的條件,所以相對的咱們只能選擇盡可能符合你需要的片鍵,而這時就只能根據你專案的需求來決定,例如說這專案是讀吃比較重還是寫吃比較重,比較最大的搜尋條件,或搜尋時間過久的搜尋,這時都是要考量的。
這邊開始我們就要來說明一些片鍵的種類。
這種類型的片鍵,大部份都是欄位為Date
類型或是ObjectId
,是種會根據時間來增加欄位,或是根據先後順序進來的欄位。
假如我們使用這種欄位做為片鍵,會發生什麼事情 ? 一開始建立片鍵時你不會看到什麼問題,而是再於你新增時會發生,假設我們有下面的分片cluster
。
shard001 | shard002 | shard003 |
---|---|---|
{min~2000} | {2007~2008} | {2014~2016} |
{2001~2003} | {2009~2010} | {2017~max} |
{2004~2006} | {2011~2013} |
然後這時我們要問個問題,我們進行新增時,它會加到那個chunk
?
答案是{2017~max}
,所以也就是說,接來下你所有的新增操作,都只是在這個分片中進行,並且該chunk
也會一直變大,然後在分拆、再分配,這樣會導致mongos
壓力大因為會一直分配,而且所有的新增操作壓力都會放在這個分片上,這不是什麼好事。
呃對了順到說一下ObjectId
為啥也是,因為它的組成有時間戳。
ObjectId = 時間戳 + 機器 + PID + 計數器
這種類型的片鍵,大概就是類似選擇使用者名、電子郵件等,或是資料沒有規律的欄位,由於資料新增時是隨機分配的,所以各分片增加的速度會差不多,這也代表這mongos
需要處理chunk
分配的壓力下降不少。
但這種類型的片鍵有個缺點,那就是在尋找大量資料的情況下,效率不高,因為資料都是分散的必定需要去每個分片尋找,而如果是集中式的片鍵(類似等等要說的片鍵),就指定針對該分片進行尋找就好囉。
這類型的片鍵,基本上就是根據用戶的IP、經緯度、或是地址之類,不過所謂的位置定義,事實上不能以實體上的位置來定義,有時後也可以用比較相關的資料方在一起,這也是種方法。
這種類型的片鍵優點就是讀與寫操作時比較有效率,因為可以很明確的選擇某個分片,但缺點就是有可能會有某些分片資料很多,某些分片資料很少的狀況,例如假設片鍵根據縣市來進行分片,然後存使用者資料,這時會發現新北市那個分片資料量爆多的,因為它人口最多。
Hashed
片鍵這種類型的片鍵讀與寫的操作能平均的分配,但缺點也是隨機分配片鍵的缺點,尋找大量資料時效率不高。
我們做個簡單的範例,假設我們有如下資料。
var objs = [];
for (var i=0;i<100000;i++){
objs.push({"name":"user"+i});
}
db.users.insert(objs);
然後我們指行下面指令來建立hash
索引,事實上我現在才知道有這種索引……。
db.users.ensureIndex({"name":"hashed"})
然後進行分片。
sh.shardCollection("test.users",{"name":"hashed"})
咱們來執行sh.status()
看看結果,它會將name
欄位的值進行hash
然後等出hash值
後在進行分片,其中這個NumberLong("-6854066565760758296")
就是hash值
,基本上都有唯一性,但是只一種情況下會有相同的值,那就數值型,例如1.9999
和1
這兩種hash值
會相等,因為1.9999
會取整數。
複合型片鍵
這樣類型的策略最適合用來處理需要大量搜尋範圍的應用,通常這種片鍵中的第一個值是比較低基數的鍵(最好是分片數的兩倍),而第二值就是我們上面介紹過的升序片鍵,假設我們有下列資料,state
是個基數小的欄位,大概只有六個分區,然後另一個就是升序欄位Date
,它是建立日期,它只會增加不會減,除非你自已偷改系統時間。
{ "state" : "AAA" , "date" : 20160101},
{ "state" : "BBB" , "date" : 20160102},
{ "state" : "CCC" , "date" : 20160103},
{ "state" : "DDD" , "date" : 20160104},
{ "state" : "EEE" , "date" : 20160105},
{ "state" : "AAA" , "date" : 20160106},
...
...
...
{ "state" : "AAA" , "date" : 20161231}
然後這時我們進行分片。
{ "state" : 1, "date" :1 }
它大置上會長成這樣,下面為其中一個chunk
的範例,該chunk
只會包含分區為AAA
與BBB
的並且它的日期是升序,也就是接下來新增的只會增加日期不會減少。
chunk1 | |
---|---|
範圍 | {"AAA" ~ "BBB"} |
document1 | { "state" : "AAA" , "date" : 20160101} |
document2 | { "state" : "BBB" , "date" : 20160102} |
document3 | { "state" : "AAA" , "date" : 20160110} |
... |
... |
documentN | { "state" : "AAA" , "date" : 20161231} |
其它的chunk
大至致上也長的差不多,這時我們簡單的用表格來看看我們的cluster
是什麼樣子,基本上最一開始chunk
數量不多。
shard001 | shard002 | shard003 |
---|---|---|
{ "AAA" ~ "BBB"} | { "CCC" ~ DDD" } | { "EEE" ~ "FFF" } |
然後這時資料開始隨著時間流動,開始慢慢的增加,而當一個chunk
裝不下時,就會開始拆開,這時我們的cluster
大概是降。
shard001 | shard002 | shard003 |
---|---|---|
{ "AAA" ~ "BBB"} ** |
{ "CCC" ~ DDD" } ** |
{ "EEE" ~ "FFF" } ** |
{ "AAA" ~ "BBB"} ** |
{ "CCC" ~ DDD" }** |
{ "EEE" ~ "FFF" } ** |
{ "AAA" ~ "BBB"} | { "EEE" ~ "FFF" } |
有沒有注意到我們上面有用**
符號,這代表這個chunk
不會在變動了,我們接下來的新增動作只會影響到最下面的chunk
。這有什麼好處,好處是寫入請求時,非常的有效率,它只會去新增至最下面的chunk
。
這種策略另一種好處是搜尋,如果我們這時要尋找AAA
的某段時間,它會處理的非常快,因為每一塊chunk
時間區間都是固定的,而且如果要找最新的一定是去找最下面的chunk
,因此如果某些需求是很常使用到搜尋某欄位(升序欄位)的範圍的很適和用這種策略。
本篇文章花了很多的文字在慢慢的說明片鍵的選擇,片鍵真的是影響到你的分片的好壞,一個好的片鍵帶你上天堂,壞的片鍵帶你下地獄,沒選好真的會很好,尤其是那種很老的系統一開始片鍵就選錯,然後資料量又超大,如果有人叫你想辦法調整效能,你大概會選擇幹掉提出問題的人,記好,片鍵是要根據你的應用需求來決定,這很重要。