帶你快速瞭解 MongoDB 分散式叢集

木石頭發表於2020-07-21

在分散式應用系統中,mongodb 已經成為 NoSQL 經典資料庫。要想很好的使用 mongodb,僅僅知道如何使用它是不夠的。只有對其架構原理等有了充分認識,才能在實際運用中使其更好地服務於應用,遇到問題知道怎麼處理,而不是抓瞎抹黑。這篇文章就帶你進入 mongodb 叢集的大門。

叢集概覽

mongodb 相關的程式分為三類:

  • mongo 程式 – 該程式是 mongodb 提供的 shell 客戶端程式,通過該客戶端可以傳送命令並操作叢集;
  • mongos 程式 – mongodb 的路由程式,負責與客戶端連線,轉發客戶端請求到後端叢集,對客戶端遮蔽叢集內部結構;
  • mongod 程式 – 提供資料讀寫的 mongodb 例項程式。

類比銀行服務,mongo 程式相當於客戶,mongos 程式是櫃檯服務員,mongod 程式是銀行後臺實際處理業務的人員或者流程。客戶只需要和櫃檯服務員溝通,告知辦什麼業務,櫃檯服務員將業務轉往後臺,後臺實際處理。

下圖是 mongodb 叢集的一般拓撲結構。

帶你快速瞭解 MongoDB 分散式叢集

如圖,mongodb 叢集的節點分為三類:

  • mongos 路由節點:處理客戶端的連線,扮演存取路由器的角色,將請求分發到正確的資料節點上,對客戶端遮蔽分散式的概念;
  • config 配置節點:配置服務,儲存資料結構的後設資料,比如每個分片上的資料範圍,資料塊列表等。配置節點也是 mongod 程式,只是它儲存的資料是叢集相關的後設資料;
  • shard 分片節點:資料儲存節點,分片節點由若干個副本集組成,每個副本集儲存部分全體資料,所有副本集的資料組成全體資料,而副本集內部節點存放相同的資料,做資料備份與高可用。

還是拿銀行業務類比,當客戶辦理保單儲存業務時,

  1. 櫃檯服務員接受客戶的保單業務請求(mongos 路由節點接收客戶端的操作請求);
  2. 櫃檯服務員查詢檔案目錄系統檢視該保單應該儲存到哪個倉庫(mongos 節點與 config 配置節點通訊,查詢相關運算元據在哪個分片節點);
  3. 知道哪個倉庫後,櫃檯服務員將保單給倉庫管理員,倉庫管理員將保單放到指定倉庫中(mongos 節點將請求傳送給資料所在分片節點,分片節點進行讀寫處理)。

mongos 路由服務

mongos 服務類似閘道器,連線 mongodb 叢集與應用程式,對外遮蔽 mongodb 內部結構,應用程式只需要將請求傳送給 mongos,而無需關心叢集內部副本分片等資訊。

mongos 本身不儲存資料與索引資訊,它通過查詢 config 配置服務來獲取,所以可以考慮將 mongos 與應用程式部署在同一臺伺服器上,當伺服器當機時 mongos 也一起失效,防止出現 mongos 閒置。

mongos 節點也可以是單個節點,但為了高可用,一般部署多個節點。就像櫃檯服務員一樣,可以有多個,相互之間沒有主備關係,都可以獨立處理業務。

需要注意的是,在開啟分片的情況下,應用程式應該避免直接連線分片節點進行資料修改,因為這種情況下很可能造成資料不一致等嚴重後果,而是通過 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 例項。

帶你快速瞭解 MongoDB 分散式叢集

所以副本集是一個縱向概念,描述的是相同的資料儲存在多個節點上;而分片是一個橫向概念,描述的是全量資料被切成不同的片段,每個片段獨立儲存。這個片段就是分片,而分片通過副本集進行儲存。

副本集

副本集包含三種角色:

  • 主節點(Primary)
  • 副節點(Secondary)
  • 仲裁節點(Arbiter)

一個副本集由一個主節點,多個副節點,0或多個仲裁節點組成。

主節點與副節點是資料節點。主節點提供資料的寫操作,資料寫到主節點後,會通過同步機制同步到副節點上。預設讀操作也由主節點提供,但是可以手動設定 read preference,優先從副節點讀取。

仲裁節點不是資料節點,不儲存資料,也不提供讀寫操作。仲裁節點是作為投票者存在,當主節點異常需要進行切換時,仲裁節點有投票權,但沒有被投票權。仲裁節點可以在資源有限的情況下,依然支援故障恢復。比如只有2個節點的硬碟資源,在這種情況下可以增加一個不佔儲存的仲裁節點,組成“一主一副一仲裁”的副本集架構,當主節點宕掉時,副節點能夠自動切換。

節點間通過“心跳”進行溝通,以此知道彼此的狀態。當主節點異常不可用時,從其他有被投票權的節點中投票選出一個升級為主節點,繼續保持服務高可用。這裡投票採取“大多數”原則,即需要多於總節點數一半的節點同意,才能被選舉成主節點。也因此不建議採用偶數個節點組成副本集,因為偶數情況下,如果發生半數節點網路隔離,隔離的半數節點達不到“大多數”的要求,無法選舉產生新的主節點。

通過 rs.status() 可以檢視副本集,參考《教你快速搭建 mongodb 叢集》

分片集

分片就是將全部資料根據一定規則劃分成沒有交集的資料子集,每個子集就是一個分片,不同分片存放在不同節點上。這裡有幾個問題:

  • 劃分規則也就是分片策略是什麼?
  • 分片資料是如何存放的?
  • 資料量越來越大,分片如何動態調整?

資料塊 Chunk

chunk 由多個文件組成,一個分片中包含多個 chunk。chunk 是分片間資料遷移的最小單位。實際上,文件是通過分片策略計算出應該儲存在哪個 chunk,而 chunk 存放在分片上。

帶你快速瞭解 MongoDB 分散式叢集

如圖,假設按照文件的 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 上。

分片策略的選擇至關重要,等資料量大了再更改分片策略將會很麻煩。分片策略的原則:

  1. 均勻分佈原則。分片的目標就是讓資料在各個分片上均勻分佈,資料的存取壓力也分解到各個分片上。比如以自增長的 id 升序為片鍵,會導致新資料永遠都寫在最後的 chunk 上,且 chunk 分裂與遷移也會落在該 chunk 所在分片上,造成該分片壓力過大。
  2. 大基數原則。集合的片鍵可能包含的不同值的個數,稱為基數。基數越大,資料就能劃分得更細。基數越小,chunk 的個數就有限。比如性別,只有男女,如果作為片鍵,最多兩個 chunk,等資料越來越大後,便無法橫向擴充套件。
  3. 就近原則。儘可能讓一次查詢的資料分佈在同一個 chunk 上,這樣提升磁碟讀取效能。避免毫無意義的隨機片鍵,雖然分佈均勻了,但每次查詢都要跨多個 chunk 才能完成,效率低下。

需要說明的是,mongodb 分片叢集雖然比較完備,但是存在一些限制,如備份相對困難,分片集合無法做關聯查詢等。所以要根據實際業務來評估,如果副本集已經夠用了,不一定要進行分片存取。

相關文章