為何要分區
1.減少單機請求數,降低單機負載,提高總負載
2.減少單機的儲存空間,提高總存空間。
常見的mongodb sharding 伺服器架構
要構建一個 MongoDB Sharding Cluster,需要三種角色:
1.Shard Server
即儲存實際資料的分區,每個Shard可以是一個mongod執行個體,也可以是一組mongod執行個體構成的Replication Set。為了實現每個Shard內部的auto-failover(自動故障切換),MongoDB官方建議每個Shard為一組Replica Set。
2.Config Server
為了將一個特定的collection儲存在多個shard中,需要為該collection指定一個shard key(片鍵),例如{age: 1} ,shard key可以決定該條記錄屬於哪個chunk(分區是以chunk為單位,後續會介紹)。Config Servers就是用來儲存:所有shard節點的配置資訊、每個chunk的shard key範圍、chunk在各shard的分布情況、該叢集中所有DB和collection的sharding配置資訊。
3.Route Process
這是一個前端路由,用戶端由此接入,然後詢問Config Servers需要到哪個Shard上查詢或儲存記錄,再串連相應的Shard進行操作,最後將結果返回給用戶端。用戶端只需要將原本發給mongod的查詢或更新要求原封不動地發給Routing Process,而不必關心所操作的記錄儲存在哪個Shard上。(所有操作在mongos上操作即可)
配置分區伺服器
下面我們在同一台物理機器上構建一個簡單的 Sharding Cluster:
Shard Server 1:27017Shard Server 2:27018Config Server :27027Route Process:40000
1.步驟一: 啟動Shard Server
mkdir -p ./data/shard/s0 ./data/shard/s1 #建立資料目錄mkdir -p ./data/shard/log # 建立日誌目錄./bin/mongod --port 27017 --dbpath /usr/local/mongodb/data/shard/s0 --fork --logpath /usr/local/mongodb/data/shard/log/s0.log # 啟動Shard Server執行個體1./bin/mongod --port 27018 --dbpath /usr/local/mongodb/data/shard/s1 --fork --logpath /usr/local/mongodb/data/shard/log/s1.log # 啟動Shard Server執行個體2
步2.驟二: 啟動Config Server
mkdir -p ./data/shard/config #建立資料目錄./bin/mongod --port 27027 --dbpath /usr/local/mongodb/data/shard/config --fork --logpath /usr/local/mongodb/data/shard/log/config.log #啟動Config Server執行個體
注意,這裡我們完全可以像啟動普通mongodb服務一樣啟動,不需要添加—shardsvr和configsvr參數。因為這兩個參數的作用就是改變啟動連接埠的,所以我們自行指定了連接埠就可以
3.步驟三: 啟動Route Process
./bin/mongos --port 4000 --configdb localhost:27027 --fork --logpath /usr/local/mongodb/data/shard/log/route.log --chunkSize=1 # 啟動Route Server執行個體
mongos啟動參數中,chunkSize這一項是用來指定chunk的大小的,單位是MB,預設大小為200MB,為了方便測試Sharding效果,我們把chunkSize指定為 1MB。意思是當這個分區中插入的資料大於1M時開始進行資料轉移
4.步驟四: 配置Sharding
# 我們使用MongoDB Shell登入到mongos,添加Shard節點./bin/mongo admin --port 40000 #此操作需要串連admin庫> db.runCommand({ addshard:"localhost:27017" }) #添加 Shard Server 或者用 sh.addshard()命令來添加,下同;{ "shardAdded" : "shard0000", "ok" : 1 }> db.runCommand({ addshard:"localhost:27018" }){ "shardAdded" : "shard0001", "ok" : 1 }> db.runCommand({ enablesharding:"test" }) #設定分區儲存的資料庫{ "ok" : 1 }> db.runCommand({ shardcollection: "test.users", key: { id:1 }}) # 設定分區的集合名稱。且必須指定Shard Key,系統會自動建立索引,然後根據這個shard Key來計算{ "collectionsharded" : "test.users", "ok" : 1 } > sh.status(); #查看片的狀態 > printShardingStatus(db.getSisterDB("config"),1); # 查看片狀態(完整版); > db.stats(); # 查看所有的分區伺服器狀態
注意這裡我們要注意片鍵的選擇,選擇片鍵時需要根據具體業務的資料形態來選擇,切不可隨意選擇,實際中尤其不要輕易選擇自增_id作為片鍵,除非你很清楚你這麼做的目的,具體原因我不在此分析,根據經驗推薦一種較合理的片鍵方式,“自增欄位+查詢欄位”,沒錯,片鍵可以是多個欄位的組合。
另外這裡說明一點,分區的機制:mongodb不是從單篇文檔的層級,絕對平均的散落在各個片上, 而是N篇文檔,形成一個塊"chunk",優先放在某個片上, 當這片上的chunk,比另一個片的chunk區別比較大時(>=3) ,會把本片上的chunk,移到另一個片上, 以chunk為單位,維護片之間的資料均衡。
也就是說,一開始插入資料時,資料是只插入到其中一塊分區上的,插入完畢後,mongodb內部開始在各片之間進行資料的移動,這個過程可能不是立即的,mongodb足夠智能會根據當前負載決定是立即進行移動還是稍後移動。
在插入資料後,立馬執行db.users.stats();兩次可以驗證如上所說。
這種分區機制,節省了人工維護成本,但是由於其是優先往某個片上插入,等到chunk失衡時,再移動chunk,並且隨著資料的增多,shard的執行個體之間,有chunk來回移動的現象,這將會為伺服器帶來很大的IO開銷,解決這種開銷的方法,就是手動預先分區;
手動預先分區
以shop.user表為例:
sh.shardCollection(‘shop.user',{userid:1}); # user表用userid做shard keyfor(var i=1;i<=40;i++) { sh.splitAt('shop.user',{userid:i*1000}) } # 預先在1K 2K...40K這樣的界限切好chunk(雖然chunk是空的), 這些chunk將會均勻移動到各片上.
通過mongos添加user資料. 資料會添加到預先分配好的chunk上, chunk就不會來回移動了.
repliction set and shard
一般mongoDB如果真的到了分區的層級後,那片伺服器避無可免的要用到複製集,部署的基本思路同上,只需要注意兩點:
sh.addShard( host ) server:port OR setname/server:port # 如果是複製集的片伺服器,我們應該複製集的名稱寫在前面比如
sh.addShard('ras/192.168.42.168:27017'); # 27017也就是複製集中的primary
另外在啟動原生mongod服務的時候,最好把ip也給寫進去,否則有可能會有不可預知的錯誤。
查看分區配置的方法:
1.列舉使用分區的資料庫
為了列舉使用分區的資料庫,需要查詢Config資料庫。如果partitioned域值為true,則這個庫使用了分區技術。
串連一個mongos執行個體,運行命令擷取使用分區功能的資料庫:
use configdb.databases.find( { "partitioned" : true} )
例如:使用以下命令返回叢集中的所有資料庫
use configdb.databases.find()
如果返回結果:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }{ "_id" : "mydb", "partitioned" : true, "primary" : "firstset" }{ "_id" : "test", "partitioned" : false, "primary" : "secondset" }
顯示只有mydb使用了分區。
2.列舉所有的分區
為了列舉當前集合的所有分區,使用listShards命令:
use admindb.runCommand( { listShards : 1 })
返回結果:
{ "shards" : [ { "_id" : "firstset", "host" : "firstset/mongo01:10001,mongo01:10002,mongo01:10003" }, { "_id" : "secondset", "host" : "secondset/mongo01:30001,mongo01:30002,mongo01:30003" } ], "ok" : 1}
3.查看叢集的詳細資料
為了查看叢集的詳細資料,可以使用db.printShardingStatus()或者sh.status()。所有的方法返回同樣的結果。
例如,使用
查看資訊:
--- Sharding Status --- sharding version: { "_id" : 1, "version" : 4, "minCompatibleVersion" : 4, "currentVersion" : 5, "clusterId" : ObjectId("535a2dab0063b308757e1b70")} shards: { "_id" : "firstset", "host" : "firstset/mongo01:10001,mongo01:10002,mongo01:10003" } { "_id" : "secondset", "host" : "secondset/mongo01:30001,mongo01:30002,mongo01:30003" } databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "mydb", "partitioned" : true, "primary" : "firstset" } mydb.test_collection shard key: { "name" : 1 } chunks: secondset 6 firstset 6 { "name" : { "$minKey" : 1 } } -->> { "name" : "cat" } on : secondset Timestamp(2, 0) { "name" : "cat" } -->> { "name" : "cow" } on : secondset Timestamp(3, 0) { "name" : "cow" } -->> { "name" : "dog" } on : secondset Timestamp(4, 0) { "name" : "dog" } -->> { "name" : "dragon" } on : secondset Timestamp(5, 0) { "name" : "dragon" } -->> { "name" : "elephant" } on : secondset Timestamp(6, 0) { "name" : "elephant" } -->> { "name" : "horse" } on : secondset Timestamp(7, 0) { "name" : "horse" } -->> { "name" : "lion" } on : firstset Timestamp(7, 1) { "name" : "lion" } -->> { "name" : "pig" } on : firstset Timestamp(1, 7) { "name" : "pig" } -->> { "name" : "rabbit" } on : firstset Timestamp(1, 8) { "name" : "rabbit" } -->> { "name" : "snake" } on : firstset Timestamp(1, 9) { "name" : "snake" } -->> { "name" : "tiger" } on : firstset Timestamp(1, 10) { "name" : "tiger" } -->> { "name" : { "$maxKey" : 1 } } on : firstset Timestamp(1, 11) { "_id" : "test", "partitioned" : false, "primary" : "secondset" }
(1)sharding version展示了分區中繼資料的版本號碼。
(2)shards展示了在叢集中被作為分區的mongod執行個體。
(3)databases展示了叢集中所有的資料庫,包括沒有使用分區功能的庫。
(4)chunks資訊展示了mydb庫的每個分區上有多少個塊和每個塊的範圍。