在生產環境中,通常情況使用副本集就夠了(使用配置文件部署副本集可跳轉:5.x 副本集部署,使用命令行部署副本集可參考這篇文章)。除非容量非常大,併發訪問非常高,副本集已經無法正常提供服務時,才建議考慮使用分片。這一節內容就來聊聊 MongoDB 分片。
1 MongoDB 分片介紹
1.1 MongoDB 分片架構
MongoDB 分片架構圖如下:
各個組件的作用:
-
shard:存儲數據,爲了提高可用性和數據一致性,每個分片都是一個副本集。分片和分片之間的數據不重複。
-
Router(或者mongos):與客戶端相連,並將操作定向到適當的一個或多個分片。從 MongoDB 4.4 開始,mongos 開始支持 hedged reads 最大程度減少延遲。生產環境可配置多個 mongos 以實現高可用或者負載均衡。
-
config Server:存儲集羣的元數據。該數據包含集羣數據集到分片的映射。查詢路由器使用此元數據將操作定向到特定的分片。
1.2 分片鍵
分片鍵是集合中每個文檔中都存在的索引字段或索引複合字段,MongoDB將分片鍵值劃分爲多個塊,並將這些塊均勻地分佈在各個分片上。要將分片鍵值劃分爲多個塊,MongoDB使用基於範圍的分區或基於哈希的分區。有基於範圍的分片和基於哈希的分片。
從 MongoDB 4.2 開始,可以更新文檔的分片鍵值,除非分片鍵字段是不可變 id 字段。
1.3 平衡
平衡器是管理數據塊遷移的後臺進程。平衡器可以從羣集中的任何查詢路由器運行。
當分片集合在集羣中分佈不均勻時,平衡器進程會將塊從具有最多塊數的分片遷移到具有最小塊數的分片中,直到集羣平衡。
1.4 從集羣添加和刪除分片
將分片添加到集羣會導致不平衡,當 MongoDB立即開始將數據遷移到新地分片時,集羣平衡可能需要一段時間.
刪除分片時,平衡器將所有塊從一共分片遷移到其他分片。遷移所有數據並更新元數據後,可以安全地刪除分片。
2 MongoDB 分片集羣部署
2.1 架構介紹
這次實驗架構如下:
其中:
Hostname | IP |
node1 | 192.168.150.232 |
node2 | 192.168.150.253 |
node3 | 192.168.150.123 |
MongoDB 版本採用的是:5.0.3。
2.2 部署 config server
第一臺機器上:
mkdir /data/mongodb/config -p
mongod --configsvr --replSet config --dbpath /data/mongodb/config --bind_ip localhost,192.168.150.232 --logpath /data/mongodb/config/mongod.log --port 27020 --fork
第二臺機器上:
mkdir /data/mongodb/config -p
mongod --configsvr --replSet config --dbpath /data/mongodb/config --bind_ip localhost,192.168.150.253 --logpath /data/mongodb/config/mongod.log --port 27020 --fork
第三臺機器上:
mkdir /data/mongodb/config -p
mongod --configsvr --replSet config --dbpath /data/mongodb/config --bind_ip localhost,192.168.150.123 --logpath /data/mongodb/config/mongod.log --port 27020 --fork
連接到其中一臺:
mongosh --host 192.168.150.232 --port 27020
啓動副本集:
rs.initiate(
{
_id: "config",
configsvr: true,
members: [
{ _id : 0, host : "192.168.150.232:27020" },
{ _id : 1, host : "192.168.150.253:27020" },
{ _id : 2, host : "192.168.150.123:27020" }
]
}
)
查看副本集狀態:
rs.status()
注意以下信息:
......
members: [
{
_id: 0,
name: '192.168.150.232:27020',
health: 1,
state: 1,
stateStr: 'PRIMARY',
uptime: 53,
......
},
{
_id: 1,
name: '192.168.150.253:27020',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 17,
......
},
{
_id: 2,
name: '192.168.150.123:27020',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 17,
......
}
],
......
有 1 個 PRIMARY 和 2 個 SECONDARY,並且 health 的值都爲 1,說明集羣創建正常。
2.3 MongoDB 實例啓動
node1
mkdir /data/mongodb/shardtest01 -p
mkdir /data/mongodb/shardtest02 -p
mongod --shardsvr --replSet shardtest01 --dbpath /data/mongodb/shardtest01 --bind_ip localhost,192.168.150.232 --logpath /data/mongodb/shardtest01/mongod.log --port 27001 --fork
mongod --shardsvr --replSet shardtest02 --dbpath /data/mongodb/shardtest02 --bind_ip localhost,192.168.150.232 --logpath /data/mongodb/shardtest02/mongod.log --port 27002 --fork
node2
mkdir /data/mongodb/shardtest01 -p
mkdir /data/mongodb/shardtest02 -p
mongod --shardsvr --replSet shardtest01 --dbpath /data/mongodb/shardtest01 --bind_ip localhost,192.168.150.253 --logpath /data/mongodb/shardtest01/mongod.log --port 27001 --fork
mongod --shardsvr --replSet shardtest02 --dbpath /data/mongodb/shardtest02 --bind_ip localhost,192.168.150.253 --logpath /data/mongodb/shardtest02/mongod.log --port 27002 --fork
node3
mkdir /data/mongodb/shardtest01 -p
mkdir /data/mongodb/shardtest02 -p
mongod --shardsvr --replSet shardtest01 --dbpath /data/mongodb/shardtest01 --bind_ip localhost,192.168.150.123 --logpath /data/mongodb/shardtest01/mongod.log --port 27001 --fork
mongod --shardsvr --replSet shardtest02 --dbpath /data/mongodb/shardtest02 --bind_ip localhost,192.168.150.123 --logpath /data/mongodb/shardtest02/mongod.log --port 27002 --fork
2.4 創建分片副本集
創建第一個分片副本集
連接到其中一臺:
mongosh --host 192.168.150.232 --port 27001
啓動副本集:
rs.initiate(
{
_id: "shardtest01",
members: [
{ _id : 0, host : "192.168.150.232:27001" },
{ _id : 1, host : "192.168.150.253:27001" },
{ _id : 2, host : "192.168.150.123:27001" }
]
}
)
查看副本集:
rs.status()
判斷是否正常跟上面聊到的 config server 副本集判斷方法一樣。
創建第二個分片副本集
連接到其中一臺:
mongosh --host 192.168.150.232 --port 27002
啓動副本集:
rs.initiate(
{
_id: "shardtest02",
members: [
{ _id : 0, host : "192.168.150.232:27002" },
{ _id : 1, host : "192.168.150.253:27002" },
{ _id : 2, host : "192.168.150.123:27002" }
]
}
)
查看副本集:
rs.status()
判斷是否正常跟上面聊到的 config server 副本集判讀方法一樣。
2.5 啓動 mongos
在其中一臺機器上(這裏選擇的時:192.168.150.232)啓動 mongos,啓動 mongos 需要指定之前部署的 config 副本集。
mkdir /data/mongodb/mongos -p
mongos --configdb config/192.168.150.232:27020,192.168.150.232:27020,192.168.150.232:27020 --bind_ip localhost,192.168.150.232 --logpath /data/mongodb/mongos/mongos.log --fork
2.6 將分片副本集添加到集羣
連接到分片集羣:
mongosh --host 192.168.150.232 --port 27017
將分片副本集添加到集羣:
sh.addShard( "shardtest01/192.168.150.232:27001,192.168.150.253:27001,192.168.150.123:27001")
sh.addShard( "shardtest02/192.168.150.232:27002,192.168.150.253:27002,192.168.150.123:27002")
2.7 創建分片表
爲數據庫啓動分片
sh.enableSharding("martin")
對 collection 進行分片,這裏是根據"_id"字段做 hash 分片,讀者可根據自己實際情況進行分片,要注意的是,如果collection drop 掉重建了,需要重新創建 collection 分片規則。
sh.shardCollection("martin.userinfo", {_id: "hashed" } )
查看分片詳情
sh.status();
可以看到如下結果:
......
shards
[
{
_id: 'shardtest01',
host: 'shardtest01/192.168.150.123:27001,192.168.150.232:27001,192.168.150.253:27001',
state: 1,
topologyTime: Timestamp({ t: 1649497030, i: 2 })
},
{
_id: 'shardtest02',
host: 'shardtest02/192.168.150.123:27002,192.168.150.232:27002,192.168.150.253:27002',
state: 1,
topologyTime: Timestamp({ t: 1649502644, i: 2 })
}
]
---
active mongoses
[ { '5.0.3': 1 } ]
......
databases
[
{
database: { _id: 'config', primary: 'config', partitioned: true },
collections: {
'config.system.sessions': {
shardKey: { _id: 1 },
unique: false,
balancing: true,
chunkMetadata: [
{ shard: 'shardtest01', nChunks: 1022 },
{ shard: 'shardtest02', nChunks: 2 }
],
chunks: [
'too many chunks to print, use verbose if you want to force print'
],
tags: []
}
}
},
{
database: {
_id: 'martin',
primary: 'shardtest01',
partitioned: true,
version: {
uuid: UUID("b0ec23fa-2c9c-45ad-8ac1-eab27e4b24c3"),
timestamp: Timestamp({ t: 1649497150, i: 1 }),
lastMod: 1
}
},
collections: {
'martin.userinfo': {
shardKey: { _id: 'hashed' },
unique: false,
balancing: true,
chunkMetadata: [
{ shard: 'shardtest01', nChunks: 1 },
{ shard: 'shardtest02', nChunks: 1 }
],
chunks: [
{ min: { _id: MinKey() }, max: { _id: Long("0") }, 'on shard': 'shardtest02', 'last modified': Timestamp({ t: 2, i: 0 }) },
{ min: { _id: Long("0") }, max: { _id: MaxKey() }, 'on shard': 'shardtest01', 'last modified': Timestamp({ t: 2, i: 1 }) }
],
tags: []
}
}
}
]
其中:
-
database._id 表示開啓分片的庫;
-
database.primary 表示主 shard;
-
database.partitioned 表示這個庫是否開啓分片,true 表示開啓;
-
collections 中表示分片的表;
-
collections.shardKey 表示分片健;
-
collections.balancing 爲 true 表示平衡;
-
collections.chunks 數據分佈情況。
3 數據分佈測試
3.1 寫入數據
登錄 mongos
mongo --host 192.168.150.232 --port 27017 martin
往分片表 userinfo 寫入數據:
for (var i=1; i<=10; i++ ) db.userinfo.save({userid:i,username:'a'});
3.2 查看數據分佈
登錄 shardtest01:
mongo --host 192.168.150.232 --port 27001 martin
查看數據:
shardtest01:PRIMARY> db.userinfo.find()
{ "_id" : ObjectId("62524b9da1e27b1723e77844"), "userid" : 1, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e77847"), "userid" : 4, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e77848"), "userid" : 5, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e7784a"), "userid" : 7, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e7784c"), "userid" : 9, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e7784d"), "userid" : 10, "username" : "a" }
登錄 shardtest02:
mongo --host 192.168.150.232 --port 27002 martin
查看數據:
shardtest02:PRIMARY> db.userinfo.find()
{ "_id" : ObjectId("62524b9da1e27b1723e77845"), "userid" : 2, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e77846"), "userid" : 3, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e77849"), "userid" : 6, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e7784b"), "userid" : 8, "username" : "a" }
可以看到數據分佈在兩個分片中。
4 參考資料
官方文檔 Deploy a Sharded Cluster:https://www.mongodb.com/docs/manual/tutorial/deploy-shard-cluster/