一、分片叢集的基本架構
為什麼要使用分片叢集?
副本集遇到的問題:
副本集(ReplicaSet) 幫助我們解決讀請求擴充、高可用等問題。随着業務場景進一 步增長,可能會出現以下問題:
- 存儲容量超出單機磁盤容量
- 活躍資料集超出單機記憶體容量:很多讀請求需要從磁盤讀取
- 寫入量超出單機 IOPS 上限
垂直擴容(Scale Up) VS ⽔平擴容(Scale Out):
- 垂直擴容 : 用更好的伺服器,提高 CPU 處理核數、記憶體數、帶寬等
- 水準擴容 : 将任務配置設定到多台計算機上
什麼是 MongoDB 分⽚叢集:
- MongoDB 分片叢集(Sharded Cluster)是對資料進行水準擴充的一種方
- MongoDB 使用 分片叢集 來支援大資料集和高吞吐量的業務場景。
分⽚叢集的基本架構
- Mongos
-
- 分片叢集的通路入口
- 對請求進行路由、分發、合并
- 部署多個 Mongos 來保證高可用
- ConfigServer
-
- 存儲元資訊和叢集配置
- 部署為副本集來保證高可用
- Shard
-
- 存儲使用者資料,不同 Shard 儲存不同使用者資料
如何連結分片叢集
有了一個分片叢集以後,Drivers 需要通過連接配接 Mongos 來達到和整個叢集互動的目 的,而 Mongos 則會根據用戶端的請求來向後端不同的 Shard 進行請求的發起。 比如對 集合一進行讀寫,Mongos 會和 Shard A 和 Shard B 進行請求互動,如果讀寫集合二, 那麼 Mongos 指揮隻會和 Shard A 進行資料互動。
如下圖所示:在阿裡雲 Mongos 上申請的一個分片叢集,列舉了每個 Mongos 的連結 位址,并且拼接好了 ConnectionStringURI,如果使用單個 Mongos 進行連結,可能會 有單點的風險,是以推薦使用 ConnectionStringURI 來進行通路。
ConnectionStringURI 各個組成部分:
mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:por tN]]][/[database][?options]]
- Mongodb://:字首,代表這是一個 Connection String URI 連接配接位址。
- Username:password@:連接配接 MongoDB 執行個體的使用者名和密碼,使用英文冒号(:) 分隔。
- HostX:portX:執行個體的連接配接位址和端口号。
- /Database:鑒權資料庫名,即資料庫賬号所屬的資料庫。
- ?Options:指定額外的連接配接選項。
舉個例子:
“Example : mongodb://user:password@mongos1:3717,mongos2:3717/ad min”
使用者名為 User,密碼為 Password,然後來連接配接 Mongos1 和 Mongos2,它們的端 口都是 3717,全資料庫是 admin,這樣的一個 ConnectionStringURI。
Database 的主分⽚(Primary Shard)
Primary Shard 的定義:
預設情況下,每個 Database 的集合都是未分片的,存儲在一個固定的 Shard 上, 稱為 Primary Shard。
Primary Shard 的選擇:
當建立一個新的 Database 時,系統會根據各個 Shard 目前存儲的資料量,選擇一 個資料量最小的 Shard 作為新 Database 的 Primary Shard。
如何将集合進行分片
MongoDB 将資料進行分片支援集合級别,已經被分片的集合被切分成多份保 存在 Shard 上。
sh.enableSharding("")
- // eg: "record" Example : sh.enableSharding("records") sh.shardCollection(".", { : , ... } )
- : 分片鍵字段的名字
- : {1 | -1 |"hashed"} 。1 | -1 : 基于範圍分片鍵,"hashed" : 哈希
分片鍵
“Example : sh.shardCollection("records.people", { zipcode: 1 } )”對 recor ds.people 集合進行分片,這是一個基于 records 範圍分片。
二、Shard Key(分片鍵)
範圍分⽚ VS 哈希分⽚
-
範圍分⽚:根據 ShardKey 的值進⾏資料分⽚。
優點:很好的滿足範圍查詢的需求;
缺點:分片鍵單調寫入,無法擴充寫能力;
範圍分⽚⽀持多個字段的範圍分⽚:{x : 1} {x : 1 , y : 1}
如上圖所示:是一個基于 x 的範圍配置設定,資料被分為了 4 部分,切割點分别是 x:-75 ; x:25 ; x:175 值相近的資料是相鄰的,這種情況下,可以很好的滿足範圍查詢的需求。但 是如果是基于分片鍵的單調寫入,由于資料都會由于所有的寫入都會被最後一個 Chunk 來 承載,是以這樣就無法很好的擴充寫能力。
-
哈希分⽚:根據 ShardKey 計算 哈希值,基于哈希值進⾏資料分⽚。
優點:分片單調寫入,充分的擴充寫能力;
缺點:不能高效的進行範圍查詢。
如上圖所示: x:25 x:26 x:27,經過哈希計算後資料被打散不同的 Chunk 上,基 于哈希分片可以單調,對于分片鍵單調寫入的場景,可以充分的擴充寫能力,但是卻不能高 效的進行範圍查詢。
哈希分⽚僅⽀持單個字段的哈希分⽚:
{ x : "hashed" } {x : 1 , y : "hashed"} // 4.4 new
4.4 以後的版本,可以将單個字段的哈希分片和一個到多個的範圍分片鍵字段來進行組 合,比如下指定 x:1,y 是哈希的方式。
如何選擇合理的分⽚鍵
- Cardinality(基數):越⼤越好
-
- 以性别作為分片鍵 :資料最多被拆分為 2 份
- 以⽉份作為分片鍵 :資料最多被拆分為 12 份
- Frequency(頻率,⽂檔中出現某個值的頻率):越低越好 記錄全國人口的集合,以目前所在城市作為分片鍵:大多數資料集中在一線城市所在的 Chunk。
-
Monotonically Changing(單調變化):使⽤哈希分⽚
記錄日志集合,使用⽇志⽣成時間作為分片鍵:
如果使用範圍分片,資料寫入隻會在最後一個 Shard 上完成。
分片鍵(ShardKey)的限制
ShardKey 必須是一個索引。非空集合須在 ShardCollection 前建立索引;空集合 ShardCollection 自動建立索引
4.4 版本之前:
- ShardKey 大小不能超過 512 Bytes;
- 僅支援單字段的哈希分片鍵;
- Document 中必須包含 ShardKey;
- ShardKey 包含的 Field 不可以修改。
4.4 版本之後:
- ShardKey 大小無限制;
- 支援複合哈希分片鍵;
- Document 中可以不包含 ShardKey,插入時被當做 Null 處理;
- 為 ShardKey 添加字尾 refineCollectionShardKey 指令,可以修改 ShardKey 包含 的 Field;
而在 4.2 版本之前,ShardKey 對應的值不可以修改;4.2 版本之後,如果 ShardKey 為非_ID 字段,那麼可以修改 ShardKey 對應的值。
FefineCollectionShardKey
4.4 版本新增指令,通過分片鍵增加字尾字段的方式來修改分片鍵:
db.adminCommand( {
refineCollectionShardKey: "<database>.<collection>",
key: { <existing key specification>,
<suffix1>: <1|"hashed">, ... }
} )
這個例子中:
- : 目前的分片鍵,即新的分片鍵必須以目前分片鍵為 字首;
- : 新增的分片鍵字段;
- <1|"hashed"> : <1> -- 範圍分片鍵 ;<"hashed"> -- 哈希分片鍵。
FefineCollectionShardKey 的使用說明:
- 新的 ShardKey 對應的索引在FefineCollectionShardKey執⾏前須已經建立完成;
- FefineCollectionShardKey 隻會修改 Config 節點上的中繼資料,不會有任何資料遷 移,資料的打散随後續正常分裂&遷移⽽完成;
- 4.4 版本中⽀持了 ShardKey 缺失的情況(當做 Null 處理),為了應對并不是所有 ⽂檔都存在新的 ShardKey 的所有字段;
- 4.4 版本中⽀持複合哈希分⽚鍵,⽽在之前的版本中隻能⽀持單字段的哈希分⽚鍵。
特定⽬标的操作(Targeted Operations)vs⼴播的操作(Broadcast Operations)
Mongos 是如何基于請求當中的分片鍵資訊來做請求轉發,有兩種轉發行為,一種叫 做特定目标的操作,一種叫做廣播操作。
- 特定目标的操作(Targeted Operations):根據分⽚鍵計算出⽬标 Shard(s),發 起請求并傳回結果。
- 包含分片鍵的查詢操作、更新、删除操作、插入操作
如上圖所示:以 a 為 Shard Key 如果請求當中帶了 a 字段,那麼 Mongos 就可以識 别出來它的目标 Shard,如果是 Shard B,就可以直接跟 Shard B 進行互動,擷取結果 并傳回給用戶端。
- ⼴播的操作(Broadcast Operations):将請求發送給所有 Shard,合并查詢結果 并傳回給用戶端。
- 不包含分片鍵的查詢操作、_ID 字段的更新、删除操作
如圖所示:
三、Chunk & Balancer
什麼是 Chunk?
- MongoDB 基于 ShardKey 将 Collection 拆分成多個 資料子集,每個子集稱為一 個 Chunk;
- shardedCollection 的資料按照 ShardKey 劃分為 MinKey ~ MaxKey 的間;
- 每個 Chunk 有自己負責的一個區間(前閉後開);
- 存儲 ShardedCollection 的 Shard 上有該 Collection 的一個或多個 Chunk ;
如上圖所示:分片的集合是基于 x 的範圍分片,資料被分成了 4 個 Chunk, Chunk 1 : [minKey, -75) ; Chunk2 : [-75, 25) ; Chunk3 : [25, 175) ; Chunk4 : [175, maxKey)是個前閉後開的區間。ShardA 是持有 Chunk1 和 Chunk2,而 ShardB 和 ShardC 則分别持有 Chunk3 和 Chunk4。
Chunk 分裂(Chunk Splits)
-
Chunk 分裂的定義
伴随着資料的寫入,當 Chunk 增長到指定大小(預設為 64MB)時,MongoDB 會 對 Chunk 進行分裂,稱為 Chunk Split。
-
Chunk 分裂的⽅式
⼿動觸發:
-
- sh.splitAt(namespace, query)
- sh.splitFind(namespace, query)
⾃動觸發:隻有 插⼊和更新 操作才會觸發⾃動 Chunk Split。當 Chunk Size 被 調⼩時,不會⽴即發⽣Chunk Split。
- JumboChunk 一個最小的 Chunk 可以隻包含一個唯一的 ShardKey,這樣的 Chunk 不可以再進 行分裂,稱為 JumboChunk。
如下圖所示:
Chunk 分裂管理
Chunk 分裂管理包括:⼿動進⾏Chunk 分裂與調整 ChunkSize。
-
⼿動進⾏Chunk 分裂
場景舉例:業務需要向集合中插⼊⼤量的資料,⽽這些資料隻分布在較少的 Chunk 中。
直接插⼊⽆法利⽤多 Shard 并發寫⼊,并且插⼊後觸發 Chunk 分裂,進⽽觸發 Chunk 遷移,産⽣很多⽆效 IO。
sh.splitAt(namespace, query) : 指定 Chunk 分裂點,
例如:x: [0, 100) , sh.splitAt(ns, {x: 70}) 分裂後 x: [0, 70) , [70, 100)
sh.splitFind(namespace, query) : 從中間分裂⽬标 Chunk,
例如:x: [0, 100) , sh.splitFind(ns, {x: 70})分裂後 x: [0, 50) , [50, 100)
-
調整 ChunkSize
例如: use config; db.settings.save( { _id:"chunksize", value: } );
這裡調整 ChunkSize 的方式,是對 config 庫的 settings 集合,增加一條文檔,這個 文檔的 ID 是 ChunkSize。
說明:
- 隻有在 插⼊和更新 操作才會觸發對應Chunk分裂 -- 調ChunkSize會⽴即觸 發所有 Chunk 分裂為新的⼤⼩;
- ChunkSize 取值範圍 : 1 ~ 1024 MB;
- 調⼩ ChunkSize 可以讓 Chunk 更均衡的分布,但是 Chunk 遷移次數會增加;
- 調⼤ ChunkSize 會減少 Chunk 遷移,但會導緻 Chunk 分布不均。
Chunk 遷移(Chunk Migration)
-
Chunk 遷移的定義:
為了保證資料負載均衡,MongoDB 支援 Chunk 在 Shard 間遷移,稱為 Chunk Migration。
- Chunk 遷移的⽅式:
-
- 自動觸發:當 Chunk 在 Shard 之間分布不均時,Balancer 程序會自動觸發 Chunk 遷移;
- 手動觸發:sh.moveChunk(namespace, query, destination)
- Example : sh.moveChunk("records.people", { zipcode: "53187" }, "sha rd0019")。
- Chunk 遷移的影響:
-
- 影響 Shard 使用磁盤的大小;
- 增加 網絡帶寬 及 系統負載,這些會對系統性能造成影響。
- Chunk 遷移的限制:
-
- 每個 Shard 同一時間隻能有一個 Chunk 在進行遷移;
- 不會遷移 Chunk中文檔數量是平均Chunk文檔數1.3倍的Chunk // 4.4 提供 選項支援。
Balancer
- Balancer 是 MongoDB 的一個背景程序,用保證集合的 Chunk 在各個 Shard 上是 均衡的。
- Balancer 運作在 ConfigServer 的 Primary 節點。 預設為 開啟狀态。
- 當分片叢集中發生 Chunk 不均衡的情況時,Balancer 将觸發 Chunk 從 Chunk 數 量最多的 Shard 向 Chunk 數量最少的 Shard 上遷移。
如圖所示:Chunk 的數量小于 20,遷移門檻值是 2,随着 Chunk 數量增大,遷移門檻值 分别增長為 4 和 8。
AutoSplit & Balancer 管理指令
- 開啟 Chunk⾃動分裂: sh.enableAutoSplit()。
- 關閉 Chunk⾃動分裂: sh.disableAutoSplit()。
- 檢視 Balancer 是否開啟: sh.getBalancerState()。
- 檢視 Balancer 是否正在運⾏: sh.isBalancerRunning()。
- 開啟 Balancer: sh.startBalancer() / sh.setBalancerState(true)
- 4.2 版本開始,會同時開啟 AutoSplit。
- 關閉 Balancer: sh.stopBalancer() / / sh.setBalancerState(false)
- 4.2 版本開始,會同時關閉 AutoSplit。
- 開啟某個集合⾃動遷移: sh.enableBalancing(namespace)。
- 關閉某個集合⾃動遷移: sh.disableBalancing(namespace)。
- 修改 Balancer Window:
use config;
db.settings.update(
{ _id: "balancer" },
{ $set: { activeWindow : { start : "<start-time>", stop : "<stop-time>" } }
},
{ upsert: true }
); 。
JumboChunk
JumboChunk 的定義:一個最小的 Chunk 可以隻包含一個唯一的 ShardKey,這 樣的 Chunk 不可以再進行分裂。
JumboChunk 的産生:ShardKey 選擇不合理才會産生 JumboChunk。如果這些 JumboChunk 是高頻通路的,就會引起單 Shard 性能瓶頸。另外 Chunk 無法遷移,如 果再進行遷移,會引起 Shard 間資料不均。
随着 MongoDB 版本疊代,這些問題也在逐漸的被解決,比如 4.4 版本當中為我們提 供了 RefineCollectionShardKey 的指令,重新設定 ShardKey 同時 4.4 當中也給 Balancer 提供了一些設定,給 MoveChunk 提供了一些 Option,來支援 Chunk 遷移。
在 4.2 和 4.0 的較新的小版本當中,也提供了指令來清理叢集中的 JumboChunk 标
識。
四、叢集管理
指令回顧
-
- sh.setBalancerState(state)
- true : sh.startBalancer()
- false : sh.stopBalancer()
- sh.getBalancerState()
- sh.isBalancerRunning() sh.disableBalancing(namespace) sh.enableBalancing(namespace)
- Chunk
-
- sh.disableAutoSplit()
- sh.enableAutoSplit()
- sh.moveChunk( … )
- sh.splitAt( … )
- sh.splitFind( … )
- Sharding
-
- sh.shardCollection()
- sh.enableSharding()
叢集狀态檢視 - sh.status()
Sharding Version:分片叢集的版本資訊。
Shards:分片叢集中目前有 2 個 Shard,每個 Shard 的名稱、連結資訊以及目前狀
态。
Most Recently Active Mongoses:目前分片叢集中有2個4.2.1版本的Mongos。
Autosplit&balancer
- 目前開啟 Auto-Split
- Balancer 狀态為開啟
- Balancer 目前沒有正在運作
- 過去一段時間 Balancer 執行的成功、失敗資訊。
Records 庫
- Primary Shard : xxxx746b04
- 開啟 Sharding(EnableSharding)
- 相關版本資訊。
Records.people 集合
- Shard Key 為 { "Zipcode" : "Hashed" },無須 Unique 限制
- Balancer 可以針對該 集合 進行 Balance
- 集合有 4 個 Chunk,平均分布在 2 個 Shard 上
- 各 Chunk 所負責的範圍以及所屬的 Shard。
LogicalSession
3.6 版本開始,MongoDB driver 将所有的操作與 LogicalSession 關聯
3.4 版本及以前,如圖所示:
3.4 版本及以後,如圖所示:
- LogicalSession ID
{
// 唯一辨別。可以由用戶端生成,也可以由服務端生成
分片叢集使用及原理介紹 < 62
"id" : UUID("32415755-a156-4d1c-9b14-3c372a15abaf"),
// 目前登入使用者辨別
"uid" : BinData(0,"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSu
FU=")
}
- ⾃動清理機制
-
- 持久存儲: Config.System.Sessions。TTL 索引:預設 30 分鐘
- 預設每 5 分鐘一次同步,關閉已被清理的 Session,同時關閉 Session 上的 Cursor
- 使⽤⽅式
-
- use config; db.system.sessions.aggregate( [ { $listSessions: { allUse rs: true } } ] )
- db.runCommand( { killSessions: [ { id : }, ... ] } )
- startSession / refreshSessions / endSessions ...
快速掌握MongoDB核心技術幹貨目錄
電子書下載下傳:《玩轉MongoDB從入門到實戰》 | https://developer.aliyun.com/article/780915 |
走進 MongoDB | https://developer.aliyun.com/article/781079 |
MongoDB聚合架構 | https://developer.aliyun.com/article/781095 |
複制集使用及原理介紹 | https://developer.aliyun.com/article/781137 |
分片叢集使用及原理介紹 | https://developer.aliyun.com/article/781104 |
ChangeStreams 使用及原理 | https://developer.aliyun.com/article/781107 |
事務功能使用及原理介紹 | https://developer.aliyun.com/article/781111 |
MongoDB最佳實踐一 | https://developer.aliyun.com/article/781139 |
MongoDB最佳實踐二 | https://developer.aliyun.com/article/781141 |