精通 MongoDB 7 :復制和分片,來審視 MongoDB 的架構
MongoDB 通過建立在幾個核心架構基礎之上的開發者數據平臺,使您能夠滿足現代應用程序的需求。它讓您能夠以最佳方式進行創新,構建事務性、操作性和分析性應用程序。本章將特別強調兩個關鍵元素:復制和分片,來審視 MongoDB 的架構。
復制是 MongoDB 分布式架構中的關鍵組成部分,確保數據的可訪問性和對故障的韌性。它使您能夠將相同的數據集分布在不同的數據庫服務器上,防止單個服務器的故障。
此外,您還將了解到分片,這是一種將數據分布在多臺機器上的水平擴展策略。隨著應用程序的普及和它們產生的數據量增加,跨機器擴展變得至關重要,以確保足夠的讀寫吞吐量。
本章將涵蓋以下主題:
- 復制和分片如何提高可靠性和可用性。
- MongoDB 中復制和分片的各種方法。
- MongoDB 7.0 中新的分片集群特性。
- 復制與分片的比較。
人們經常將復制與分片混淆。雖然兩者都是數據庫管理中使用的系統集,但它們服務于不同的目的,并因不同的原因而使用。復制是一個數據被復制并存儲在多個位置以確保冗余和可靠性的過程,在數據保護和可訪問性中扮演著至關重要的角色。
另一方面,分片涉及將較大的數據庫劃分為更小、更易于管理的部分,稱為分片。每個分片在單獨的數據庫服務器實例上存儲數據集的一部分。然而,需要注意的是,每個分片還必須實現復制以維護數據的完整性和可用性。
結合分片和復制的目標是確保數據的持久性和高可用性。當分片的服務器實例失敗,且該分片上只有單個數據副本時,可能會導致數據不可用,直到服務器恢復或更換為止。通過在每個分片內采用復制,分片集群可以保持數據可用性,并防止服務器故障引起的任何中斷。這種方法使分片集群能夠在不停機的情況下進行滾動升級,允許平穩且不間斷的系統維護。
接下來的幾節將深入探討復制和分片及其各自的組件。
復制 在 MongoDB 中,副本集是指維護相同數據集的一組 mongod 進程。它們提供冗余和高可用性,是所有生產實施的基礎。通過在多個數據庫服務器上擁有多個數據副本,復制確保了一定程度的容錯能力,保護免受單個數據庫服務器故障的影響。
圖 2.1:一個副本集
主節點處理所有寫操作,并將所有數據集更改記錄在其操作日志(oplog)中。MongoDB 副本集只能有一個主節點。
輔助節點復制主節點的 oplog,并在自己的數據集上實現操作,確保它們鏡像主節點的數據集。如果主節點變得不可訪問,一個合格的輔助節點將啟動選舉成為新的主節點。
通過在多個服務器上存儲數據,復制提高了系統的可靠性。此外,支持滾動升級允許您在不中斷的情況下升級單個服務器的軟件或硬件,確保數據庫的持續可用性。復制顯著提高了讀取密集型應用程序的性能,將讀取負載分布在多個服務器上,以確保快速的數據檢索。
副本集選舉 MongoDB 使用建立在 Raft 共識算法之上的協議來協調副本集選舉,確保分布式系統的數據一致性。該協議包括副本集用于選擇哪個成員將承擔主角色的投票機制。
可以啟動選舉的幾個事件包括:
- 向副本集添加或刪除節點
- 初始化副本集
- 任何輔助節點和主節點之間的心跳故障超過預設的超時持續時間(默認情況下,自托管主機為 10 秒,MongoDB Atlas 為 5 秒)
您的應用程序的連接處理邏輯應該設計得能夠考慮自動故障轉移和隨后的選舉。MongoDB 驅動程序有能力識別主節點的丟失,并自動重試特定的讀取或寫入操作,為選舉提供了額外的內置韌性層。
當主副本變得不可用時,輔助副本會為新的主節點投票。具有最近寫入時間戳的副本更有可能贏得選舉。這種策略最小化了先前的主節點重新加入集時的回滾機會。選舉后,節點進入凍結期,在這期間它們不能啟動另一次選舉。這是為了防止連續、快速的選舉,這可能會破壞系統的穩定性。目前,MongoDB 副本集僅支持稱為協議版本 1(pv1)的協議。
圖 2.2:選舉新的主節點
副本集在選舉成功結束之前無法執行寫操作。然而,如果它們被配置為針對輔助節點,副本集仍然可以處理讀取操作。
在正常情況下,默認副本集配置設置下,集群選舉新主節點所需的平均時間不應超過 12 秒。這個持續時間包括宣布主節點不可訪問所需的時間,以及啟動和完成選舉所需的時間。可以通過更改設置來調整這個時間框架。選舉超時配置選項
settings.electionTimeoutMillis 可以調整這個時間。
成員優先級:一旦建立了穩定的主節點,選舉算法允許最高優先級的輔助節點啟動選舉。成員優先級影響選舉的時機和結果,優先級更高的輔助節點可能會更早地啟動選舉并獲勝。然而,盡管有優先級更高的成員可用,優先級較低的成員可能會短暫地擔任主節點。選舉過程持續進行,直到最高優先級的成員成為主節點。優先級值為 0 的成員不能成為主節點,也不尋求選舉。
由于副本集最多可以容納 50 名成員,其中只有 7 名是投票成員,包括非投票成員可以使集超過 7 名的限制。非投票成員的特征是擁有零票,必須具有優先級為 0。
副本集 oplog:oplog 是一個獨特的固定集合,它維護了一個連續的日志,記錄了所有改變 MongoDB 數據庫中數據的操作。它可以擴展超過其設置的大小限制,以防止刪除大多數提交點。
MongoD 中的寫操作在主節點上執行,隨后記錄在主節點的 oplog 中。輔助成員異步復制并應用這些操作。副本集的所有成員都持有 oplog 的副本,位于 local.oplog.rs 集合中,使它們能夠跟上數據庫的當前狀態。
為了支持復制,副本集的所有成員都相互交換心跳(ping)。任何輔助成員都可以從任何其他成員導入 oplog 條目。oplog 中的每個操作都是冪等的,這意味著無論對目標數據集應用一次還是多次,oplog 操作都會產生相同的結果。
oplog 窗口:Oplog 條目帶有時間戳。oplog 窗口指的是 oplog 中最近和最早時間戳之間的時間間隔。如果輔助節點與主節點失去連接,只有在重新建立連接的時間在 oplog 窗口的持續時間內,它才能使用復制重新同步。MongoDB 允許定義保留 oplog 條目的最小持續時間(以小時為單位),以及特定大小。系統僅在以下條件下才會丟棄 oplog 條目:
- oplog 已填滿到其最大配置容量,并且 oplog 條目已超過基于主機系統時鐘指定的保留期限。
- 沒有指定的最小 oplog 保留期限,MongoDB 默認采用其標準行為。它從最舊的條目開始截斷 oplog,確保 oplog 不超過配置的最大大小。
副本集部署架構:生產系統的標準部署涉及一個三成員的副本集。這些集提供了冗余和對故障的韌性。雖然建議避免不必要的復雜性,但架構最終應由應用程序的需求指導。通常,應遵循以下規則:
- 確保副本集具有奇數個投票成員,以防止網絡分區期間的分裂決策,使更大的部分可以接受寫入。
- 如果您的投票成員集是偶數,請考慮添加另一個攜帶數據的投票成員。如果限制阻止了這一點,請引入一個仲裁者。
- 包含隱藏或延遲的成員以支持專用功能,如備份或報告。
- 為了在數據中心故障的情況下保護您的數據,請確保至少有一個成員位于備用數據中心。
副本集仲裁者:在您擁有一個主節點和一個輔助節點,但預算限制阻止您添加另一個輔助節點的情況下,您可以將仲裁者納入您的副本集。仲裁者參與主節點選舉,但它不持有數據集的副本,也無法成為主節點。仲裁者在選舉中攜帶一票。默認情況下,其優先級設置為0。
隱藏的副本集成員:隱藏成員構成副本集的一個重要部分;然而,它們不能擔任主節點角色,并且在客戶端應用程序中不可見。盡管不可見,隱藏成員仍然能夠參與并投票選舉。隱藏節點可以作為專用副本集成員,用于執行備份操作或運行某些報告查詢。要設置一個隱藏節點并防止成員被提升為主節點,您可以將其優先級設置為0,并將隱藏參數配置為true,如下例所示:
cfg = rs.conf()
cfg.members[n].hidden = true
cfg.members[n].priority = 0
rs.reconfig(cfg)
延遲的副本集成員延遲成員也是隱藏成員,它們以故意的延遲從源oplog復制和應用操作,代表集群的先前狀態。例如,如果當前時間是08:01,并且一個成員的延遲設置為一小時,那么延遲成員上的最新操作不會晚于07:01:
cfg = rs.conf()
cfg.members[n].hidden = true
cfg.members[n].priority = 0
cfg.members[0].secondaryDelaySecs = 3600
rs.reconfig(cfg)
延遲成員充當持續的備份或數據集的實時歷史記錄,為防止人為錯誤提供安全網。例如,它們可以促進從失敗的應用程序更新和操作員錯誤中恢復,如意外刪除數據庫和集合。
寫入關注 寫入關注確定MongoDB在副本集上確認寫入操作的方式。它提供了一種機制,以確保在確認寫入操作之前,數據被寫入指定數量的節點。
寫入關注的組成部分 以下字段可以包含在寫入關注中:
{ w: <value>, j: <boolean>, wtimeout: <number> }
- w:指定必須確認寫入的副本集成員的所需數量。以下w: <value>寫入關注可用:0:無需確認。這提供了最少的持久性,但最高的性能。1:來自主節點的確認。majority:來自大多數副本集成員的確認。從MongoDB 5.0起,默認值;之前,默認值為1。<number>:來自特定數量的副本集成員的確認。
- j:請求確認寫入操作已被寫入磁盤上的日志。如果設置為true,寫入操作將等待日志確認。
- wtimeout:設置寫入關注的時間限制,以毫秒為單位。如果在這段時間內未達到指定的確認級別,寫入操作將返回錯誤。這個錯誤并不意味著寫入不會完成或被回滾;它防止寫入超過指定的閾值阻塞。
從MongoDB 4.4開始,副本集和分片集群都有能力建立全局默認寫入關注。任何未定義特定寫入關注的操作都將自動采用此全局默認寫入關注的設置。使用管理命令setDefaultRWConcern來建立全局默認配置,用于讀取或寫入關注:
db.adminCommand({
setDefaultRWConcern : 1,
defaultReadConcern: { <read concern> },
defaultWriteConcern: { <write concern> },
writeConcern: { <write concern> },
comment: <any>
})
從MongoDB 5.0起,默認寫入關注設置為 { w: "majority" }。然而,當部署包括仲裁者時,有例外:
- 投票多數被計算為投票成員數量的一半加1,向下取整。如果承載數據的投票成員數不超過這個投票多數,則默認寫入關注調整為 { w: 1 }。
- 在任何其他情況下,默認寫入關注保持為 { w: "majority" }。
寫入關注的重要性 寫入關注的選擇可以影響數據的性能和持久性:
- 性能:較低的寫入關注(例如,w: 0)可以通過減少寫入操作的延遲來提高性能。然而,它冒著數據持久性的風險。
- 持久性:較高的寫入關注(例如,w: "majority")通過等待大多數節點的確認來確保數據的持久性。這可能會在寫入操作中引入輕微的延遲。
讀取偏好 在MongoDB中,讀取偏好確定MongoDB客戶端如何將讀取操作路由到副本集的成員。默認情況下,MongoDB將所有讀取操作定向到主成員。然而,通過調整讀取偏好,可以將讀取負載分布到輔助成員,提高系統的整體性能和可用性。
讀取偏好的組成部分 MongoDB支持五種讀取偏好模式:
- primary:所有讀取操作都定向到主成員(默認)。
- primaryPreferred:如果可用,讀取定向到主成員;否則,它們被路由到輔助成員。
- secondary:所有讀取操作都定向到輔助成員。
- secondaryPreferred:如果有任何可用的輔助成員,讀取定向到輔助成員;否則,它們被路由到主成員。
- nearest:讀取操作定向到網絡延遲最低的成員,無論成員的狀態如何。
除了讀取偏好模式,您還可以指定以下選項:
- 標簽集:標簽集允許通過將自定義標簽與副本集成員關聯來定制讀取偏好。然后,客戶端可以將讀取操作定向到具有特定標簽的成員。
- 最大陳舊度:陳舊度指的是從主節點到輔助節點的復制延遲。此選項指定輔助成員在客戶端停止將其用于讀取操作之前可以有多陳舊。
讀取偏好的重要性 讀取偏好的選擇可以影響數據的性能和可用性:
- 性能:將讀取操作分布到輔助成員可以通過減少主節點的負載來提高性能。
- 可用性:如果主節點不可用,如果讀取偏好模式允許,讀取操作仍可由輔助成員提供。
讀取關注 讀取關注決定了從副本集和分片集群中讀取的數據的一致性和隔離屬性。通過調整讀取關注,可以控制讀取操作中數據的可見性,從而確保所需的一致性和隔離級別。
讀取關注的組成部分 MongoDB 支持以下讀取關注級別:
- "local":返回在查詢開始時 MongoDB 實例可用的最新數據,不考慮復制的狀態(默認)。
- "available":返回在查詢時分布式系統中當前可用的數據。這個級別提供最小的延遲,但不保證一致性。
- "majority":返回已被大多數副本集成員確認的數據。這確保了高水平的一致性。
- "linearizable":提供最高級別的一致性,通過返回反映在讀取操作開始之前完成的所有成功的大多數確認寫入的數據。
- "snapshot":返回所有副本集成員的特定時間點的數據。
讀取關注的重要性 讀取關注的選擇可以影響數據的一致性和隔離性:
- 一致性:更高的讀取關注級別(例如,“majority”或“linearizable”)確保返回的數據在副本集成員之間是一致的。
- 隔離性:通過使用“snapshot”讀取關注,可以隔離事務,使其與并發寫入隔離,確保在整個事務中數據的一致視圖。
復制方法 MongoDB Shell (mongosh) 提供了一系列 shell 輔助方法,旨在促進與副本集的交互。它們是管理復制 MongoDB 部署的關鍵工具。讓我們檢查一些管理 MongoDB 復制所需的重要方法:
- rs.add(): 向副本集添加成員
- rs.addArb(): 向副本集添加仲裁者
- rs.conf(): 顯示副本集配置文檔
- rs.initiate(): 初始化一個新的副本集
- rs.printReplicationInfo(): 從主節點的角度總結副本集的狀態
- rs.status(): 提供一份詳細當前副本集狀態的文檔
- rs.reconfig(): 重新配置現有的副本集,覆蓋現有的副本集配置
- rs.stepDown(): 觸發當前主節點變成輔助節點,觸發選舉
有關復制方法的詳細列表,請訪問以下資源:復制方法
除了上述復制方法,您還可以使用復制命令進行特定操作。例如,replSetResizeOplog 改變副本集中變量 oplog 的大小。該命令接受以下字段:
- replSetResizeOplog: 設置為 1。
- size: 指定 oplog 的最大大小,以 MB 為單位。可以設置的最小大小是 990 MB,最大是 1 PB。在 mongosh 中使用 Double() 顯式將大小轉換為雙精度浮點數。
- minRetentionHours: 表示保留 oplog 條目的最小小時數,小數值表示小時的分數(例如,1.5 表示 1 小時和 30 分鐘)。該值必須等于或大于 0。值為 0 意味著 mongod 應該從最舊的條目開始截斷 oplog,以維持配置的最大 oplog 大小。
db.adminCommand({
replSetResizeOplog: <int>,
size: <double>,
minRetentionHours: <double>
})
要了解更多關于復制命令的信息,請訪問以下資源:復制命令
分片 MongoDB 通過分片支持水平擴展。分片涉及將數據分布在眾多進程中,并在管理和組織大規模數據中發揮重要作用。這種方法將較大的數據庫劃分為更小、更易管理的組件,稱為分片。每個分片存儲在單獨的數據庫服務器實例上,這分散了負載并提供了有效的數據管理方法。此外,該技術允許創建分布式數據庫以支持地理分布的應用程序,實現強制數據在特定區域內居住的策略。
為什么需要分片? 考慮一個數據快速擴展且數據庫接近其最大容量的場景。這種情況可能會出現許多挑戰。通常,最緊迫的問題是性能惡化。隨著數據庫的增長,查詢和檢索數據所需的時間可能會顯著增加。這種減速對用戶體驗產生負面影響,使應用程序看起來緩慢或無響應。
另一個潛在障礙是存儲限制。大多數系統都有一個它們可以有效管理的數據量上限。如果您的數據增長超過了系統的存儲能力,它可能導致系統故障。在管理系統增長方面,有兩種主要策略——垂直擴展和水平擴展:
- 垂直擴展增強單個服務器的容量,例如,通過升級 CPU、增加 RAM 或擴展存儲空間。然而,技術限制可能阻止單個機器處理某些工作負載,因為存在嚴格的上限。因此,垂直擴展有實際限制。
- 水平擴展涉及將系統的數據和工作負載分散在多個服務器上,增加了所需的服務器數量。每臺機器只處理總工作負載的一部分,這可能比一臺強大的服務器更有效。雖然這種方法可能比投資昂貴的硬件更具成本效益,但它增加了基礎設施和系統維護的復雜性。
分片集群的關鍵元素 MongoDB 分片集群由以下關鍵元素組成:
- Shard:這是一個存儲集群中部分數據的副本集。集群可以有 1 到 n 個分片,每個分片包含整體數據的不同子集。MongoDB Atlas 根據集群層級提供不同的存儲容量:M10-M40 為 4 TB,M50-M60 為 8 TB,M80+ 為 14 TB。
- mongos:作為客戶端應用程序的查詢路由器,有效管理讀寫操作。它將客戶端請求引導到適當的分片,并將來自多個分片的結果匯總到一個連貫的客戶端響應中。客戶端與 mongos 實例建立連接,而不是直接與單個分片建立連接。建議在生產部署中運行多個 mongos 實例以確保高可用性。
- Config servers:作為副本集運作,充當專門數據庫的角色,作為分片元數據的主要存儲庫。這些元數據包括分片數據的狀態和結構,包括分片集合的列表和路由詳細信息等重要信息。它在實現集群內高效的數據管理和查詢路由中起著至關重要的作用。
圖 2.3 展示了分片集群內組件的交互。
圖 2.3:一個分片集群
在 MongoDB 中,數據分片發生在集合級別,這意味著集合的數據分布在集群內的多個分片中。需要注意的是,分片集群中的每個數據庫都有自己的主分片,負責存儲該數據庫內所有未分片的集合。值得一提的是,分片集群中的主分片概念與副本集中的主節點不同;它們服務于不同的目的,彼此之間沒有關聯。
創建新數據庫時,由 mongos 進程選擇主分片。它選擇集群中數據量最少的分片。listDatabases 命令返回的 totalSize 字段被用作選擇標準的一個因素。
使用 movePrimary 命令可以在初次分配后移動主分片。movePrimary 命令最初改變集群元數據中的主分片,然后遷移所有未分片的集合到指定的分片。
微分片 在某些情況下,MongoDB 提供了一種更先進的分片配置,稱為微分片。與傳統的將每個分片分配給專用機器的方法不同,微分片允許通過在單個主機上共置多個分片來實現更細粒度的控制和資源優化。
微分片可以是一種有效的策略,特別是當處理每個分片的較小數據大小時。通過在單個主機上合并多個分片,可以最大化資源利用率,從而提高硬件效率。
圖 2.4:微分片配置
注意: 這種方法需要仔細的資源管理,以防止同一主機上運行的不同MongoDB進程之間發生沖突。每個MongoDB進程都需要分配足夠的資源以有效運行。在實施微分片策略時,正確配置每個MongoDB進程的緩存大小至關重要。可以通過設置
storage.wiredTiger.engineConfig.cacheSizeGB來調整MongoDB中WiredTiger存儲引擎的緩存大小。這個緩存應該足夠大,以容納每個MongoDB進程的工作集。如果緩存缺乏必要的空間來加載更多數據,WiredTiger將從緩存中移除頁面以騰出空間。
默認情況下,storage.wiredTiger.engineConfig.cacheSizeGB 配置為總RAM的50%,少于1GB。然而,當在同一臺機器上運行多個MongoDB進程時,您應該調整此設置,以確保所有MongoDB進程的總內存使用量不超過可用RAM。
例如, 如果您在一臺擁有16GB RAM的機器上運行兩個MongoDB進程,您可以為每個進程將
storage.wiredTiger.engineConfig.cacheSizeGB設置為25%(或4GB),而不是默認的50%。這可以防止MongoDB進程集體消耗超過總可用RAM的內存,為操作系統和其他應用程序留出足夠的內存。
分片的優勢: 分片提供了幾個好處,例如:
- 增強的讀寫速度: 將數據集分布在多個分片上允許并行處理。每個額外的分片都會增加您的總吞吐量,并且良好分布的數據集可以提高查詢性能。多個分片可以同時處理查詢,加快響應時間。
- 擴展的存儲容量: 增加分片的數量可以提高您的總存儲量。如果一個分片可以存儲4TB的數據,每個額外的分片就會增加另外4TB。這允許幾乎無限的存儲容量,隨著您數據需求的增長,可以實現可擴展性。
- 數據本地性: 區域分片允許數據庫分布在不同的地理位置,非常適合分布式應用。策略可以將數據限制在特定區域內,每個區域持有一個或多個分片,提高數據管理的效率和適應性。在區域分片中,不同的分片鍵值范圍可以與每個區域相關聯,根據地理位置的相關性實現更快、更精確的數據訪問。
數據分布: 在分片的MongoDB集群中,正確的數據分布對于平衡工作負載、提高性能和增強可擴展性至關重要,它可以防止瓶頸并確保資源的有效利用。
分片鍵: MongoDB在集合級別執行分片,允許您選擇要分片的集合。選擇分片鍵對于分片集群至關重要,因為不當的選擇可能導致數據分布不均、分片之間負載分配不均衡和查詢性能下降。這些問題可能會使某些分片過載,而其他分片則未充分利用,降低系統效率。在極端情況下,一個被稱為熱分片的單個分片可能會成為瓶頸,嚴重影響集群性能。因此,選擇正確的分片鍵對于分片環境中的最優性能至關重要。
通過使用由一個或多個文檔字段組成的分片鍵,MongoDB將集合的文檔分布在各個分片上。數據通過分解分片鍵值的范圍被劃分為不重疊的塊。目標是在集群的分片中均勻分布這些塊,確保有效的分布。
在MongoDB的近期版本中,對分片功能進行了顯著修改:
- 從MongoDB 4.4開始,分片集合中的文檔可以省略分片鍵字段。在這種情況下,這些缺失的分片鍵字段在文檔分布到分片時被視為空值。
- 從MongoDB 4.4開始,您可以通過向現有的分片鍵添加后綴字段或字段來增強分片鍵。
- 在MongoDB 5.0中,您可以通過修改其分片鍵來重新分片集合。
分片策略: MongoDB支持兩種分片策略,用于在分片集群中分布數據:
- 范圍分片: 涉及根據分片鍵值將數據分割成連續的序列。具有相似分片鍵值的文檔可能位于同一個分片或塊中,有助于有效查詢連續序列。然而,不當的分片鍵選擇可能會影響讀寫性能。范圍分片是默認方法,除非配置了哈希分片或區域的選項。當分片鍵表現出以下特征時,范圍分片最有效:
- 高基數:分片鍵的基數為平衡器可以產生的塊的數量設定了上限。只要可能,選擇具有高基數的分片鍵。
- 低頻率:分片鍵的頻率表示數據中特定分片鍵值的復發率。當大部分文檔只持有一小部分潛在的分片鍵值時,容納這些文檔的塊可能會變成集群瓶頸。
- 非單調變化值:基于逐漸增加或減少的值的分片鍵更有可能將插入操作導向集群內的單個塊。
- 哈希分片: 通過計算分片鍵字段值的哈希值,然后根據哈希分片鍵值分配每個塊的范圍。盡管一系列分片鍵在值上看起來可能很接近,但它們的哈希值不太可能落在同一個塊中。值得注意的是,哈希分片對于執行基于范圍的操作并不高效。哈希鍵適用于具有單調變化字段的分片鍵,例如ObjectId值或時間戳。一個典型的例子是默認的_id字段,假設它只包含ObjectId值。
MongoDB 4.4引入了使用單個哈希字段構建復合索引的能力。要創建這樣的復合哈希索引,在索引創建期間,將任何單個索引鍵的值指定為哈希。復合哈希索引為復合索引中的單個字段計算哈希值;這個值連同索引中的其他字段一起用作您的分片鍵。以下是一個示例:
db.planets.createIndex({ "name":1, "_id": "hashed" })
sh.shardCollection("sample_guides.planets", { "name": 1, "_id": "hashed" })
上述命令在name字段上創建了一個升序的復合哈希索引,_id作為哈希字段,并分片了planets集合。
分片鍵索引: 要分片一個已填充的集合,該集合需要有一個以分片鍵開頭的索引。然而,在分片一個空集合時,如果集合還沒有為指定的分片鍵準備合適的索引,MongoDB會自動生成所需的支持索引。
塊: MongoDB將分片數據組織成不同的部分,稱為塊或范圍(從MongoDB 5.2開始,默認大小為128MB;在此版本之前為64MB)。每個塊由分片鍵定義的包含下限和不包含上限所特征。這些塊包含特定分片內一系列不間斷的分片鍵值。從MongoDB 6.1開始,塊不再自動分割。相反,塊僅在跨分片移動時分割。
注意: 在MongoDB 6.1之前,當塊大小超過最大塊大小時,塊會被自動分割器分割。
平衡器和均勻塊分布: 從MongoDB 6.0.3開始,分片集群中的數據分布基于數據大小而非塊的數量。為了確保塊的平衡分布,一個名為平衡器的后臺進程跟蹤每個分片上每個分片集合的數據量,并在不同分片之間移動塊。當特定分片上的分片集合的數據量達到特定的遷移限制時,平衡器嘗試重新分配跨分片的數據,目標是在遵守區域的同時實現每個分片上的數據均勻分布。默認情況下,這個平衡過程是持續活躍的。
平衡器在配置服務器副本集(CSRS)的主節點上運行。對于分片集群的平衡過程對用戶和應用層是完全透明的,盡管在執行期間可能會觀察到輕微的性能影響。
為了減輕這種影響,平衡器嘗試:
- 限制一個分片一次只參與一個遷移。換句話說,一個分片不能同時參與多個數據遷移。平衡器會一個接一個地執行范圍遷移。
- MongoDB可以執行并行數據遷移;但是,一個分片在任何時刻僅限于參與一個遷移。在由n個分片組成的分片集群中,MongoDB可以執行多達n/2(向下取整)的并發遷移。
當特定分片集合的最重載分片與最輕載分片之間的數據差異達到遷移閾值時,開始一輪平衡。當分片之間的數據差異(對于該特定集合)小于集合的已建立范圍大小的三倍時,認為集合是平衡的。如果范圍大小設置為默認的128MB,則如果某個集合的任何兩個分片之間的數據大小差異至少為384MB,將觸發遷移。
可以為平衡器的操作設置特定時間框架,以防止其干擾生產流量。這稱為平衡窗口或平衡器窗口。
塊管理: 在某些情況下,需要手動管理塊。
- 巨型塊(Jumbo chunks): 在MongoDB中,超過指定范圍大小并且不能由MongoDB自動分割的塊被標記為巨型塊。雖然MongoDB自動管理塊分割和平衡,但在某些情況下需要手動干預來管理巨型塊。清除巨型塊標志的首選方法是嘗試分割塊。如果塊可以被分割,MongoDB在成功分割塊后將移除標志。可以使用sh.splitAt()或sh.splitFind()方法來分割巨型塊。
- 不可分割的塊(Indivisible chunks): 在某些情況下,MongoDB無法分割不再屬于巨型塊的塊,例如,一個包含單個分片鍵值的范圍的塊。在這種情況下,您無法分割塊以清除標志。在這些情況下,您可以修改分片鍵(您可以重新分片集合)以使塊可分割,或者您可以手動移除標志。
要手動清除標志,在管理數據庫中,發起clearJumboFlag命令,提供分片集合的命名空間和以下任一選項:
- 巨型塊的邊界:
db.adminCommand({
clearJumboFlag: "sample.customers",
bounds: [{ "x": 5 }, { "x": 6 }]
});
- 落在巨型塊內的具有分片鍵和值的find文檔:
db.adminCommand({
clearJumboFlag: "sample.customers",
find: { "x": 5 }
});
如果集合使用哈希分片鍵,請避免在使用clearJumboFlag時使用find字段。對于具有哈希分片鍵的集合,使用邊界字段更為合適。
- 預分割范圍(Pre-splitting the ranges): 在大多數情況下,分片集群將自動生成、分割和分配數據范圍,無需手動監督。然而,在某些情況下,MongoDB無法產生足夠的范圍,或者以能夠跟上必要吞吐量的速度分散數據。
如果您即將向新的分片集群加載大量數據,預分割可以幫助從一開始就將數據均勻分布在各個分片上。這可以防止單個分片成為瓶頸。
注意: 建議僅對當前為空的集合預分割范圍。嘗試為已經包含數據的集合手動分割范圍可能導致不規則的范圍邊界和大小,并且也可能導致平衡行為運行效率低下或根本不運行。
要手動分割空范圍,可以使用分割命令。該命令將分片集群中的一個塊分割成兩個單獨的塊。分割命令必須在管理數據庫中執行。
讓我們來看一個例子。假設您有一個名為myapp.products的集合,您希望根據指定的分割點將范圍預分割為四個不同的價格類別。分片鍵設置在價格字段上。可以使用以下代碼在mongosh中實現:
// 根據指定的分割點將集合分割為塊
var splitPoints = [20, 50, 100];
// 注意:'split'命令可以采用middle、find或bounds作為選項。
// 在這個例子中,您使用middle選項來指定分割點。
for(var i = 0; i < splitPoints.length; i++) {
db.adminCommand({
split: "myapp.products",
middle: {
price: splitPoints[i]
}
});
}
這段代碼將數據分為四個價格類別:
- 價格低于20美元的產品
- 價格在20至50美元之間的產品
- 價格在50至100美元之間的產品
- 價格超過100美元的產品
從MongoDB 6.0開始,平衡器根據數據大小在分片之間分配數據。僅僅分割范圍可能無法確保數據在分片之間均勻分布。因此,您必須手動移動塊以確保平衡分布:
// 您希望將塊移動到的分片列表
var shards = ["shard0000", "shard0001", "shard0002", "shard0003"];
for (var i = 0; i < splitPoints.length; i++) {
var lowerBound = { price: MinKey };
if (i > 0) {
lowerBound = { price: splitPoints[i-1] };
}
var upperBound = { price: MaxKey };
if (i < splitPoints.length - 1) {
upperBound = { price: splitPoints[i] };
}
// 手動將塊移動到所需的分片
db.adminCommand({
moveChunk: "myapp.products",
find: lowerBound,
to: shards[i],
bounds: [lowerBound, upperBound]
});
}
這種方法有助于從一開始就在分片之間均勻分配數據。
查詢分片數據: 查詢MongoDB分片集群中的數據與在單服務器部署或副本集中查詢數據不同。您不是連接到單個服務器或副本集的主節點,而是連接到mongos,它充當查詢路由器并決定向哪個分片請求數據。在下一節中,您將探索mongos路由器的工作原理。
mongos路由器: mongos實例提供對您的MongoDB集群的唯一接口和入口點。應用程序連接到mongos而不是直接連接到底層的分片。mongos執行查詢,收集結果,并將它們傳遞給應用程序。mongos進程不持有任何持久狀態,通常也不會使用大量的系統資源。它作為請求的代理。當查詢進來時,mongos檢查它,決定需要在哪些分片上執行查詢,并在所有目標分片上建立游標。
find: 如果查詢包含分片鍵或分片鍵的前綴,mongos將執行針對性操作,只查詢包含您要查找的鍵的分片。
假設用戶集合的復合分片鍵是_id, email, country:
db.user.find({ _id: 1 })
db.user.find({ _id: 1, "email": "packt@packt.com" })
db.user.find({ _id: 1, "email": "packt@packt.com" , "country": "UK" })
這些查詢包含前綴(前兩個查詢的情況)或完整的分片鍵。另一方面,對{ email, country }或{ country }的查詢將無法定位正確的分片,導致廣播操作。廣播操作是任何不包含分片鍵或分片鍵前綴的操作,導致mongos查詢每個分片。它們也稱為散布-收集操作或扇出查詢。
sort(), limit(), 和 skip(): 如果您想對結果進行排序,您有兩種選擇:
- 如果您在排序條件中使用分片鍵,那么mongos可以確定它必須以何種順序查詢一個或多個分片。這導致了一個高效和有針對性的操作。
- 如果您在排序條件中不使用分片鍵,那么就像沒有任何排序條件的查詢一樣,它將是一個扇出查詢。當您在不使用分片鍵的情況下對結果進行排序時,主分片在將排序后的結果集傳遞給mongos之前,會在本地執行分布式合并排序。
查詢的limit在每個單獨的分片上強制執行,然后在mongos級別再次執行,因為可能有來自多個分片的結果。另一方面,skip運算符不能傳遞給單個分片,并將由mongos在檢索所有結果后本地應用。
如果您組合使用skip()和limit()游標方法,mongos將通過將兩個值傳遞給單個分片來優化查詢。這在分頁等情況下特別有用。如果您在沒有sort()的情況下進行查詢,并且結果來自多個分片,mongos將為結果在分片之間進行輪詢。
更新和刪除 從MongoDB 7.0開始,文檔修改操作(如更新和刪除)的處理流程已被簡化。如果修改操作中的find()部分包含分片鍵,mongos可以繼續將查詢路由到適當的分片。然而,如果find部分中缺少分片鍵,它不再像早期版本那樣觸發扇出操作。
對于updateOne()、deleteOne()或findAndModify()操作,不再嚴格要求包含分片鍵或_id值。可以使用任何字段來匹配文檔,類似于非分片集合。然而,使用分片鍵進行這些操作仍然更有效率,因為它允許針對性查詢。
例如,在MongoDB 6.0中,您必須傳遞一個分片鍵來執行updateOne():
db.cities.updateOne({ "city": "New York City" }, { $set: { "population" : 8500000 }});
在這個例子中,city代表分片鍵。然而,在MongoDB 7.0中,您可以在不包含過濾條件中的分片鍵的情況下運行updateOne()操作:
db.cities.updateOne({ "population" : 293200 }, { $set: { "areaSize" : 211 }});
表2.3 總結了MongoDB 7.0中可用于分片的操作:
操作 | 描述 |
insert() | 必須包含分片鍵 |
update() | 可以包含分片鍵,但不是必須的 |
查詢帶分片鍵 | 針對性操作 |
查詢不帶分片鍵 | 后臺進行散布-收集操作 |
索引排序,查詢帶分片鍵 | 針對性操作 |
索引排序,查詢不帶分片鍵 | 分布式排序合并 |
updateOne(), replaceOne(), deleteOne() | 可以使用任何字段進行匹配,但使用分片鍵更有效 |
表2.1: 分片操作
保護性讀取 從MongoDB 4.4開始,mongos實例可以對使用非主讀取偏好的讀取操作進行保護性讀取。mongos實例將每個查詢分片的讀取操作定向到副本集中的兩個成員,然后返回每個分片的第一個響應結果。
支持保護性讀取的操作包括:collStats、count、dataSize、dbStats、distinct、filemd5、find、listCollections、listIndexes和planCacheListFilters。
分片方法 為了管理數據的分布,MongoDB提供了一組輔助方法。這些方法用于啟用分片、定義數據應如何分布以及監控分片的狀態。它們是管理分片MongoDB部署的重要工具,允許在多臺機器上高效地擴展數據。以下是各種mongosh shell輔助方法的列表:
- sh.shardCollection()對于在MongoDB中設置分片至關重要。一旦集合被分片,MongoDB不提供方法來取消分片。但是,如果需要,稍后可以更改分片鍵。
- db.collection.getShardDistribution()為特定分片集合提供了數據在分片上分布的詳細分解。以下是此命令顯示的輸出信息摘要:
表2.2:db.collection.getShardDistribution()的輸出信息。
輸出 | 類型 | 描述 |
<shard-x> | 字符串 | 保存分片名稱 |
<host-x> | 字符串 | 保存主機名稱(s) |
<size-x> | 數字 | 包括數據大小,包括度量單位 |
<count-x> | 數字 | 報告分片中的文檔數量 |
<number of chunks-x> | 數字 | 報告分片中的塊數量 |
<size-x>/<number of chunks-x> | 計算值 | 反映分片的每個塊的估計數據大小,包括度量單位 |
<count-x>/<number of chunks-x> | 計算值 | 反映分片的每個塊的估計文檔數量 |
<stats.size> | 數字 | 報告分片集合中數據的總大小,包括度量單位 |
<stats.count> | 數字 | 報告分片集合中的總文檔數量 |
<calc total chunks> | 計算值 | 報告所有分片的塊數量 |
<estDataPercent-x> | 計算值 | 表示每個分片的估計數據大小占集合總數據大小的百分比 |
<estDocPercent-x> | 計算值 | 反映每個分片的估計文檔數量占集合總文檔數量的百分比 |
- sh.status()提供關于分片集群的信息。可以通過verbose參數調整輸出的詳細程度,允許提供高級概覽或詳細報告。此命令提供的信息包括分片版本、每個分片的詳細信息、活動mongos實例的狀態、自動分割的狀態、平衡器的狀態以及數據庫和分片集合的信息。
- 從MongoDB 5.0開始,sh.reshardCollection()方法使得修改集合的分片鍵以改變數據在集群中的分布成為可能。然而,在開始重新分片操作之前,請確保您的應用程序能夠承受重新分片期間可能增加延遲的兩秒寫入阻塞。如果這是不可接受的,請考慮調整您的分片鍵。您的數據庫服務器還應該擁有必要的資源:
- 存儲:確保每個分片上可用的存儲至少是將要重新分片的集合大小的2.2倍。例如,對于1TB的集合,每個分片至少應有2.2TB的可用存儲。
- 計算每個分片所需的存儲,使用以下公式:每個分片所需的可用存儲 = (集合存儲大小 * 2.2) / 集合將分布的分片數量
- 假設一個集合占用2TB的存儲,并且分布在四個分片上,每個分片至少需要1.1TB的可用存儲。
- 示例計算:(2TB * 2.2) / 4個分片 = 1.1TB/分片
- I/O:您的I/O容量不應超過50%。
- CPU使用率:保持CPU使用率低于80%。
- sh.getBalancerState()返回一個布爾值,指示平衡器當前是否處于活動狀態。
- $shardedDataDistribution,在聚合階段,返回有關分片集群中數據分布的信息。
MongoDB 7.0中的新分片集群特性
MongoDB 7.0繼續簡化了分片集群的管理和理解,無論是對于運維還是開發用例。這個版本提供了額外的洞察力,幫助為初始和未來的分片鍵選擇做出最佳決策。此外,從MongoDB 7.0開始,開發者在使用這些命令時可以在分片和非分片集群上體驗到一致的接口,同時在必要時仍然保留優化性能的選項。讓我們看看MongoDB 7.0中引入的新分片特性。
分片鍵顧問命令
由于復雜的數據模式和權衡,選擇分片鍵是一項復雜的任務。然而,MongoDB 7.0中的新特性旨在簡化這項任務:
- analyzeShardKey 提供了評估候選分片鍵與現有數據的能力。MongoDB 7.0中的分片鍵分析提供了評估分片鍵適用性的指標,包括其唯一性(基數)、頻率,以及它是否穩定增加或減少(單調性)。此命令可以在副本集以及分片集群上運行。
- configureQueryAnalyzer 提供了跨集群查詢路由模式的指標,幫助發現負載不均衡和熱分片。它返回一個文檔,包含描述舊配置的字段(如果存在的話),以及描述新配置的字段。有了這兩個命令,可以有信心地設置初始分片鍵,或為實時重新分片做好準備,背后有確保對所選分片鍵有信心的必要數據。
- mergeAllChunksOnShard 命令旨在整合或合并特定分片擁有的給定集合的所有可合并塊。它通過查找特定分片上的所有可合并塊,然后將它們合并,有助于解決分片維護操作期間性能下降的問題。這可以通過減少需要在分片維護操作期間查詢的塊數量,來幫助減少碎片化并提高性能。
AutoMerger
AutoMerger是一個自動合并符合某些可合并要求的塊的特性。此過程作為平衡操作的一部分在后臺運行。除非被禁用,否則AutoMerger在首次啟用平衡器時啟動,并在每次運行后暫停固定間隔(autoMergerIntervalSecs)。它在啟用時的每個間隔執行自動合并。對于每個集合,它確保連續合并之間的最小延遲(autoMergerThrottlingMS)。如果定義了平衡窗口,AutoMerger僅在該窗口內運行。在滿足以下所有條件的情況下,同一集合中的兩個或多個連續塊是可合并的:
- 屬于同一個分片。
- 不是巨型塊(不能參與遷移)。
- 具有可以安全清除的遷移歷史,而不會干擾事務或快照讀取。這意味著塊的最后一次遷移至少發生在minSnapshotHistoryWindowInSeconds和transactionLifetimeLimitSeconds值之前。
您可以使用以下方法來控制AutoMerger的行為:
- sh.startAutoMerger()
- sh.stopAutoMerger()
- sh.enableAutoMerger()
- sh.disableAutoMerger()
無需分片鍵的命令支持
在分片和非分片集群上使用updateOne、deleteOne和findAndModify等命令的使用將是一致的,同時在需要時提供優化性能的選項。
總結
MongoDB中的復制是一個通過在多個服務器間同步數據來提供冗余和提高數據可用性的過程。這是通過副本集實現的,副本集是一組維護相同數據集的MongoDB服務器。在副本集中,一個節點作為主節點,接收所有寫操作,而輔助節點將主節點的操作復制到它們自己的數據集中。這種結構為故障轉移和恢復提供了一個健壯的系統。如果主節點失敗,輔助節點之間的選舉將確定一個新的主節點,允許客戶端操作的連續性。
MongoDB中的分片是一種將數據分割和分布到多個服務器或分片上的方法。每個分片是一個獨立的副本集,并且分片共同組成一個單一的邏輯數據庫——分片集群。這種方法用于支持具有非常大數據集和高吞吐量操作的部署,有效地解決可擴展性問題。