巧用 maxTimeMS 服务端超时,避免承载亿级用户的腾讯云数据库MongoDB服务雪崩
騰訊云數據庫MongoDB作為一款基于開源社區MongoDB版本的文檔數據庫產品,其承載著公司內外包括微信、看點、QQ音樂在內的億級用戶重量級APP產品。在某些場景的使用過程中,用戶在客戶端請求超時后會不斷重試,可能導致服務端大量請求積壓,出現惡性循環甚至導致服務雪崩。一般遇到這種情況,后臺會自動檢測并做服務降級,主動拒絕一部分用戶請求,或者重啟后端服務等舉措來應對。但是這些措施對業務有損,或者不可自行恢復。
本文圍繞 MongoDB 原生 maxTimeMS 特性和騰訊云MongoDB的優化,并結合 4.0 版本代碼,詳細闡述如何巧用 maxTimeMS 服務端超時,來避免服務端請求積壓導致雪崩的情形。
背景
業務方在騰訊云MongoDB運營過程中,曾有業務集群出現過:慢請求 -> 客戶端斷開重試 -> 服務端累積的請求越來越多 -> 服務雪崩 -> 人工重啟解決的問題。
過去,為了防止服務雪崩,騰訊云MongoDB應對的解決方案是:在內核中實現了連接狀態檢測、自適應限流等功能進行過載保護,并開發了外圍工具 kill 長時間運行的請求等。但是過載保護本質上會進行服務降級,對業務還是會產生一定影響,而外圍工具處理起來也不夠及時,甚至可能在節點 hang 住時無法工作,以上解決舉措都存在著一定程度的弊端。
為了更好地避免服務雪崩,騰訊云MongoDB建議設置服務端超時,并和客戶端超時保持一致。這樣在客戶端出現超時后,服務端也立刻終止這些“無意義”請求的執行。通過避免服務端資源的無效占用,極大地降低客戶端不斷重試導致服務雪崩的概率。
MongoDB原生服務端超時原理
當一個用戶請求到達 mongos 或者 mongod 時,會生成一個對應的 OperationContext 對象,來記錄這個請求從開始到結束期間的完整上下文信息。上下文信息中就包含了后面要介紹的“時間信息”:起始時間,已執行時間,超時時間,以及是否是 kill 狀態等。相關成員和接口如下圖所示:
其中 checkForInterrupt() 接口的作用就是檢測請求是否應該終止,需滿足以下 3 個條件之一:
1.實例處于 shutdown 狀態;
2.用戶使用 killOp 命令,調用 markKilled() 接口,將 OperationContext 標記為了終止狀態;
3.用戶通過 maxTimeMS 參數給 OperationContext 配置了超時 Deadline,然后檢測到 hasDeadlineExpired() 為 true;
一個請求在執行過程中會多次調用 checkForInterrupt() 檢測自己是否超時,比如在獲取Global ticket,獲取資源鎖,yield,內部迭代重試等階段會主動檢測并超時退出。
備注1:原生 MongoDB 在 3.7.3 版本通過?SERVER-33473 (https://jira.mongodb.org/browse/SERVER-33473)才支持獲取 Global Ticket 時能夠主動超時和打斷,因此更低版本在 qr/qw 較大,請求排隊比較嚴重時無法及時超時退出;而且在 3.7.3 版本通過?SERVER-32638? (https://jira.mongodb.org/browse/SERVER-32638)才支持獲取資源鎖時主動超時,因此更低版本在獲取互斥鎖卡住時也無法及時超時退出。
因此,為了有更好的體驗,強烈建議升級到 4.0 或更高版本。
備注2:終止OperationContext 和 kill 進程/線程行為不同。OperationContext 自己檢測和主動退出的機制使得 killOp 和 maxTimeMS 不會絕對精確。從我們對 maxTimeMS 的測試來看,能夠保證誤差在 1 秒內,大部分在 10ms 級別。也就是說用戶設置的 100ms 超時,在后臺可能會執行 110ms 左右。
使用小貼士:
以常見的 CRUD 操作為例,用戶在命令參數中加上 maxTimeMS 的設置即可。
例如將以下操作的服務端超時設置為 100ms:
// 查詢操作 db.runCommand({find:"cmongo_test", filter:{a:1} , maxTimeMS: 100}) // 插入操作 db.runCommand({insert:"cmongo_test", documents: [{a:1}], maxTimeMS: 100}) // 更新操作 db.runCommand({update:"cmongo_test", updates:[{q:{a:1}, u:{$set:{b:3}}}], maxTimeMS: 100}) // 刪除操作 db.runCommand({delete:"cmongo_test3", deletes: [{q : {a :1}, limit:1}], maxTimeMS: 100})如果服務端超時,客戶端會收到類似如下的錯誤(客戶端還沒超時斷開的情況下):
{"ok" : 0,"errmsg" : "Encountered non-retryable error during query :: caused by :: operation exceeded time limit","code" : 50,"codeName" : "MaxTimeMSExpired", }MongoDB 原生版本問題
在騰訊云MongoDB運營過程中,發現原生版本有 2 個比較大的使用痛點:一是原生 5.0 以下版本,在分片集群模式下不支持insert/update/delete 寫命令的超時;二是缺乏服務端默認的 maxTimeMS 配置。
1.原生 5.0 以下版本,在分片集群模式下不支持 insert/update/delete 寫命令的超時
在 4.4 及以下版本中,mongos 在接收到寫命令時,會使用 maxTimeMS 設置請求的 OperationContext 超時,然后將寫入的數據拆分成子請求發給 mongod. 但是 mongod 側收到的子請求中已經沒有了 maxTimeMS 參數,因此 mongod 側不會主動超時。
所以用戶在對 mongos 發起一個攜帶 maxTimeMS 的寫命令時,遲遲等不到超時報錯。
以 4.0 版本為例,常用命令對 maxTimeMS 的支持情況如下:
命令 | mongos | mongod | 備注 |
find | 支持? ? | 支持 | https://docs.mongodb.com/v4.0/reference/command/find/ |
getmore | 支持 | 支持 | https://docs.mongodb.com/v4.0/reference/command/getMore/ getMore 這里特指 awaitDataTimeOut 超時,即 cursor 等待有新數據的時間。?SERVER-34277 (https://jira.mongodb.org/browse/SERVER-34277)提議引入新字段進行區分,不過社區還沒有做 |
findAndModify | 支持 | 支持 | https://docs.mongodb.com/v4.0/reference/command/findAndModify/ |
aggregate相關 aggregate/count/distinct | 支持 | 支持 | https://docs.mongodb.com/v4.0/reference/command/aggregate/ https://docs.mongodb.com/v4.0/reference/command/count/ https://docs.mongodb.com/v4.0/reference/command/distinct/ |
insert | ? × | 支持 | https://docs.mongodb.com/v4.0/reference/command/insert/ |
update | ? × | 支持 | https://docs.mongodb.com/v4.0/reference/command/update/ |
delete | ? × | 支持 | https://docs.mongodb.com/v4.0/reference/command/delete/ |
這個 Bug 直到 4.7.0 版本通過 SERVER-46187 (https://jira.mongodb.org/browse/SERVER-46187)才修復,并隨著今年 5.0 版本的推出才解決。但是,5.0 新版本嘗鮮的寥寥無幾,絕大部分用戶都在使用比較成熟的 4.0 / 4.2 / 4.4 版本。
2.缺乏服務端默認的 maxTimeMS 配置
MongoDB 的命令和參數眾多,普通用戶很難注意并理解 maxTimeMS 的配置,即使想開啟這個功能也需要修改代碼并上線發布。而且如果多租戶共享一個集群,怎么保證其他人也開啟了默認超時也是一個問題。因此,整體使用體驗上需要改進。
另外,和 maxTimeMS 參數對比,原生MongoDB 允許在服務端配置默認的 writeConcern 級別,并在最新發布的 5.0 版本中將默認設置調整為 majority ,防止新手用戶不理解規則導致重要數據出現安全性問題。本質上來說,是通過服務端默認配置來降低用戶的使用成本。
因此,騰訊云MongoDB作為一個注重用戶體驗的云數據庫,認為有必要在服務端支持默認的 maxTimeMS 配置。
騰訊云MongoDB對 maxTimeMS 服務端超時的優化
1.完善 mongos 寫命令對 maxTimeMS 的支持
Mongos 會根據原始請求按目標 shard 分組之后重構子請求,并將每個子請求轉發給對應的 shard。
下圖展示一個寫請求在mongos 上的執行路徑,比較關鍵的點有:
在 runCommand 函數中,會從命令中解析 maxTimeMS(客戶指定的),并設置 OperationContext 的 deadline;
在BatchWriteExec::executeBatch 函數中,會重構子請求,加上 sessionId 和 transactionId 等信息,但是沒有攜帶 maxTimeMS 信息;
這時會出現 2 個問題:
OperationContext 只能跟蹤 1 個請求在 1 個進程中的執行信息,也就是說 mongos 和 mongod 各自有自己獨立的 OperationContext。因此,雖然請求在 mongos 中有 deadline,但是 mongod 上沒有;
子請求沒有將 maxTimeMS 透傳給 mongod,因此 mongod 側也無法根據子請求信息設置 OperationContext 的 deadline;
解決方法:在生成子請求時,計算總請求當前還剩余多少執行時間,并作為 maxTimeMS 參數增加到子請求中,再透傳給 mongod。
整體架構如下所示:
備注:社區 5.0 版本 SERVER-46187 (https://jira.mongodb.org/browse/SERVER-46187)的修復思路和騰訊云MongoDB的修復方法類似。
2.支持騰訊云MongoDB服務端默認配置
騰訊云MongoDB支持分片和副本集 2 種使用模式。
對于分片集群,可以通過 mongos 在 configSvr 的 config.cmongo_settings 中配置默認參數。mongos 在處理請求時,如果請求中攜帶了用戶指定的 maxTimeMS 參數,則以用戶指定的為準;如果用戶沒有指定,則增加默認配置。
整體架構如下圖所示:
對于副本集集群,也是在副本集的 config.cmongo_settings 表中存儲默認配置,如下圖所示:
備注:默認配置只對用戶請求生效,騰訊云MongoDB集群內部的行為。(比如主從同步,session 上下文信息持久化等請求不受影響)
使用小貼士
騰訊云MongoDB在 4.0 和 4.2 版本進行了上述優化。
對于需要使用 maxTimeMS 功能的用戶,建議先將騰訊云MongoDB的小版本升級到最新,然后通過如下方式進行默認配置:
分片集群登陸 mongos, 副本集登陸 mongod;
配置 config.cmongo_settings 表(在 1 個 mongos 或者 mongod 節點上配置即可,其他 mongos 或者 mongod 節點會在 10 秒內自動同步配置);
總結
本文通過對 MongoDB 服務端超時原理和原生版本存在的問題做了分析闡述。騰訊云MongoDB在原生版本的基礎上,解決了 4.0 和 4.2 版本無法在 mongos 側正確處理寫命令超時的問題,并支持了服務端的默認配置,保證服務端超時后能很快退出,防止后端請求積壓導致服務雪崩,最終在功能正確性和用戶體驗上實現了相關優化目標。
招賢納士
騰訊云MongoDB作為一款基于開源社區MongoDB版本的文檔數據庫產品,其承載著公司內外包括微信、看點、QQ音樂在內的億級用戶重量級APP產品。
歡迎有志向從事NoSQL數據庫系統研發的同學加入騰訊TEG云架平NoSQL內核團隊,可通過郵箱(tencentarchitect@126.com)聯系,或者掃碼加入我們的微信群進行技術交流。
鵝廠架構師圈
關于我們
本期出品:騰訊TEG云架平NoSQL內核團隊
責任編輯:zhenyi/melissa/cathy
技術分享:關注微信公眾號 【鵝廠架構師】
總結
以上是生活随笔為你收集整理的巧用 maxTimeMS 服务端超时,避免承载亿级用户的腾讯云数据库MongoDB服务雪崩的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 业界首创,腾讯网络平台部实现大规模光网络
- 下一篇: linux cmake编译源码,linu