mongodb原始碼實現、調優、最佳實踐系列-數百萬行mongodb核心原始碼閱讀經驗分享
關於作者
前滴滴出行技術專家,現任OPPO 文件資料庫 mongodb 負責人,負責 oppo 千萬級峰值 TPS/ 十萬億級資料量文件資料庫 mongodb 研發和運維工作,一直專注於分散式快取、高效能服務端、資料庫、中介軟體等相關研發。後續持續分享《 MongoDB 核心原始碼設計、效能最佳化、最佳運維實踐》, Github 賬號地址 :
序言
Mongodb 核心原始碼由第三方庫 third_party 和 mongodb 服務層原始碼組成,其中 mongodb 服務層程式碼在不同模組實現中依賴不同的 third_party 庫,第三方庫是 mongodb 服務層程式碼實現的基礎 ( 例如 : 網路底層 IO 實現依賴 asio-master 庫 , 底層儲存依賴 wiredtiger 儲存引擎庫 ) ,其中第三方庫也會依賴部分其他庫 ( 例如: wiredtiger 庫依賴 snappy 演算法庫, asio-master 依賴 boost 庫 ) 。
雖然Mongodb 核心原始碼數百萬行,工程量巨大,但是 mongodb 服務層程式碼實現層次非常清晰,程式碼目錄結構、類命名、函式命名、檔名命名都非常一目瞭然,充分體現了 10gen 團隊的專業精神。
說明:mongodb 核心除第三方庫 third_party 外的程式碼,這裡統稱為 mongodb 服務層程式碼。
本文以mongodb 服務層 transport 實現為例來說明如何快速閱讀整個 mongodb 程式碼,我們在走讀程式碼前,建議遵循如下準則。
1 熟悉 mongodb 基本功能和使用方法
首先,我們需要熟悉mongodb 的基本功能,明白 mongodb 是做什麼用的,用在什麼地方,這樣才能體現 mongodb 的真正價值。此外,我們需要提前搭建一個 mongodb 叢集玩一玩,這樣也可以進一步促使我們瞭解 mongodb 內部的一些常用基本功能。千萬不要急於求成,如果連 mongodb 是做什麼的都不知道,或者連 mongodb 的運維操作方法都沒玩過,直接讀取程式碼會非常不適合,沒有目的的走讀程式碼不利於分析整個程式碼,同時閱讀程式碼過程會非常痛苦。
2 下載程式碼編譯原始碼
熟悉了mongodb 的基本功能,並搭建叢集簡單體驗後,我們就可以從 github 下載原始碼,自己編譯原始碼生成二進位制檔案,編譯文件存放於 docs/building.md 程式碼目錄中,原始碼編譯步驟如下 :
1. 下載對應releases 中對應版本的原始碼
2. 進入對於目錄,參考docs/building.md 檔案內容進行相關依賴工具安裝
3. 執行buildscripts/scons.py 編譯出對應二進位制檔案,也可以直接 scons mongod mongos 這樣編譯。
4. 編譯成功後的生產可執行檔案存放於./build/opt/mongo/ 目錄
在正在編譯程式碼並執行的過程中,發現以下兩個問題:
1. 編譯出的二進位制檔案佔用空間很大,如下圖所示:
從上圖可以看出,透過strip 處理工具處理後,二進位制檔案大小已經和官方二進位制包大小一樣了。
2. 在一些低版本作業系統執行的時候出錯,找不到對應stdlib 庫,如下圖所示:
如上圖所示,當編譯出的二進位制檔案複製到線上執行後,發現無法執行,提示libstdc 庫找不到。原因是我們編譯程式碼時候依賴的 stdc 庫版本比其他作業系統上面的 stdc 庫版本更高,造成了不相容。
解決辦法: 編譯的時候編譯指令碼中帶上-static-libstdc++ ,把 stdc 庫透過靜態庫的方式進行編譯,而不是透過動態庫方式。
3 瞭解程式碼日誌模組使用方法,試著加列印除錯
由於前期我們對程式碼整體實現不熟悉,不知道各個介面的呼叫流程,這時候就可以透過加日誌列印進行除錯。Mongodb 的日誌模組設計的比較完善,從日誌中可以很明確的看出由那個功能模組列印日誌,同時日誌模組有多種列印級別。
1. 日誌列印級別設定
啟動引數中verbose 設定日誌列印級別,日誌列印級別設定方法如下: Mongod -f ./mongo.conf -vvvv
這裡的v 越多,表明日誌列印級別設定的越低,也就會列印更多的日誌。一個 v 表示只會輸出 LOG(1) 日誌, -vv 表示 LOG(1) LOG(2) 都會寫日誌。
2.
如何在.cpp
檔案中使用日誌模組記錄日誌
如果需要在一個新的.cpp
檔案中使用日誌模組列印日誌,需要進行如下步驟操作:
i) 新增宏定義 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kExecutor
ii) 使用 LOG(N) 或者 log() 來記錄想要輸出的日誌內容,其中 LOG(N) 的 N 代表日誌列印級別, log() 對應的日誌全記錄到檔案。
例如: LogComponent::kExecutor 代表 executor 模組相關的日誌,參考 log_component.cpp 日誌模組檔案實現,對應到日誌檔案內容如下:
4 學會用 gdb 除錯 mongodb 程式碼
Gdb 是 linux 系統環境下優秀的程式碼除錯工具,支援設定斷點、單步除錯、列印變數資訊、獲取函式呼叫棧資訊等功能。 gdb 工具可以繫結某個執行緒進行執行緒級除錯,由於 mongodb 是多執行緒環境,因此在用 gdb 除錯前,我們需要確定除錯的執行緒號, mongod 程式包含的執行緒號及其對應執行緒名檢視方法如下 :
注意: 在除錯mongod 工作執行緒處理流程的時候,不要選擇 adaptive 動態執行緒池模式,因為執行緒可能因為流量低引起工作執行緒不飽和而被銷燬,從而造成除錯過程因為執行緒銷燬而中斷, synchronous 執行緒模式是一個連結一個執行緒,只要我們不關閉這個連結,執行緒就會一直存在,不會影響我們理解 mongodb 服務層程式碼實現邏輯。 synchronous 執行緒模式除錯的時候可以透過 mongo shell 連結 mongod 服務端埠來模擬一個連結,因此除錯過程相對比較可控。
在對工作執行緒除錯的時候,發現gdb 無法查詢到 mongod 程式的符號表,無法進行各種 gdb 功能除錯,如下圖所示:
上述gdb 無法 attach 到指定執行緒除錯的原因是無法載入二進位制檔案符號表,這是因為編譯的時候沒有加上 -g 選項引起, mongodb 透過 SConstruct 指令碼來進行 scons 編譯,要啟用 gdb 功能需要在 scons 編譯程式碼的時候指定 gdbserver 選項 :scons --gdbserver=GDBSERVER -j 2 。
編譯出新的二進位制檔案後,就可以gdb 除錯了,如下圖所示,可以很方便的定位到某個函式之前的呼叫棧資訊,並進行單步、列印變數資訊等除錯:
5 熟悉程式碼目錄結構、模組細化拆分
在進行程式碼閱讀前還有很重要的一步就是熟悉程式碼目錄及檔案命名實現,mongodb 服務層程式碼目錄結構及檔案命名都有很嚴格的規範。下面以 truansport 網路傳輸模組為例, transport 模組的具體目錄檔案結構:
從上面的檔案分佈內容,可以清晰的看出,整個目錄中的原始碼實現檔案大體可以分為如下幾個部分:
1. message_compressor_* 網路傳輸資料壓縮子模組
2. service_entry_point* 服務入口點子模組
3. service_executor* 服務執行子模組,即執行緒模型子模組
4. service_state_machine* 服務狀態機處理子模組
5. Session* 回話資訊子模組
6. Ticket* 資料分發子模組
7. transport_layer* 套接字處理及傳輸層模式管理子模組
透過上面的拆分,整個大的transport 模組實現就被拆分成了 7 個小模組,這 7 個小的子模組各自負責對應功能實現,同時各個模組相互銜接,整體實現網路傳輸處理過程的整體實現,下面的章節將就這些子模組進行簡單功能說明。
6 從 main 入口開始大體走讀程式碼
前面5 個步驟過後,我們已經熟悉了 mongodb 編譯除錯以及 transport 模組的各個子模組的相關程式碼檔案實現及大體子模組作用。至此,我們可以開始走讀程式碼了, mongos 和 mongod 的程式碼入口分別在 mongoSMain() 和 mongoDbMain() ,從這兩個入口就可以一步一步瞭解 mongodb 服務層程式碼的整體實現。
注意: 走讀程式碼前期不要深入各種細節實現,大體瞭解程式碼實現即可,先大體弄明白程式碼中各個模組功能由那些子模組實現,千萬不要深究細節。
7 總結
本章節主要給出了數百萬級mongodb 核心程式碼閱讀的一些建議,整個過程可以總結為如下幾點:
1. 提前瞭解mongodb 的作用及工作原理。
2. 自己搭建叢集提前學習下mongodb 叢集的常用運維操作,可以進一步幫助理解 mongodb 的功能特性,提升後期程式碼閱讀的效率。
3. 自己下載原始碼編譯二進位制可執行檔案,同時學會使用日誌模組,透過加日誌列印的方式逐步開始除錯。
4. 學習使用gdb 程式碼除錯工具除錯執行緒的執行流程,這樣可以更進一步的促使快速學習程式碼處理流程,特別是一些複雜邏輯,可以大大提升走讀程式碼的效率。
5. 正式走讀程式碼前,提前瞭解各個模組的程式碼目錄結構,把一個大模組拆分成各個小模組,先大體瀏覽各個模組的程式碼實現。
6. 前期走讀程式碼千萬不要深入細節,捋清楚各個模組的大體功能作用後再開始一步一步的深入細節,瞭解深層次的內部實現。
7. 從main() 入口逐步開始走讀程式碼,結合 log 日誌列印和 gdb 除錯。
8. 跳過整體流程中不熟悉的模組程式碼,只走讀本次想弄明白的模組程式碼實現。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69984922/viewspace-2729061/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-mongodb網路傳輸層模組原始碼實現三MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現三MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現一MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-網路傳輸層模組原始碼實現三MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-網路傳輸層模組原始碼實現四MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-網路傳輸層模組原始碼實現二MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二MongoDB原始碼運維
- mongodb核心原始碼實現及效能優化系列:Mongodb write寫(增、刪、改)模組原始碼實現MongoDB原始碼優化
- mongodb核心原始碼實現及效能最佳化系列:Mongodb特定場景效能數十倍提升最佳化實踐MongoDB原始碼
- mongodb核心原始碼實現、效能調優系列-為何要對開源mongodb資料庫核心做二次開發MongoDB原始碼資料庫
- mongodb核心原始碼實現及效能最佳化:transport_layer網路傳輸層模組原始碼實現二MongoDB原始碼
- mongodb網路傳輸處理原始碼實現及效能調優-體驗核心效能極致設計MongoDB原始碼
- Vuex 原始碼解析(如何閱讀原始碼實踐篇)Vue原始碼
- mongodb核心原始碼實現及效能最佳化:常用高併發執行緒模型設計及mongodb執行緒模型最佳化實踐MongoDB原始碼執行緒模型
- 萬字長文 | MongoDB絡傳輸處理原始碼實現及效能調優MongoDB原始碼
- 個人經驗分享如何閱讀Go語言原始碼Go原始碼
- MongoDB 最佳實踐MongoDB
- MongoDB最佳實踐MongoDB
- Redis原始碼閱讀:sds字串實現Redis原始碼字串
- 如何閱讀數百萬級大工程mongodb核心原始碼-2020年度中mongodb中文社群一等獎MongoDB原始碼
- Mongodb write寫(增、刪、改)模組原始碼實現MongoDB原始碼
- mongodb核心transport_layer網路傳輸層模組原始碼實現三MongoDB原始碼
- mongodb核心transport_layer 網路傳輸層模組原始碼實現四MongoDB原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- MongoDB最佳安全實踐MongoDB
- [Redis原始碼閱讀]dict字典的實現Redis原始碼
- 原始碼閱讀之ArrayList實現細節原始碼
- 原始碼閱讀之Java棧的實現原始碼Java
- 分享一些閱讀Java相關框架原始碼的經驗Java框架原始碼
- 原始碼閱讀系列彙總原始碼
- 原始碼閱讀之LinkedList實現細節原始碼
- Python原始碼閱讀-閉包的實現Python原始碼
- Laravel 原始碼閱讀指南 -- HTTP 核心Laravel原始碼HTTP
- 最佳實踐 | 原始碼升級gcc原始碼GC
- Axios 原始碼解讀 —— 原始碼實現篇iOS原始碼
- [Redis原始碼閱讀]實現一個redis命令--nonzerodecrRedis原始碼
- 為什麼要閱讀核心原始碼?原始碼
- MongoDB原始碼分析之MongosXFMongoDB原始碼