如果說復(fù)制集是mongo為了備份數(shù)據(jù),將一份數(shù)據(jù)存儲在多臺實例上的一種集群架構(gòu)的話,那么當(dāng)我們的數(shù)據(jù)存儲過多,最好能將數(shù)據(jù)分開存儲,這個時候就可以使用mongo的另一個多實例部署架構(gòu)--數(shù)據(jù)分片
分片的概念就是將數(shù)據(jù)拆分,將其分散存儲在不同機器上的過程。在很多中間件都有類似的概念,有些中間件中叫做'分區(qū)'概念,其實也是與分片類似的說法。基本上現(xiàn)代主流數(shù)據(jù)庫都支持,用戶手動管理,將數(shù)據(jù)存儲在不同的集合中,連接彼此獨立,但是手動維護帶來的問題就是維護難度的增大,整體分片的調(diào)整,如增加節(jié)點,刪除節(jié)點難度都變得很困難。而在MongoDB中,內(nèi)部集成了自動分片功能,可以讓分片操作對用戶不可見,從而簡化手動管理的成本。
分片初體驗
想要開始搭建一個分片集群,我們先理解一下這個集群中的幾個組件以及其作用,由于分片集群的目標(biāo)一般是想要滿足五臺、十臺甚至更多的實例,對外僅僅是和連接單機mongo一樣,因此必須有其他組件幫忙隱藏其中的一些細(xì)節(jié),因此整個mongo的分片集群架構(gòu)就可以分成如下這樣:

分片集群的三大組件
我們可以看到整個MongoDB Sharding集群中大體可以分為三個組件:
數(shù)據(jù)分片 -- shards
數(shù)據(jù)分片用于保存數(shù)據(jù),保證了數(shù)據(jù)的高可用和一致性,這里每一個shard可以是獨立的mongod實例,也可以是一個復(fù)制集,防止出現(xiàn)單點的故障問題。當(dāng)然也可以選擇把所有的shard的復(fù)制集放在一個服務(wù)器中啟動多個mongod實例,在sharding中,每個node的database數(shù)據(jù)庫可以選擇分片也可以選擇不分片,每一個db中都有一個獨立的primary shard,在未分片的集合中就是存在于各自的primary shard中的
查詢路由 -- mongos
我們可以看到,當(dāng)我們想要連接分片集群,將數(shù)據(jù)分開存儲,或者想要從分開存儲的分片集群中,將我們想要的數(shù)據(jù)聚集查詢出來,為了隱藏其中的細(xì)節(jié),這個時候就需要使用查詢路由--mongos了,客戶端連接不是直接連接分片集群中,而是連接到mongos,通過mongos進行一次路由過程的操作分發(fā),而mongos通過查詢維護的一個“內(nèi)容列表”,里面記錄了每個分片中按照什么規(guī)則(分片鍵)存儲數(shù)據(jù),每個分片中包含了哪些內(nèi)容等,而客戶端的請求到達mongos以后,mongos會根據(jù)記錄的內(nèi)容,選擇性將請求分發(fā)到對應(yīng)的一個或者多個分片服務(wù)實例上,然后再將所有分片的響應(yīng)結(jié)果進行合并再統(tǒng)一輸出給客戶端程序,從而實現(xiàn)了屏蔽分片的細(xì)節(jié),流程大體如下:

配置服務(wù)器 -- Config Server
配置服務(wù)器就是用于存儲分片集群的配置信息的元數(shù)據(jù),其中包含了mongos需要的shard的路由規(guī)則,路由鍵等信息,在mongo 3.2版本開始config server可以配置成為replica set了,在3.4以后官方已經(jīng)規(guī)定config server必須為replica set,并且為了保證生產(chǎn)環(huán)境的穩(wěn)定,rs中至少要有三個副本集成員存在
分片集群上手初體驗
現(xiàn)在我們開始搭建一個簡單的分片架構(gòu),在mongo3.4以后的版本中,一個分片集群,shard節(jié)點的數(shù)量至少要是兩個,而config節(jié)點和路由mongos節(jié)點則至少需要一個,因此我們這里來通過在一臺服務(wù)器上啟動多個節(jié)點的方式 模擬分片架構(gòu),搭建兩個shards復(fù)制集作為兩個shard節(jié)點,搭建一個config server復(fù)制集作為config節(jié)點,而mongos我們則使用單節(jié)點啟動方式,快速搭建一個分片集群架構(gòu)快速上手體驗一下
配置shards
cd /etc;
#首先創(chuàng)建一下shard的log存放的目錄
mkdir -p /var/log/mongodb_shard;
mkdir -p /var/log/mongo_shard1;
mkdir -p /var/log/mongo_shard2;
mkdir -p /var/log/mongodb_shard3;
#接著創(chuàng)建shard的數(shù)據(jù)庫文件存放目錄
mkdir -p /var/lib/mongo_shard;
mkdir -p /var/lib/mongo_shard1;
mkdir -p /var/lib/mongo_shard2;
mkdir -p /var/lib/mongo_shard3;
#創(chuàng)建shard啟動配置文件存放目錄
mkdir mongo_shard/;
mkdir mongo_shard1/;
mkdir mongo_shard2/;
mkdir mongo_shard3/;
#創(chuàng)建pidFilePath存放目錄
mkdir -p /var/run/mongo_shard;
mkdir -p /var/run/mongo_shard1;
mkdir -p /var/run/mongo_shard2;
mkdir -p /var/run/mongo_shard3;
#將mongo的配置文件分別cp到這些目錄中,這里我們cp的是上篇文章中我們配置的復(fù)制集的配置文件,否則配置文件中還要配置復(fù)制集
cp -r mongod1.conf mongo_shard/mongod.conf;
cp -r mongod1.conf mongo_shard1/mongod.conf;
cp -r mongod1.conf mongo_shard2/mongod.conf;
cp -r mongod1.conf mongo_shard3/mongod.conf;
#vim修改每一個shard的config啟動參數(shù),主要修改systemLog下的path,storage下的dbPath,processManagement下的pidFilePath,這里我們分別修改為上面創(chuàng)建的log目錄,db存儲目錄,以及pidFilePath存放目錄,net下的啟動端口這里分別修改為30017、30018和30019、30020,除此之外,由于shard我們要創(chuàng)建成為兩個rs,因此,replication下的replSetName參數(shù),mongo_shard和mongo_shard1我們將其指定為shard,mongo_shard和mongo_shard1我們將其指定為shard2,代表幾個shard組成的復(fù)制集名稱分別叫shard和shard2
vim mongo_shard/mongod.conf;
vim mongo_shard1/mongod.conf;
vim mongo_shard2/mongod.conf;
vim mongo_shard3/mongod.conf;
這里我們需要注意,由于當(dāng)前的角色是shard,因此我們在給常規(guī)的配置文件參數(shù)修改后,還要添加sharding相關(guān)的配置,代表當(dāng)前的角色為shard,如下:

添加如下配置即可:
sharding:
# 分片角色
clusterRole: shardsvr
下面是一個配置好的shard的mongod.conf的完整配置示例:
# mongod.conf
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb_shard/mongod.log
# Where and how to store data.
storage:
dbPath: /var/lib/mongo_shard
journal:
enabled: true
# engine:
# wiredTiger:
# how the process runs
processManagement:
fork: true #后臺啟動進程
pidFilePath: /var/run/mongo_shard/mongod.pid
timeZoneInfo: /usr/share/zoneinfo
# network interfaces
net:
port: 30017
bindIp: 0.0.0.0 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.
#security:
#operationProfiling:
#replication:
replication:
replSetName: shard
#sharding:
sharding:
# 分片角色
clusterRole: shardsvr
## Enterprise-Only Options
#auditLog:
#snmp:
將所有的shard的目錄和文件配置準(zhǔn)備完成,我們開始將其分別啟動,組成rs復(fù)制集:
/usr/bin/mongod -f /etc/mongo_shard/mongod.conf;
/usr/bin/mongod -f /etc/mongo_shard1/mongod.conf;
/usr/bin/mongod -f /etc/mongo_shard2/mongod.conf;
/usr/bin/mongod -f /etc/mongo_shard3/mongod.conf;
#查看當(dāng)前進程是否都存在
ps -ef|grep mongo
#可以看到 已經(jīng)啟動完成了
root 113935 1 13 02:35 ? 00:00:02 /usr/bin/mongod -f /etc/mongo_shard/mongod.conf
root 114005 1 26 02:35 ? 00:00:03 /usr/bin/mongod -f /etc/mongo_shard1/mongod.conf
root 114151 1 41 02:35 ? 00:00:03 /usr/bin/mongod -f /etc/mongo_shard2/mongod.conf
root 114252 1 41 02:35 ? 00:00:03 /usr/bin/mongod -f /etc/mongo_shard3/mongod.conf
root 114341 2433 0 02:35 pts/0 00:00:00 grep --color=auto mongo
#登錄shard復(fù)制集的任意一個節(jié)點,初始化復(fù)制集
/usr/bin/mongo --port 30017;
#初始化復(fù)制集
rs.initiate({ _id:"shard", members:[{ _id:0, host:"127.0.0.1:30017" },{ _id:1, host:"127.0.0.1:30018" }] });
#登錄shard2復(fù)制集的任意一個節(jié)點,初始化復(fù)制集
/usr/bin/mongo --port 30019;
#初始化復(fù)制集
rs.initiate({ _id:"shard2", members:[{ _id:0, host:"127.0.0.1:30019" },{ _id:1, host:"127.0.0.1:30020" }] });
配置config server
用于存儲分片配置的Config Server我們這里僅僅配置成一個master-slave的rs即可:
cd /etc;
#首先創(chuàng)建一下config_server的log存放的目錄
mkdir -p /var/log/mongo/config_master;
mkdir -p /var/log/mongo/config_slave;
#接著創(chuàng)建config_server的數(shù)據(jù)庫文件存放目錄
mkdir -p /var/lib/mongo/config_master;
mkdir -p /var/lib/mongo/config_slave;
#創(chuàng)建config_server啟動配置文件存放目錄
mkdir mongo_config_master/;
mkdir mongo_config_slave/;
#創(chuàng)建pidFilePath存放目錄
mkdir -p /var/run/mongo_config_master;
mkdir -p /var/run/mongo_config_slave;
#將mongo的配置文件分別cp到這些目錄中,這里我們cp的是上篇文章中我們配置的復(fù)制集的配置文件,否則配置文件中還要配置復(fù)制集
cp -r mongod1.conf mongo_config_master/mongod.conf;
cp -r mongod1.conf mongo_config_slave/mongod.conf;
#vim修改每一個config_server的config啟動參數(shù),主要修改systemLog下的path,storage下的dbPath,processManagement下的pidFilePath,這里我們分別修改為上面創(chuàng)建的log目錄,db存儲目錄,以及pidFilePath存放目錄,net下的啟動端口這里分別修改為37017和37018,除此之外,由于這里的節(jié)點我們要創(chuàng)建成為一個主-從的rs,因此,replication下的replSetName參數(shù)我們將其定義成config,代表幾個節(jié)點組成的復(fù)制集名稱叫config
vim mongo_config_master/mongod.conf;
vim mongo_config_slave/mongod.conf;
與上面shard的處理方式幾乎一樣,當(dāng)然這里唯一的區(qū)別就是,分片角色的配置,上面是shard,這里需要配置為config,如下:
#sharding:
sharding:
# 分片角色
clusterRole: configsvr
一切配置準(zhǔn)備好以后,我們開始啟動config server復(fù)制集,完成初始化操作:
/usr/bin/mongod -f /etc/mongo_config_master/mongod.conf;
/usr/bin/mongod -f /etc/mongo_config_slave/mongod.conf;
#登錄第一個復(fù)制集
/usr/bin/mongo --port 37017;
#初始化復(fù)制集
rs.initiate({ _id:"config", members:[{ _id:0, host:"127.0.0.1:37017" },{ _id:1, host:"127.0.0.1:37018" }] });
啟動查詢路由--mongos
這里我們只配置一個mongos實例,不去做集群啟動了,因此我們需要和前面一樣,創(chuàng)建四個目錄,分別存放log,db文件和pid目錄以及config配置文件的目錄
cd /etc;
#首先創(chuàng)建一下shard的log存放的目錄
mkdir -p /var/log/mongos_db;
#接著創(chuàng)建mongos的數(shù)據(jù)庫文件存放目錄
mkdir -p /var/lib/mongos;
#創(chuàng)建mongos啟動配置文件存放目錄
mkdir mongos/;
#創(chuàng)建pidFilePath存放目錄
mkdir -p /var/run/mongos;
#復(fù)制 配置文件到目錄下
cp -r mongod1.conf mongos/mongod.conf;
#修改配置文件
vim mongos/mongod.conf;
#修改 path: /var/log/mongos_db/mongod1.log
#修改 dbPath: /var/lib/mongos/mongo1
#修改 pidFilePath: /var/run/mongos/mongod1.pid
#修改 port: 27050
#修改 replSetName: mongos
#配置文件中添加如下配置,指定config集群的信息
sharding:
configDB: config/127.0.0.1:37017,127.0.0.1:37018
#需要注意的是,使用mongos的話,是無法識別storage.dbPath配置的,因此要注釋當(dāng)前的配置
#storage:
#dbPath: /var/lib/mongos/mongo1
#journal:
# enabled: true
#除此之外,還要刪除replication相關(guān)的配置
#replication:
# replSetName: mongos
#啟動mongos,注意這里用的是mongos啟動
/usr/bin/mongos -f /etc/mongos/mongod.conf;
#連接mongos
mongo --port=27050;
這個時候我們連接進去,可以看到,當(dāng)前的角色已經(jīng)變了,成為了mongos,需要注意的是我們要做分片操作,首先需要給需要分片的數(shù)據(jù)庫啟動分片,開啟命令如下:
sh.enableSharding("db_name");
現(xiàn)在整個db的集合都可以進行分片了,不過需要注意的是,我們要對某個集合做分片,必須要選擇一個片鍵,片鍵是集合文檔內(nèi)的一個鍵,Mongo會根據(jù)策略和這個片鍵進行拆分?jǐn)?shù)據(jù),我們要給集合啟用分片之前,需要先在片鍵上創(chuàng)建索引:
db.collection.ensureIndex({"key":1});
然后我們就可以設(shè)置根據(jù)這個key來作為片鍵進行分片操作了:
sh.shardCollection("db.collection",{id:"key"});
配置完成后,我們來將shard信息添加進來,進行分片數(shù)據(jù)插入測試:
use admin;
#添加shard信息
mongos> sh.addShard("shard/127.0.0.1:30017,127.0.0.1:30018");
mongos> sh.addShard("shard2/127.0.0.1:30019,127.0.0.1:30020");
#設(shè)置test庫為允許sharding
sh.enableSharding("test");
#設(shè)置test中的tes集合的id按照hash的方式進行sharding
sh.shardCollection("test.tes",{id:"hashed"});
#插入幾條數(shù)據(jù)測試一下
for(i=1;i<=1000;i++){db.tes.insert({id:i,name:"Leo"})}
這里我們插入數(shù)據(jù)完成以后,分別進入兩個shard查看一下當(dāng)前的數(shù)據(jù),看是否生效
#登錄第一個shard
/usr/bin/mongo --port 30017;
use test;
db.tes.find();
#結(jié)果如下:
shard:PRIMARY> db.tes.find()
{ "_id" : ObjectId("60a3a08cd9ea857697bcf0cd"), "id" : 3, "name" : "Leo3" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0d0"), "id" : 6, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0d2"), "id" : 8, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0d5"), "id" : 11, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0d6"), "id" : 12, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0de"), "id" : 20, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0df"), "id" : 21, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0e4"), "id" : 26, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0e5"), "id" : 27, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0e8"), "id" : 30, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0ea"), "id" : 32, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0eb"), "id" : 33, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0f0"), "id" : 38, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0f2"), "id" : 40, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0f3"), "id" : 41, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0f4"), "id" : 42, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0f6"), "id" : 44, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0fa"), "id" : 48, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0fc"), "id" : 50, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0fe"), "id" : 52, "name" : "Leo" }
#登錄第二個shard
/usr/bin/mongo --port 30019;
use test;
db.tes.find();
#結(jié)果如下:
shard2:PRIMARY> db.tes.find()
{ "_id" : ObjectId("60a3a07bd9ea857697bcf0ca"), "id" : 0, "name" : "Leo0" }
{ "_id" : ObjectId("60a3a081d9ea857697bcf0cb"), "id" : 1, "name" : "Leo1" }
{ "_id" : ObjectId("60a3a086d9ea857697bcf0cc"), "id" : 2, "name" : "Leo2" }
{ "_id" : ObjectId("60a3a091d9ea857697bcf0ce"), "id" : 4, "name" : "Leo4" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0cf"), "id" : 5, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0d1"), "id" : 7, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0d3"), "id" : 9, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0d4"), "id" : 10, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0d7"), "id" : 13, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0d8"), "id" : 14, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0d9"), "id" : 15, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0da"), "id" : 16, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0db"), "id" : 17, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0dc"), "id" : 18, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0dd"), "id" : 19, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0e0"), "id" : 22, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0e1"), "id" : 23, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0e2"), "id" : 24, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0e3"), "id" : 25, "name" : "Leo" }
{ "_id" : ObjectId("60a3a0d3d9ea857697bcf0e6"), "id" : 28, "name" : "Leo" }
可見當(dāng)前我們的分片整個是已經(jīng)搭建完成了,這個時候我們可以查看當(dāng)前的分片集群的一個狀態(tài),通過如下命令:
mongos> sh.status();
--- Sharding Status ---
sharding version: {
"_id" : 1,
"minCompatibleVersion" : 5,
"currentVersion" : 6,
"clusterId" : ObjectId("60a39c69f5d7a342f9cdfb1d")
}
shards:
{ "_id" : "shard", "host" : "shard/127.0.0.1:30017,127.0.0.1:30018", "state" : 1 }
{ "_id" : "shard2", "host" : "shard2/127.0.0.1:30019,127.0.0.1:30020", "state" : 1 }
active mongoses:
"4.2.8" : 1
autosplit:
Currently enabled: yes
balancer:
Currently enabled: yes
Currently running: no
Failed balancer rounds in last 5 attempts: 0
Migration Results for the last 24 hours:
512 : Success
databases:
{ "_id" : "config", "primary" : "config", "partitioned" : true }
config.system.sessions
shard key: { "_id" : 1 }
unique: false
balancing: true
chunks:
shard 512
shard2 512
too many chunks to print, use verbose if you want to force print
{ "_id" : "test", "primary" : "shard2", "partitioned" : true, "version" : { "uuid" : UUID("72bb131e-1e88-4267-adc2-9f97e1e8d663"), "lastMod" : 1 } }
test.tes
shard key: { "id" : "hashed" }
unique: false
balancing: true
chunks:
shard 2
shard2 2
{ "id" : { "$minKey" : 1 } } -->> { "id" : NumberLong("-4611686018427387902") } on : shard Timestamp(1, 0)
{ "id" : NumberLong("-4611686018427387902") } -->> { "id" : NumberLong(0) } on : shard Timestamp(1, 1)
{ "id" : NumberLong(0) } -->> { "id" : NumberLong("4611686018427387902") } on : shard2 Timestamp(1, 2)
{ "id" : NumberLong("4611686018427387902") } -->> { "id" : { "$maxKey" : 1 } } on : shard2 Timestamp(1, 3)
sh命令和前面的rs命令很像,不過這里是用來代替分片集群管理的,而rs則是一個全局變量,除了可以管理以外,還內(nèi)置了很多輔助函數(shù),我們也可以選擇使用sh.help();查看可以使用的輔助函數(shù)有哪些