深度解讀 MongoDB 最全面的增強版本 4.4 新特性

程式碼派就是我發表於2020-11-25

>>釋出會傳送門

瞭解產品詳情

MongoDB 在今年正式釋出了新的 4.4 大版本,這次的釋出包含眾多的增強 Feature,可以稱之為是一個維護性的版本,而且是一個使用者期待已久的維護性版本,MongoDB 官方也把這次釋出稱為「User-Driven Engineering」,說明新版本主要是針對使用者呼聲最高的一些痛點,重點進行了改進。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

而阿里雲作為 MongoDB 官方的全球戰略合作伙伴,也即將全網獨家上線 4.4 新版本,下面就由阿里雲 MongoDB 團隊的工程師針對一些使用者關注度比較高的 Feature ,進行深度解讀。

可用性和容錯性增強

Mirrored Reads

在服務阿里雲 MongoDB 客戶的過程中,筆者觀察到有很多的客戶雖然購買的是三節點的副本集,但是實際在使用過程中讀寫都是在 Primary 節點,其中一個可見的 Secondary 並未承載任何的讀流量。

那麼在偶爾的當機切換之後,客戶能明顯的感受到業務的訪問延遲會有抖動,經過一段時間後才會恢復到之前的水平,抖動原因就在於,新選舉出的主庫之前從未提供過讀服務,並不瞭解業務的訪問特徵,沒有針對性的對資料做快取,所以在突然提供服務後,讀操作會出現大量的「Cache Miss」,需要從磁碟重新載入資料,造成訪問延遲上升。在大記憶體例項的情況下,這個問題更為明顯。

在 4.4 中,MongoDB 針對上述問題實現了「Mirrored Reads」功能,即,主庫會按一定的比例把讀流量複製到備庫上執行,來幫助備庫預熱快取。這個執行是一個「Fire and Forgot」的行為,不會對主庫的效能產生任何實質性的影響,但是備庫負載會有一定程度的上升。

流量複製的比例是可動態配置的,透過 mirrorReads 引數設定,預設複製 1% 的流量。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

此外,可以透過db.serverStatus( { mirroredReads: 1 } )來檢視 Mirrored Reads 相關的統計資訊,
9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

Resumable Initial Sync

在 4.4 之前的版本中,如果備庫在做全量同步,出現網路抖動而導致連線閃斷,那麼備庫是需要重頭開始全量同步的,導致之前的工作全部白費,這個情況在資料量比較大時,比如 TB 級別,更加讓人崩潰。

而在 4.4 中,MongoDB 提供了,因網路異常導致全量同步中斷情況下,從中斷位置恢復全量同步的能力。在嘗試恢復一段時間後,如果仍然不成功,那麼會重新選擇一個同步源進行新的全量同步。這個嘗試的超時時間預設是 24 小時,可以透過 replication.initialSyncTransientErrorRetryPeriodSeconds 在程式啟動時更改。

需要注意的是,對於全量同步過程中遇到的非網路異常導致的中斷,仍然需要重新發起全量同步。

Time-Based Oplog Retention

我們知道,MongoDB 中的 Oplog 集合記錄了所有的資料變更操作,除了用於複製,還可用於增量備份,資料遷移,資料訂閱等場景,是 MongoDB 資料生態的重要基礎設施。

Oplog 是作為 Capped Collection 來實現的,雖然從 3.6 開始,MongoDB 支援透過 replSetResizeOplog 命令動態修改 Oplog 集合的大小,但是大小往往並不能準確反映下游對 Oplog 增量資料的需求,考慮如下場景,

• 計劃在凌晨的 2 - 4 點對某個 Secondary 節點進行停機維護,應避免上游 Oplog 被清理而觸發全量同步。

• 下游的資料訂閱元件可能會因為一些異常情況而停止服務,但是最慢會在 3 個小時之內恢復服務並繼續進行增量拉取,也應當避免上游的增量缺失。

所以,在真實的應用場景下,很多時候是需要保留最近一個時間段內的 Oplog,這個時間段內產生多少的 Oplog 往往是很難確定的。

在 4.4 中,MongoDB 支援 storage.oplogMinRetentionHours 引數定義最少保留的 Oplog 時長,也可以透過 replSetResizeOplog 命令線上修改這個值,如下,
9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

擴充套件性和效能增強

Hidden Indexes

Hidden Index 是阿里雲 MongoDB 和 MongoDB 官方達成戰略合作後共建的一個 Feature。我們都知道資料庫維護太多的索引會導致寫效能的下降,但是往往業務上的複雜性決定了運維 MongoDB 的同學不敢輕易的刪除一個潛在的低效率索引,擔心錯誤的刪除會帶來業務效能的抖動,而重建索引往往代價也非常大。

Hidden Index 正是為了解決 DBA 同學面臨的上述困境,它支援透過 collMod 命令對現有的索引進行隱藏,保證後續的 Query 都不會利用到該索引,在觀察一段時間後,確定業務沒有異常,可以放心的刪除該索引。
9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

需要注意的是,索引被隱藏之後只是對 MongoDB 的執行計劃器不可見,並不會改變索引本身的一些特殊行為,比如唯一鍵約束,TTL 淘汰等。

索引在隱藏期間,如果新的寫入,也是會被更新的,所以也可以透過取消隱藏,很方便的讓索引立刻變的可用。

Refinable Shard Keys

當使用 MongoDB 分片叢集時,相信大家都知道選擇一個好的 Shard key 是多麼的重要,因為它決定了分片叢集在指定的 Workload 下是否有良好的擴充套件性。但是在實際使用 MongoDB 的過程中,即使我們事先仔細斟酌了要選擇的 Shard Key,也會因為 Workload 的變化而導致出現 Jumbo Chunk,或者業務流量都打向單一 Shard 的情況。

在 4.0 及之前的版本中,集合選定的 Shard Key 及其對應的 Value 都是不能更改的,在 4.2 版本,雖然可以修改 Shard Key 的 Value,但是資料的跨 Shard 遷移以及基於分散式事務的實現機制導致效能開銷很大,而且並不能完全解決 Jumbo Chunk 或訪問熱點的問題。比如,現在有一個訂單表,Shard Key 為 {customer_id:1},在業務初期每個客戶不會有很多的訂單,這樣的 Shard Key 完全可以滿足需求,但是隨著業務的發展,某個大客戶累積的訂單越來越多,進而對這個客戶訂單的訪問成為某個單一 Shard 的熱點,由於訂單和customer_id天然的關聯關係,修改customer_id並不能改善訪問不均的情況。

針對上述類似場景,在 4.4 中,你可以透過 refineCollectionShardKey 命令給現有的 Shard Key 增加一個或多個 Suffix Field 來改善現有的文件在 Chunk 上的分佈問題。比如,在上面描述的訂單業務場景中,透過refineCollectionShardKey命令把 Shard key 更改為{customer_id:1, order_id:1},即可避免單一 Shard 上的訪問熱點問題。

需要了解的是,refineCollectionShardKey 命令效能開銷非常低,只是更改 Config Server 上的後設資料,不需要任何形式的資料遷移(因為單純的新增 Suffix 並不會改變資料在現有chunk 上的分佈),資料的打散仍然是在後續正常的 Chunk 自動分裂和遷移的流程中逐步進行的。此外,Shard Key 需要有對應的 Index 來支撐,所以refineCollectionShardKey 要求提前建立新 Shard Key 對應的 Index。

因為並不是所有的文件都存在新增的 Suffix Field(s),所以在 4.4 中實際上隱含支援了「Missing Shard Key」的功能,即新插入的文件可以不包含指定的 Shard Key Field。但是,筆者不建議這麼做,很容易產生 Jumbo Chunk。

Compound Hashed Shard Keys

在 4.4 之前的版本中,只能指定單欄位的雜湊片鍵,原因是此時 MongoDB 不支援複合雜湊索引,這樣導致的結果是,很容易出現集合資料在分片上分佈不均。

而在 4.4 中支援了複合雜湊索引,即,可以在複合索引中指定單個雜湊欄位,位置不限,可以作為字首,也可以作為字尾,進而也就提供了對複合雜湊片鍵的支援,

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

有這個新功能之後,會帶來很多好處,比如在如下兩個場景下,

• 因為法律法規的要求,需要使用 MongoDB 的 zone sharding 功能,把資料儘量均勻打散在某個地域的多個分片上。

• 集合指定的片鍵的值是遞增的,比如在上文中舉的例子,{customer_id:1, order_id:1} 這個片鍵,如果customer_id 是遞增的,而業務也總是訪問最新的顧客的資料,導致的結果是大部分的流量總是訪問單一分片。

在沒有「複合雜湊片鍵」支援的情況下,只能由業務對需要的欄位提前計算雜湊值,儲存到文件中的某個特殊欄位中,然後再透過「範圍分片」的方式指定這個預先計算出雜湊值的特殊欄位及其他欄位作為片鍵來解決上述問題。

而在 4.4 中直接把需要的欄位指定為為雜湊的方式即可輕鬆解決上述問題,比如,對於上文描述的第二個問題場景,片鍵設定為 {customer_id:‘hashed’, order_id:1} 即可,大大簡化了業務邏輯的複雜性。

Hedged Reads

訪問延遲的升高可能會帶來直接的經濟損失,Google 有一個研究報告表明,如果網頁的載入時間超過 3 秒,使用者的跳出率會增加 50%。所以,在 4.4 中 MongoDB 提供了 Hedged Reads 的功能,即在分片叢集場景下,mongos 會把一個讀請求同時傳送到某個分片的兩個副本整合員,然後選擇最快的返回結果回覆客戶端,來減少業務上的 P95 和 P99 延遲。

Hedged Reads 功能是作為 Read Preference 的一部分來提供的, 所以可以是在 Operation 粒度上做配置,當 Read Preference 指定 nearest 時,預設啟用 Hedged Reads 功能,當指定為 primary 時,不支援 Hedged Reads 功能,當指定為其他時,需要顯示的指定 hedgeOptions,如下,
9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

此外,Hedged Reads 也需要 mongos 開啟支援,配置 readHedgingMode 引數為 on,預設 mongos 開啟該功能支援。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

降低複製延遲

主備複製的延遲對 MongoDB 的讀寫有非常大的影響,一方面,在一些特定的場景下,讀寫需要等待,備庫需要及時的複製並應用主庫的增量更新,讀寫才能繼續,另一方面,更低的複製延遲,也會帶來備庫讀時更好的一致性體驗。

Streaming Replication

在 4.4 之前的版本中,備庫透過不斷的輪詢主庫來獲取增量更新操作。每次輪詢時,備庫主動給主庫傳送一個 getMore 命令讀取其上的 Oplog 集合,如果有資料,返回一個最大 16MB 的 Batch,如果沒有資料,備庫也會透過 awaitData 選項來控制備庫無謂的 getMore 開銷,同時能夠在有新的增量更新時,第一時間獲取到對應的 Oplog。

拉取是由單個 OplogFetcher 執行緒來完成,每個 Batch 的獲取都需要經歷一個完整的 RTT,在副本集網路狀況不好的情況下,複製的效能就嚴重受限於網路延遲。所以,在 4.4 中,增量的 Oplog 是不斷的“流向”備庫的,而不是依靠備庫主動輪詢,相比於之前的方式,至少在 Oplog 獲取上節省了一半的 RTT。

當使用者的寫操作指定了 “majority” writeConcern 的時候,寫操作需要等待足夠多的備庫返回複製成功的確認,MongoDB 內部的一個測試表明,在新的複製機制下,在高延遲的網路環境中,可以平均提升 50% 的 majority 寫效能。

另外一個場景是使用者使用了Causal Consistency,為了保證可以在備庫讀到自己的寫操作(Read Your Write),同樣強依賴備庫對主庫 Oplog 的及時複製。

Simultaneous Indexing

在 4.4 之前的版本中,索引建立需要在主庫完成之後,才會複製到備庫上執行。備庫上的建立動作,在不同的版本中,因為建立機制和建立方式(前臺、後臺)的不同,對備庫 Oplog 的應用影響也大為不同。

但是,即使在 4.2 中,統一了前後臺索引建立機制,使用了相當細粒度的加鎖機制——只在索引建立的開始和結束階段對集合加獨佔鎖,也會因為索引建立本身的效能開銷(CPU、IO),導致複製延遲,或者因為一些特殊操作,比如 collMod 命令修改集合元資訊,而導致 Oplog 的應用阻塞,甚至會因為主庫歷史 Oplog 被覆蓋掉而進入 Recovering 狀態。

在 4.4 中,主庫和備庫上的索引建立操作是同時進行的,可以大幅減少因為上述情況所帶來的主備延遲,儘量保證即使在索引建立過程中,備庫讀也可以訪問到最新的資料。

此外,新的索引建立機制是在 majority 的具備投票許可權的資料承載節點返回成功後,索引才會真正生效。所以,也可以減輕在讀寫分離場景下,因為索引不同而導致的效能差異。

查詢能力和易用性增強

傳統的關係型資料庫(RDBMS)普遍以 SQL 語言為介面,客戶端可以在本地編寫融入部分業務邏輯的複雜 SQL 語句,來實現強大的查詢能力。MongoDB 作為一個新型的文件資料庫系統,也有自定義的 MQL 語言,複雜查詢能力主要藉助於 Aggregation Pipeline 來實現,雖弱於 RDBMS,但在最近的幾個大版本中也在持續不斷的打磨,最終的目的是使使用者在享受到 MongoDB 靈活性和擴充套件性的同時,也能享受到豐富的功能性。

Union

在多表聯合查詢能力上,4.4 之前只提供了一個 $lookup stage 用於實現類似於 SQL 中的「left outer join」功能,在 4.4 中新增的 $unionWith stage 又提供了類似 SQL 中的「union all」功能,使用者把兩個集合中的資料聚合到一個結果集中,然後做指定的查詢和過濾。區別於  l o o k u p s t a g e 的 是 , lookup stage 的是, l o o k u p s t a g e unionWith stage 支援分片集合。當在 Aggregate Pipeline 中使用了多個 $unionWith stage 的時候,可以對多個集合資料做聚合,使用方式如下,
9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

可以在 pipeline 引數中指定不同的 stage,用於在對集合資料聚合前,先進行一定的過濾,使用起來非常靈活,下面舉一個簡單的例子,比如業務上對訂單資料按表拆分儲存到不同的集合,第二季度有如下資料(演示目的),

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

現在假設業務上需要知道,二季度不同產品的銷量,在 4.4 之前,可能需要業務自己把資料都讀出來,然後在應用層面做聚合才能解決這個問題,或者依賴某種資料倉儲產品來做分析,但是需要有某種資料的同步機制。

而在 4.4 中只需要如下一條 Aggregate 語句即可解決問題,
9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

Custom Aggregation Expressions

4.4 之前的版本中可以透過 find 命令中的 $where operator 或者 MapReduce 功能來實現在 Server 端執行自定義的 JavaScript 指令碼,進而提供更為複雜的查詢能力,但是這兩個功能並沒有做到和 Aggregation Pipeline 在使用上的統一。

所以,在 4.4 中,MongoDB 提供了兩個新的 Aggregation Pipeline Operator,$accumulator 和 $function 用來取代 $where operator 和 MapReduce,藉助於「Server Side JavaScript」來實現自定義的 Aggregation Expression,這樣做到複雜查詢的功能介面都集中到 Aggregation Pipeline 中,完善介面統一性和使用者體驗的同時,也可以把Aggregation Pipeline 本身的執行模型利用上,實現所謂 「1+1 > 2」 的效果。

$accumulator 和 MapReduce 功能有些相似,會先透過init 函式定義一個初始的狀態,然後對於每一個輸入的文件,根據指定的 accumate 函式更新狀態,然後會根據需要決定是否執行 merge 函式,比如,如果在分片集合上使用了 $accumulator operator,那麼最後需要把不同分片上執行完成的結果做 merge,最後,如果指定了 finalize 函式,在所有輸入文件處理完成後,會根據該函式把狀態轉換為一個最終的輸出。

$function 和 $where operator 在功能上基本一致,但是強大之處是可以和其他的 Aggregation Pipeline Operator 配合使用,此外也可以在 find 命令中藉助於 $expr operator 來使用 $function operator,等價於之前的 $where operator,MongoDB 官方在文件中也建議優先使用 $function operator。

其他易用性增強

Some Other New Aggregation Operators and Expressions

除了上述的 $accumulator 和 $function operator,4.4 中還新增了其他多個 Aggregation Pipeline Operator,比如做字串處理的,獲取陣列收尾元素的,還有用來獲取文件或二進位制串大小的運算子,具體見如下列表,

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

Connection Monitoring and Pooling

4.4 的 Driver 中增加了對客戶端連線池的行為監控和自定義配置,透過標準的 API 來訂閱和連線池相關的事件,包括連線的關閉和開啟,連線池的清理。也可以透過 API 來配置連線池的一些行為,比如,擁有的最大/最小連線數,每個連線的最大空閒時間,執行緒等待可用連線時的超時時間,具體可以參考 MongoDB 官方的設計文件。

Global Read and Write Concerns

在 4.4 之前的版本中,如果操作的執行沒有顯式指定 readConcern 或者 writeConcern,也會有預設行為,比如readConcern 預設是 local,而 writeConcern 預設是 {w: 1}。但是,這個預設行為並不可以變更,如果使用者想讓所有的 insert 操作的 writeConcern 預設都是是 {w: majority},那麼只能所有訪問 MongoDB 的程式碼都顯式去指定該值。

在 4.4 中可以透過 setDefaultRWConcern 命令來配置全域性預設的 readConcern 和 writeConcern,如下,
9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

也可以透過 getDefaultRWConcern 命令獲取當前預設的readConcern 和 writeConcern。
此外,這次 MongoDB 做的更加貼心,在記錄慢日誌或診斷日誌的時候,會記錄當前操作的 readConcern 或者 writeConcern 設定的來源,二者相同的來源定義有如下三種,

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

對於 writeConcern 來說,還有如下一種來源,
9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

New MongoDB Shell (beta)

對於運維 MongoDB 的同學來說,使用最多的工具可能就是 mongo shell,4.4 提供了新版本的 mongo shell,增加了像程式碼高亮,命令自動補全,更加可讀的錯誤資訊等非常人性化的功能,不過,目前還是 beta 版本,很多命令還不支援,僅供嚐鮮。

9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

其他

這次的 4.4 釋出,前面講了主要是一個維護性的版本,所以除了上述解讀,還有很多其他小的最佳化,像 $indexStats 最佳化,TCP Fast Open 支援最佳化建連,索引刪除最佳化等等,還有一些相對大的增強,像新的結構化日誌LogV2,新的安全機制支援等,這些可能不是使用者最優先去關注的,在這裡就不一一描述了,感興趣的讀者可以自行參考官方的 Release Notes。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31550522/viewspace-2736714/,如需轉載,請註明出處,否則將追究法律責任。

相關文章