在分佈式應用系統中,mongodb 已經成爲 NoSQL 經典數據庫。要想很好的使用 mongodb,僅僅知道如何使用它是不夠的。只有對其架構原理等有了充分認識,才能在實際運用中使其更好地服務於應用,遇到問題知道怎麼處理,而不是抓瞎抹黑。這篇文章就帶你進入 mongodb 集羣的大門。git
集羣概覽
mongodb 相關的進程分爲三類:github
- mongo 進程 – 該進程是 mongodb 提供的 shell 客戶端進程,經過該客戶端能夠發送命令並操做集羣;
- mongos 進程 – mongodb 的路由進程,負責與客戶端鏈接,轉發客戶端請求到後端集羣,對客戶端屏蔽集羣內部結構;
- mongod 進程 – 提供數據讀寫的 mongodb 實例進程。
類比銀行服務,mongo 進程至關於客戶,mongos 進程是櫃檯服務員,mongod 進程是銀行後臺實際處理業務的人員或者流程。客戶只須要和櫃檯服務員溝通,告知辦什麼業務,櫃檯服務員將業務轉日後臺,後臺實際處理。web
下圖是 mongodb 集羣的通常拓撲結構。算法
如圖,mongodb 集羣的節點分爲三類:mongodb
- mongos 路由節點:處理客戶端的鏈接,扮演存取路由器的角色,將請求分發到正確的數據節點上,對客戶端屏蔽分佈式的概念;
- config 配置節點:配置服務,保存數據結構的元數據,好比每一個分片上的數據範圍,數據塊列表等。配置節點也是 mongod 進程,只是它存儲的數據是集羣相關的元數據;
- shard 分片節點:數據存儲節點,分片節點由若干個副本集組成,每一個副本集存儲部分全體數據,全部副本集的數據組成全體數據,而副本集內部節點存放相同的數據,作數據備份與高可用。
仍是拿銀行業務類比,當客戶辦理保單保存業務時,shell
- 櫃檯服務員接受客戶的保單業務請求(mongos 路由節點接收客戶端的操做請求);
- 櫃檯服務員查詢文件目錄系統查看該保單應該保存到哪一個倉庫(mongos 節點與 config 配置節點通訊,查詢相關操做數據在哪一個分片節點);
- 知道哪一個倉庫後,櫃檯服務員將保單給倉庫管理員,倉庫管理員將保單放到指定倉庫中(mongos 節點將請求發送給數據所在分片節點,分片節點進行讀寫處理)。
mongos 路由服務
mongos 服務相似網關,鏈接 mongodb 集羣與應用程序,對外屏蔽 mongodb 內部結構,應用程序只須要將請求發送給 mongos,而無需關心集羣內部副本分片等信息。數據庫
mongos 自己不保存數據與索引信息,它經過查詢 config 配置服務來獲取,因此能夠考慮將 mongos 與應用程序部署在同一臺服務器上,當服務器宕機時 mongos 也一塊兒失效,防止出現 mongos 閒置。後端
mongos 節點也能夠是單個節點,但爲了高可用,通常部署多個節點。就像櫃檯服務員同樣,能夠有多個,相互之間沒有主備關係,均可以獨立處理業務。bash
須要注意的是,在開啓分片的狀況下,應用程序應該避免直接鏈接分片節點進行數據修改,由於這種狀況下極可能形成數據不一致等嚴重後果,而是經過 mongos 節點來操做。服務器
config 配置服務
config 配置節點本質也是一個副本集,副本集中存放集羣的元數據,如各個分片上的數據塊列表,數據範圍,身份驗證等信息。以下,能夠看到數據庫 config,數據庫中集合保存了集羣的重要元數據。
mongos> use config; switched to db config mongos> show collections; changelog chunks collections databases lockpings locks migrations mongos shards tags transactions version
通常狀況下,用戶不該該直接變動 config 的數據,不然極可能形成嚴重後果。
shard 分片服務
分佈式存儲要解決的是兩個問題:
- 隨着業務不斷髮展,數據量愈來愈大,單機存儲受限於物理條件,必然要經過增長服務器來支持不斷增大的數據。因此分佈式下,不可能所有數據存儲在一個節點上,必然是將數據劃分,部分數據放到這個節點,另外部分數據放到另外的節點上。也就是數據的伸縮性。
- 考慮高可用。若是同一份數據只存在一個節點上,當這個節點發生異常時,數據不可用。這就要求分佈式下同一份數據須要存儲在多個節點上,以達高可用效果。
在 mongodb 集羣中,數據的伸縮性經過分片集來實現,高可用經過副本集來實現。
如圖,所有數據爲1-6,將其劃分爲3部分,1-2爲一個分片,3-4爲一個分片,5-6爲一個分片。每一個分片存儲在不一樣的節點上。而每一個分片有3個副本,組成副本集,每一個副本都是獨立的 mongod 實例。
因此副本集是一個縱向概念,描述的是相同的數據存儲在多個節點上;而分片是一個橫向概念,描述的是全量數據被切成不一樣的片斷,每一個片斷獨立存儲。這個片斷就是分片,而分片經過副本集進行存儲。
副本集
副本集包含三種角色:
- 主節點(Primary)
- 副節點(Secondary)
- 仲裁節點(Arbiter)
一個副本集由一個主節點,多個副節點,0或多個仲裁節點組成。
主節點與副節點是數據節點。主節點提供數據的寫操做,數據寫到主節點後,會經過同步機制同步到副節點上。默認讀操做也由主節點提供,可是能夠手動設置 read preference,優先從副節點讀取。
仲裁節點不是數據節點,不存儲數據,也不提供讀寫操做。仲裁節點是做爲投票者存在,當主節點異常須要進行切換時,仲裁節點有投票權,但沒有被投票權。仲裁節點能夠在資源有限的狀況下,依然支持故障恢復。好比只有2個節點的硬盤資源,在這種狀況下能夠增長一個不佔存儲的仲裁節點,組成「一主一副一仲裁」的副本集架構,當主節點宕掉時,副節點可以自動切換。
節點間經過「心跳」進行溝通,以此知道彼此的狀態。當主節點異常不可用時,從其餘有被投票權的節點中投票選出一個升級爲主節點,繼續保持服務高可用。這裏投票採起「大多數」原則,即須要多於總節點數一半的節點贊成,才能被選舉成主節點。也所以不建議採用偶數個節點組成副本集,由於偶數狀況下,若是發生半數節點網絡隔離,隔離的半數節點達不到「大多數」的要求,沒法選舉產生新的主節點。
經過 rs.status() 能夠查看副本集,參考《教你快速搭建 mongodb 集羣》
分片集
分片就是將所有數據根據必定規則劃分紅沒有交集的數據子集,每一個子集就是一個分片,不一樣分片存放在不一樣節點上。這裏有幾個問題:
- 劃分規則也就是分片策略是什麼?
- 分片數據是如何存放的?
- 數據量愈來愈大,分片如何動態調整?
數據塊 Chunk
chunk 由多個文檔組成,一個分片中包含多個 chunk。chunk 是分片間數據遷移的最小單位。實際上,文檔是經過分片策略計算出應該存儲在哪一個 chunk,而 chunk 存放在分片上。
如圖,假設按照文檔的 x 字段值來進行分片,根據不一樣取值範圍存放在不一樣的數據塊,如25-175在 chunk 3上。
把書比做 mongodb 中的文檔,書櫃比做數據塊,房間比做分片。每本書根據必定規則放到某書櫃上,房間中有不少書櫃。當某個房間的書櫃太多,就須要以書櫃爲單位,遷移到相對比較寬鬆的房間。
chunk 的大小默認爲 64MB,也能夠自定義。chunk 的存在有兩個意義:
- 當某個 chunk 超過大小時,會觸發 chunk 分裂。
- 當分片間的 chunk 數不均衡時,會觸發 chunk 遷移。
chunk 遷移由 mongodb 的平衡器來操做,默認平衡器是開啓的,是運行在後臺的一個進程,也能夠手動關閉。
能夠經過下面命令來查看平衡器狀態:
sh.getBalancerState()
chunk 的大小對集羣的影響:
- 比較小時,chunk 數比較多,數據分佈比較均勻,但會引發頻繁的數據塊分裂與遷移;
- 比較大時,chunk 數比較少,數據容易分散不均勻,遷移時網絡傳輸量大。
因此要自定義數據塊大小時,必定要考慮完備,不然將大大影響集羣與應用程序的性能。
片鍵 Shard Key
mongodb 集羣不會自動將數據進行分片,須要客戶端告知 mongodb 哪些數據須要進行分片,分片的規則是什麼。
某個數據庫啓用分片:
mongos> sh.enableSharding(<database>)
設置集合的分片規則:
mongos> sh.shardCollection(<database.collection>,<key>,<unique>,<options>) # unique 與 options 爲可選參數
例如,將數據庫 mustone 開啓分片,並設置庫中 myuser 集合的文檔根據 _id 字段的散列值來進行劃分分片。
sh.enableSharding("mustone") sh.shardCollection("mustone.myuser",{_id: "hashed"})
這裏劃分規則體如今 上, 定義了分片策略,分片策略由片鍵 Shard Key 與分片算法組成。片鍵就是文檔的某一個字段,也能夠是複合字段。分片算法分爲兩種:
- 基於範圍。如 設置爲 id:1 表示基於字段 id 的升序進行分片,id:-1 表示基於字段 id 的倒序進行分片,字段 id 就是 shard key(片鍵)。當集合中文檔爲空時,設置分片後,會初始化單個 chunk,chunk 的範圍爲(-∞,+∞)。當不斷往其中插入數據到達 chunk 大小上限後,會進行 chunk 分裂與必要遷移。
- 基於hash。如上面的栗子, 設置爲 _id:」hashed」,表示根據字段 _id 的哈希來分片,此時片鍵爲 _id。初始化時會根據分片節點數初始化若干個 chunk,如3個分片節點會初始化6個 chunk,每一個 shard 2個 chunk。
每一個數據庫會分配一個 primary shard,初始化的 chunk 或者沒有開啓分片的集合都默認放在這個 primary shard 上。
分片策略的選擇相當重要,等數據量大了再更改分片策略將會很麻煩。分片策略的原則:
- 均勻分佈原則。分片的目標就是讓數據在各個分片上均勻分佈,數據的存取壓力也分解到各個分片上。好比以自增加的 id 升序爲片鍵,會致使新數據永遠都寫在最後的 chunk 上,且 chunk 分裂與遷移也會落在該 chunk 所在分片上,形成該分片壓力過大。
- 大基數原則。集合的片鍵可能包含的不一樣值的個數,稱爲基數。基數越大,數據就能劃分得更細。基數越小,chunk 的個數就有限。好比性別,只有男女,若是做爲片鍵,最多兩個 chunk,等數據愈來愈大後,便沒法橫向擴展。
- 就近原則。儘量讓一次查詢的數據分佈在同一個 chunk 上,這樣提高磁盤讀取性能。避免毫無心義的隨機片鍵,雖然分佈均勻了,但每次查詢都要跨多個 chunk 才能完成,效率低下。
須要說明的是,mongodb 分片集羣雖然比較完備,可是存在一些限制,如備份相對困難,分片集合沒法作關聯查詢等。因此要根據實際業務來評估,若是副本集已經夠用了,不必定要進行分片存取。