深度解讀 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。

 

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章