replica set
副本集形式,類似於mysql master slave,這裏成爲primary 和 secondary,具有選舉機制,最低3臺,1主2從。
複製主要用於備份、災難恢復和讀寫分離。一個Replica Set就是一組mongod實例。Replica Set中的Primary接收所有的寫操作,Secondaries從Primary複製操作然後應用到自己的data set。
心跳檢測
在replica sets結構中,節點之間會各自向其它節點發送一個心跳檢測請求。在正常情況下,其它節點會返回一個包含自身信息的回覆包,回覆包中主要包括了下面一些信息:它們現在是什麼角色(primary 還是 secondary),它們是否能夠在必要的時候成爲 primary,以及他們當前時鐘時間等等。
節點在收到回覆包後,會用這些信息更新自己的一個狀態映射表。
當primary節點映射表發生了變化,那麼它會那自己是否還能和集羣中大多數節點進行通信,如果不能與大多數節點通信,那麼它會把自己從 primary 降級爲 secondary。
降級
在節點從 primary 降級爲 secondary 的過程中,會有一些問題出現。在 MongoDB 中,寫操作默認是通過 fire-and-forget 的模式來進行的,也就是說寫操作通常不關心是否成功,發完請求後客戶端就認爲成功了。但如果這時候 primary 進行降級操作,那麼客戶端並不知道這時候 primary 已經降級成爲 secondary 了,客戶端可能還會將後續的寫操作發送給這個節點。這時候剛剛降級的這個 secondary 可以發送一個包說“我已經不是 primary 了”,但是我們上面說過了,客戶端根本就無視你這個包。所以客戶端根本不知道這次寫入已經失敗了。
對於這個問題,你可能會說"那我們每次都使用安全寫入不就行了"(安全寫入意思是說等待服務器返回成功後客戶端才認爲寫成功了),但是很明顯,這非常不靠譜。所以我們的做法是,在一個 primary 降級成爲 secondary 後,它會將原來的所有連接關閉。這樣客戶端在下一次寫入的時候就會出現 socket 錯誤。而客戶端在發現這個錯誤之後,就會重新向集羣獲取新的 primary 的地址,並將後續的寫操作都往新的服務器上寫入。
選舉
選舉是一個獲取多數票的過程,具有一定網絡io耗時。
而當X發現現在需要一個 primary 並且自己又正好可以充當時.
它就會發起一輪選舉:X節點會向Y、Z節點各發起一個請求包:
告知他們,我認爲我可以接管 primary 的角色,你們覺得怎麼樣?
其他節點在收到通知時,他們會進行下面幾項檢測:
集羣中是否有一個primary了?
他們自己的數據是否比X節點更新?
他們自己的數據是否比X節點更新?
一旦其中某一項不滿足,pass.
如果都滿足,會反饋給X。X會告訴其他節點,我是primary。其他節點給予回饋(投票)說同意,X才正式升級爲primary。
注意:這個投票過程,有30秒間隔,單節點30秒內不能重複進行。
投票規則: 如果沒有人投反對票,並且贊成票的比例過半,那麼本輪選舉對象就能夠成爲 primary。(很民主啊,人人蔘與~)
shard(分區)
mongodb的分區功能其實比較簡單,就是根據數據對元素進行分區。只是這個邏輯可以自定義而已。
和普通的replica set集羣比起來,有什麼不同呢?
- 增加了route
- 增加了config server
步驟
1. 開啓config
./bin/mongod --fork --dbpath data/config/ --logpath log/config.log –port 10000
2. 開啓router
./bin/mongos --port 20000 --configdb 192.168.1.13:10000 --logpath log/mongos.log --fork
3. 啓動各個分片服務
./bin/mongod --dbpath data/shard1/ --logpath log/shard1.log --fork --port 27017
./bin/mongod --dbpath data/shard2/ --logpath log/shard1.log --fork --port 27018
./bin/mongod --dbpath data/shard3/ --logpath log/shard1.log --fork --port 27019
./bin/mongod --dbpath data/shard4/ --logpath log/shard1.log --fork --port 27020
4. 添加分片
./bin/mongo --port 20000
db.runCommand({addshard:"192.168.1.13:27017",allowLocal:true })
db.runCommand({addshard:"192.168.1.13:27018",allowLocal:true })
db.runCommand({addshard:"192.168.1.13:27019",allowLocal:true })
db.runCommand({addshard:"192.168.1.13:27020",allowLocal:true })
5. 默認分區規則是根據_id來的,我們也可以自定義去配置。
sh.shardCollection('dbName.colletionName',{keyName:1});
6. 刪除分片
db.runCommand({"removeshard":"192.168.1.13:27020"})
移除分片需要一個過程,MongoDB會把移除的片上的數據(塊)挪到其他片上,會造成一定耗時,要注意業務場景。
7. 管理分片
db.shards.find()
{ "_id" : "shard0000", "host" : "192.168.1.13:27017" }
{ "_id" : "shard0000", "host" : "192.168.1.13:27018" }
{ "_id" : "shard0000", "host" : "192.168.1.13:27019" }
{ "_id" : "shard0000", "host" : "192.168.1.13:27020" }
分片片鍵
mongodb分片是分佈式存儲的,因此,片鍵的定義對整個性能的影響巨大。所幸,mongodb是可以自定義片鍵的。
好片鍵的要素:
1. 讀和寫的分佈
2. 數據塊的大小
3. 每個查詢命中的分片數目
從設計角度來說,其實和我們常用的mysql的邏輯分表很像。
分片集羣數據分佈方式:
基於範圍
基於Hash
基於zone/tag
分片集羣數據分佈方式-基於範圍(最常用,適合一般性業務邏輯): | | | | ------------ | ------------ | | 片鍵範圍查詢性能好 | 數據分佈可能不均勻 | |優化讀|容易有熱點| | | |
分片集羣數據分佈方式-基於哈希(適用:日誌,物聯網等高併發場景): | | | | ------------ | ------------ | | 數據分佈均勻,寫優化 | 範圍查詢效率低 | | | |
讀寫(轉圖)
分析:
- 如果不能命中片鍵,範圍查詢將會掃描全表(性能極地,損耗極大),結果集將在route層聚集(類似於hadoop的map reduce)
- 需要合理提前規劃片鍵,否則後期移動數據將需要複雜處理。
- 如果集羣在_id上進行了分片,則無法再在其他字段上建立唯一索引。