mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

y123456yzzyz 發表於 2020-11-29

關於作者

前滴滴出行技術專家,現任OPPO 文件資料庫 mongodb 負責人,負責 oppo 千萬級峰值 TPS/ 十萬億級資料量文件資料庫 mongodb 核心研發及運維工作,一直專注於分散式快取、高效能服務端、資料庫、中介軟體等相關研發。後續持續分享《 MongoDB 核心原始碼設計、效能優化、最佳運維實踐》, Github 賬號地址 : https://github.com/y123456yz

1. Command 命令處理模組回顧

Mongodb command 命令處理模組原始碼實現一》中我們分析了一個客戶端請求到來後, mognodb 服務端大體處理流程如下:

①  message 中解析初報文頭部,從而確定一個完整的 mongodb 報文

②  body 中解析初 OpCode 操作碼資訊, 3.6 版本預設 OpCode 操作碼為 OP_MSG

③  根據解析初的OP_MSG 操作碼,構造對應 OpMsg 類,真實命令請求以 bson 資料格式儲存在該類成員 body 中。

④  body 中解析出 command 命令字串資訊 ( 如“ insert ”、“ update ”等 )

⑤  從全域性_commands map 表中查詢是否支援該命令,如果支援則執行該命令處理,如果不支援則直接報錯提示。

⑥  最終找到對應command 命令後,執行 command 的功能 run 介面。

Mongodb 核心支援的 command 命令資訊儲存在一個全域性 map _commands 中,從命令請求 bson 中解析出 command 命令字串後,就是從該全域性 map 表查詢,如果找到該命令則說明 mongodb 支援該命令,找不到則說明不支援,整個過程歸納為下圖所示:

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

OpMsg 類中解析出命令名字串後 ( 例如: insert delete ) ,從全域性 map _commands 查詢,找到則執行對應命令。如果找不到,說明不支援該命令操作,進行異常提示處理。

Mongodb 不同例項支援那些 command 命令完全取決於全域性 map _commands ,下面繼續分析該全域性 map 來源。

2.  Command 命令處理模組原始碼目錄結構

mongodb 叢集中通常包含 3 種節點例項角色: mongos mongod(ShardServer) mongod(ConfigServer) 。這 3 種例項校色功能如下:

①  Mongos :代理,從 shardServer 獲取路由資訊,轉發客戶端請求到 shard

②  mongod(ShardServer) :資料儲存節點,所有客戶端資料記錄到 shard 中。

③  mongod(ConfigServer) :記錄資料路由資訊以及一些後設資料。

Mongos 代理程式名唯一,也就是 mongos ,代理mongos 支援的命令資訊比較好確認。但是 ShardServer ConfigServer 的程式名都是 mongod ,如何區分各自支援那些命令呢?

configServer 實際上是一種特殊的 shardServer ,它擁有 shard 資料分片的功能外,還擁有特殊的後設資料管理功能,例如記錄 chunk 後設資料資訊、 mongos 資訊、分片操作日誌資訊等。因此, configServer 除了支援 shardServer 的命令外,還會支援更多的特有命令。

mongos 代理支援的命令資訊全部在 src/mongo/s/commands 目錄中實現,原始碼檔案如下 :

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

mongod(shardServer) 支援的命令資訊全部在 src/mongo/db/commands 目錄中實現,原始碼檔案如下:

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

mongod(configServer) 幾乎支援所有 shardServer 支援的命令 ( 說明:也有個別別特例,如 ”mapreduce.shardedfinish” ) ,還支援特有的一些命令,這些特意命令在 src/mongo/db/s/config 目錄中實現,原始碼檔案如下:

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

從上面的不同例項支援命令的原始碼目錄檔案可以看出,mongodb 核心原始碼設計之優秀,從目錄結構即可一眼確定不同例項角色支援的各自不同命令資訊,程式碼可讀性非常好。目錄結構可以總結為下表:

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

例項角色名

執行的命令原始碼實現目錄

說明

mongos

src/mongo/s/commands

mongos 代理支援的命令實現

mongod(shardServer)

src/mongo/db/commands

shardServer 支援的命令實現

mongod(configServer)

src/mongo/db/s/config

configServer 除了支援該目錄中命令外,還支援 shardServer 角色的幾乎所有命令

configServer shardServer 各自支援的命令範圍類似於下圖包含與被包含的關係,小橢圓代表 shardServer ,大圓代表 configServer

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

3.  command 模組類繼承關係

2 章節程式碼目錄結構可以看出,絕大部分命令功能由對應原始碼檔案實現,例如 find_cmd.cpp 原始碼檔案進行 find” 命令處理。此外,也有部分原始碼檔案,一個檔案對應多個命令實現,例如write_commands.cpp 原始碼檔案,同時負責 insert update delete 增刪改處理。

由於命令眾多,瞭解了程式碼目錄結構後,在進行核心程式碼分析前,我們先了解一下command 類的各種繼承關係。不同命令有不同功能,也就需要不同的實現,但是所有命令也會有一些共同的介面特性,例如該命令是否需要認證、是否支援從節點操作、是否支援 WriteConcern 操作等。

不同command 命令有相同的共性,也會有各自不同的獨有特性。所以, mongodb 在原始碼實現中充分考慮了這些問題,抽象出一些共有的特性介面由基類實現, command 用於的一些獨有的特性,則在繼承類中實現。 command 命令處理模組相關核心原始碼類主要繼承關係圖如下:

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

如上圖,command 命令處理模組相關實現類按照父子繼承關係可以包含四層,每層功能說明如下:

①  CommandInterface 類:虛擬介面類,只定義虛擬介面,不做具體實現。

②  Command 類:完成一些基本功能檢查,例如是否支援從節點操作、是否需要認證、是否支援 WriteConcern 、獲取命令名、是否只能在 admin 庫操作等。

③  BasicCommand 類:認證相關介面實現、定義虛擬 run 介面。

④  具體命令類:每個命令都有一個相應的類定義,都是在該層實現,真正的命令run 介面實現在該層完成。

4. command 命令註冊核心程式碼實現

前面分析提到,當解析到對應命令字串( 如: insert update ) 後,從全域性 map 表中 _commands 查詢,找到說明支援該命令,找不到則不支援。全域性 _commands 表中儲存了例項支援的 command 命令資訊,不同命令需要提前註冊到該 map 表中,註冊方式有兩種:

①  每個命令定義一個對應全域性類變數

②  new() 一個該命令類資訊

類註冊過程原始碼實現由command 類初始化構造介面完成,註冊過程核心程式碼如下所示:

1. //命令註冊,所有註冊的命令最終全部儲存到_commands全域性map表中  
2. //name和oldName實際上是同一個command,只是可能因為歷史原因,命令名改名了  
3. Command::Command(StringData name, StringData oldName)   
4.     //命令名字串  
5.     : _name(name.toString()),   
6.      //對應命令執行統計,total代表總的,failed代表執行失敗的次數  
7.      _commandsExecutedMetric("commands." + _name + ".total", &_commandsExecuted),  
8.      _commandsFailedMetric("commands." + _name + ".failed", &_commandsFailed) {  
9.     //如果_commands map表還沒有生成,則new一個  
10.     if (_commands == 0)  
11.         _commands = new CommandMap();  
12.     ......  
13.     //把name命令對應的command新增到map表中  
14.     Command*& c = (*_commands)[name];  
15.     if (c)  
16.         log() << "warning: 2 commands with name: " << _name;  
17.     c = this;  
18.     ......  
19.   
20.     //大部分命令name和oldName是一樣的,所以在陣列中只會記錄一個  
21.     //如果改名過,則name和oldName就不一樣,這時候都需要註冊到map表,對應同一個command  
22.     if (!oldName.empty()) //也就是name和oldName兩個命令對應的是同一個this類  
23.         (*_commands)[oldName.toString()] = this;  
24. }

  command 初始化建構函式中有兩個入參,分表代表當前命令名和老舊命令名稱,這樣設計是為了相容處理。

4.1 command 註冊方式一

超過99% command 命令通過定義一個全域性類變數來完成註冊,本文以 shardServer 例項的 insert update delete find ”為例,這幾個命令註冊方式如下:

1. //insert命令初始化  
2. class CmdInsert : public WriteCommand { //  
3. public:  
4.     //insert命令初始化構造  
5.     CmdInsert() : WriteCommand("insert") {}  
6.     ......  
7.     //認證檢查  
8.     Status checkAuthForRequest(...) final {  
9.         ......  
10.     }  
11.   
12.     //真正的Insert插入文件會走這裡面  
13.     void runImpl(...);  
14.     }  
15. } cmdInsert; //直接定義一個cmdInsert全域性變數  
16.   
17. //update命令初始化  
18. class CmdUpdate: public WriteCommand { //  
19. public:  
20.     //update命令初始化構造  
21.     CmdUpdate() : WriteCommand("update") {}  
22.     ......  
23.     //認證檢查  
24.     Status checkAuthForRequest(...) final {  
25.         ......  
26.     }  
1.     //查詢計劃執行過程  
2.     Status explain(...) const override {  
3.           ......  
4.     }  
27.     //真正的update插入文件會走這裡面  
28.     void runImpl(...);  
29.     }  
30. } cmdUpdate; //直接定義一個cmdUpdate全域性變數  
31.   
32. //delete命令初始化  
33. class CmdDelete: public WriteCommand { //  
34. public:  
35.     //delete命令初始化構造  
36.     CmdDelete() : WriteCommand("delete") {}  
37.     ......  
38.     //認證檢查  
39.     Status checkAuthForRequest(...) final {  
40.         ......  
41.     }  
5.     //查詢計劃執行過程  
6.     Status explain(...) const override {  
7.           ......  
8.     }  
42.   
43.     //真正的delete插入文件會走這裡面  
44.     void runImpl(...);  
45.     }  
46. } cmdDelete; //直接定義一個cmdDelete全域性變數

find 命令也是通過定義一個全域性FindCmd 類變數來完成該命令的註冊過程,註冊過程程式碼如下:

1. //find命令實現類  
2. class FindCmd : public BasicCommand {  
3. public:  
4.     //初始化構造  
5.     FindCmd() : BasicCommand("find") {}  
6.     ......  
7.       
8.     //查詢計劃執行過程  
9.     Status explain(...) const override {  
10.           ......  
11.     }  
12. } findCmd; //直接定義一個findCmd全域性變數

上面的類除了可以確定shardServer 讀寫命令的註冊方式外,還可以看出讀寫命令實現過程中,類繼承關係稍微有點區別。主要體現在: FindCmd  ( ) 命令類直接繼承 BasicCommand  命令類,而 CmdInsert (增)   CmdDelete ( ) Cmd Update( ) 這三個寫相關的命令,則通過繼承 WriteCommand  來中轉一次, WriteCommand  實現 WriteCommand  共性介面,而三個子類則實現自己特有的功能。

shardServer 例項,增、刪、改、查四個級別命令的繼承關係圖可以總結為下圖所示:

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

4.2 command 註冊方式二

除了直接定義一個全域性命令類變數外,mongodb 核心命令註冊實現的時候,部分命令註冊通過 new 一個命令類實現,例如 planCache 執行計劃對應的幾個命令就是通過該方式實現,程式碼實現如下:

1. //執行計劃相關的幾個command註冊過程,通過new實現  
2. MONGO_INITIALIZER_WITH_PREREQUISITES(SetupPlanCacheCommands, MONGO_NO_PREREQUISITES)  
3. (InitializerContext* context) {  
4.     //執行計劃相關的幾個命令註冊  
5.     new PlanCacheListQueryShapes();  
6.     new PlanCacheClear();  
7.     new PlanCacheListPlans();  
8.     return Status::OK();  
9. }   
10.   
11. //test命令相關的幾個command註冊過程,也是通過new實現  
12. MONGO_INITIALIZER(RegisterEmptyCappedCmd)(InitializerContext* context) {  
13.     //必須使能testCommandsEnabled,該命令才有效  
14.     if (Command::testCommandsEnabled) {  
15.         new CapTrunc();  
16.         new CmdSleep();  
17.         new EmptyCapped();  
18.         new GodInsert();  
19.     }  
20.     return Status::OK();  
21. }

至此,mongodb 核心 command 命令註冊過程就分析完畢,如果想新註冊一個新的命令,可以模仿這個流程實現即可。

5.  mongos mongod(shardServer) mongod(configServer) 命名規範

mongodb 不同校色得二進位制例項支援的命令有所差異,分別由不同的程式碼檔案實現對應命令功能。 mongodb 核心設計非常優秀,通過檔名即可確定對應的命令,以及該命令歸屬於那個角色例項。這裡回顧一下前面提到的不同校色例項對應的命令程式碼目錄實現:

①  mongos 代理:程式碼目錄 src/mongo/s/commands

②  mongod(shardServer) :程式碼目錄 src/mongo/db/commands

③  mongod(configServer) :程式碼目錄 src/mongo/db/s/config

除了程式碼目錄有明確的區別外,程式碼檔名及命令類名也各不相同。但是,命令類名和檔名也有特定的命名規範,有一定的命名規律,下面還是以mongod (含 shardServer configServer )和mongos 代理為例,來說明最常用的增、刪、改、查 command 命令對應的原始碼檔案命名和命令類命名。

提前梳理好各個校色例項的命名規範,對我們理解整個程式碼具有事半功倍的效果,同時也可以方便我們快速找到任何一個命令的程式碼檔案及其對應命令的核心程式碼實現,具有 舉一反三 的效果。

5.1 mongos mongod( shardServer configServer ) 命名規範

mongod 例項的寫操作命令 ( 增、刪、改 ) write_commands.cpp 檔案實現,該檔案中的 CmdInsert CmdDelete CmdUpdate 類分別對應具體的增、刪、改命令操作。讀操作命令由 find_cmd.cpp 檔案實現,對應命令類為 FindCmd

除了mongod 例項, mongos 作為代理轉發節點,同樣支援增、刪、改操作。 mongodb 核心實現的時候,如果叢集部署是 sharding 叢集模式,則需要 mongos 代理,客戶端訪問入口為代理。正是因為代理模式為 sharding 分片叢集模式,所以 mongos 支援的命令在原始檔命名和命令類命名的時候,做了特殊標記。相比 mongod 例項,所有 mongos 支援的命令相關原檔案和類實現基本上都增加 cluster 特殊標記。

以增、刪、改、查、isMaster getMore findAndModify 為例, mongos mongod( shardServer configServer ) 支援的命令列表總結如下:

命令名

mongod 例項對應命令檔案 / 命令類

mongos 例項對應命令檔案 / 命令類

insert 操作

write_commands.cpp/(CmdInsert)

cluster_write_cmd.cpp/

(ClusterCmdInsert)

delete 操作

write_commands.cpp/(CmdDelete)

cluster_write_cmd.cpp/

(ClusterCmdDelete)

update 操作

write_commands.cpp/(CmdUpdate)

cluster_write_cmd.cpp/

(ClusterCmdUpdate)

find 操作

find_cmd.cpp/(FindCmd)

cluster_find_cmd.cpp/

(ClusterFindCmd)

getMore 操作

getmore_cmd.cpp/(GetMoreCmd)

cluster_getmore_cmd.cpp/(ClusterGetMoreCmd)

findAndModify 操作

find_and_modify.cpp /(CmdFindAndModify)

cluster_find_and_modify_cmd.cpp/

(FindAndModifyCmd)

......

......

......

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

從上面的命名檔案和命令類名可以看出,大多數mongos 代理相關命令會增加 cluster 標記( 但是也有部分個例,例如 findAndModify 對應類命就沒帶改標記 )

此外,也有部分mongos mongod 例項命令不滿足上面的命名規範,例如 "dropIndexes" "createIndexes" "reIndex" "create" "renameCollection" 等命令,各自命名規則如下 :

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

命令名

mongod 例項對應命令檔案 / 命令類

mongos 例項對應命令檔案 / 命令類

dropIndexes

drop_indexes.cpp/(CmdDropIndexes)

commands_public.cpp/(DropIndexesCmd)

createIndexes

create_indexes.cpp(CmdCreateIndex)

commands_public.cpp/(CreateIndexesCmd)

reIndex

drop_indexes.cpp/(CmdReIndex)

commands_public.cpp/(ReIndexCmd)

create

Dbcommands.cpp/(CmdCreate)

commands_public.cpp/(CreateCmd)

renameCollection

rename_collection_cmd.cpp/

(CmdRenameCollection)

commands_public.cpp/(RenameCollectionCmd)

......

......

......

如上,絕大多數mongos 命令原始碼檔案和命令實現類命名相比 mongod 例項,都帶有 cluster 標識,但是還是有部分命令命名不準尋該規則。如果想知道某個命令的原始碼實現檔案,可以在前面提到的三個例項中搜尋相應字串即可定位到。注意:搜尋的時候需要帶上雙引號。

5.2 mongod( configServer ) 特有命令命名規則

mongos 命名規則類似, configServer 支援的獨有命令原始碼檔案命名規則相比 shardServer 增加了 ”configsvr” 特性,從原始碼檔名即可明顯的看出是configServer 獨有的命令。

此外,命令對應類命命名也帶有 ”ConfigSvr” 特性,例如class ConfigSvrAddShardCommand{} class ConfigSvrMoveChunkCommand{} 等,命名規則和 mongos 代理支援的 command 命名規則類似。

5.3 命名規則總結

上面的命名規則可以總結為如下圖解資訊:

mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二 mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二

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