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


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」功能,用戶把兩個集合中的數據聚合到一個結果集中,然后做指定的查詢和過濾。區別於 $lookup stage 的是,$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。

 

原文鏈接
本文為阿里雲原創內容,未經允許不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM