mongodb 源碼實現系列 - command 命令處理模塊源碼實現二
{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"關於作者","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 前滴滴出行技術專家,現任OPPO文檔數據庫mongodb負責人,負責oppo千萬級峯值TPS/十萬億級數據量文檔數據庫mongodb內核研發及運維工作,一直專注於分佈式緩存、高性能服務端、數據庫、中間件等相關研發。後續持續分享《MongoDB內核源碼設計、性能優化、最佳運維實踐》,Github賬號地址:","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/y123456yz","title":null},"content":[{"type":"text","marks":[{"type":"underline","attrs":{}}],"text":"https://github.com/y123456yz","attrs":{}}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"背景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" <>中分享了mongodb內核底層網絡IO處理相關實現,包括套接字初始化、一個完整mongodb報文的讀取、獲取到DB數據發送給客戶端等。Mongodb支持多種增、刪、改、查、聚合處理、cluster處理等操作,每個操作在內核實現中對應一個command,每個command有不同的功能,mongodb內核如何進行command源碼處理將是本文分析的重點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,mongodb提供了mongostat工具來監控當前集羣的各種操作統計。Mongostat監控統計如下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/05/05182f8de9521d9d4ca86389914aa5db.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 其中,insert、delete、update、query這四項統計比較好理解,分別對應增、刪、改、查。但是,comand、getmore不是很好理解,command代表什麼統計?getMore代表什麼統計?,這兩項相對比較難理解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 此外,通過本文字分析,我們將搞明白這六項統計的具體含義,同時弄清這六項統計由那些操作進行計數。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1. 《Command命令處理模塊一》回顧","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 《Mongodb command命令處理模塊源碼實現一》中我們分析了一個客戶端請求到來後,mognodb服務端大體處理流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① 從message中解析初報文頭部,從而確定一個完整的mongodb報文","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② 從body中解析初OpCode操作碼信息,3.6版本默認OpCode操作碼爲OP_MSG","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③ 根據解析初的OP_MSG操作碼,構造對應OpMsg類,真實命令請求以bson數據格式保存在該類成員body中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"④ 從body中解析出command命令字符串信息(如“insert”、“update”等)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑤ 從全局_commands map表中查找是否支持該命令,如果支持則執行該命令處理,如果不支持則直接報錯提示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑥ 最終找到對應command命令後,執行command的功能run接口。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Mongodb內核支持的command命令信息保存在一個全局map表_commands中,從命令請求bson中解析出command命令字符串後,就是從該全局map表查找,如果找到該命令則說明mongodb支持該命令,找不到則說明不支持,整個過程歸納爲下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ef/efc040eb2e9f6a9947b61bc25f6cde82.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 從OpMsg類中解析出命令名字符串後(例如:”insert”、”delete”等),從全局map表_commands查找,找到則執行對應命令。如果找不到,說明不支持該命令操作,進行異常提示處理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Mongodb不同實例支持那些command命令完全取決於全局map表_commands,下面繼續分析該全局map來源。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"2. Command命令處理模塊源碼目錄結構","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongodb集羣中通常包含3種節點實例角色:mongos、mongod(ShardServer)、mongod(ConfigServer)。這3種實例校色功能如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① Mongos:代理,從shardServer獲取路由信息,轉發客戶端請求到shard。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② mongod(ShardServer):數據存儲節點,所有客戶端數據記錄到shard中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③ mongod(ConfigServer):記錄數據路由信息以及一些元數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Mongos代理進程名唯一,也就是”mongos”,代理mongos支持的命令信息比較好確認。但是ShardServer和ConfigServer的進程名都是”mongod”,如何區分各自支持那些命令呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" configServer實際上是一種特殊的shardServer,它擁有shard數據分片的功能外,還擁有特殊的元數據管理功能,例如記錄chunk元數據信息、mongos信息、分片操作日誌信息等。因此,configServer除了支持shardServer的命令外,還會支持更多的特有命令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongos代理支持的命令信息全部在src/mongo/s/commands目錄中實現,源碼文件如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e1/e1148c5bef1781bfe1e1d58e5ebe0949.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongod(shardServer)支持的命令信息全部在src/mongo/db/commands目錄中實現,源碼文件如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a0/a049a8cb6a558f373dde269af50f50e2.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongod(configServer)幾乎支持所有shardServer支持的命令(說明:也有個別別特例,如”mapreduce.shardedfinish”),還支持特有的一些命令,這些特意命令在src/mongo/db/s/config目錄中實現,源碼文件如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/74/746f20bfd9c6dc98e8524ebef3322d6e.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 從上面的不同實例支持命令的源碼目錄文件可以看出,mongodb內核源碼設計之優秀,從目錄結構即可一眼確定不同實例角色支持的各自不同命令信息,代碼可讀性非常好。目錄結構可以總結爲下表:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8d/8d7a48875c12b7bd87edf86ebbee0a60.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" configServer和shardServer各自支持的命令範圍類似於下圖包含與被包含的關係,小橢圓代表shardServer,大圓代表configServer:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/14/1471a6cecd7ae1636d2d007b4aa93a05.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"3. command模塊類繼承關係","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 第2章節代碼目錄結構可以看出,絕大部分命令功能由對應源碼文件實現,例如find_cmd.cpp源碼文件進行find”命令處理。此外,也有部分源碼文件,一個文件對應多個命令實現,例如write_commands.cpp源碼文件,同時負責”insert”、”update”、”delete”增刪改處理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 由於命令衆多,瞭解了代碼目錄結構後,在進行核心代碼分析前,我們先了解一下command類的各種繼承關係。不同命令有不同功能,也就需要不同的實現,但是所有命令也會有一些共同的接口特性,例如該命令是否需要認證、是否支持從節點操作、是否支持WriteConcern操作等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 不同command命令有相同的共性,也會有各自不同的獨有特性。所以,mongodb在源碼實現中充分考慮了這些問題,抽象出一些共有的特性接口由基類實現,command用於的一些獨有的特性,則在繼承類中實現。command命令處理模塊相關核心源碼類主要繼承關係圖如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9d/9dad98780a07c6f1c761b85b7b52f77f.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 如上圖,command命令處理模塊相關實現類按照父子繼承關係可以包含四層,每層功能說明如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① CommandInterface類:虛擬接口類,只定義虛擬接口,不做具體實現。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② Command類:完成一些基本功能檢查,例如是否支持從節點操作、是否需要認證、是否支持WriteConcern、獲取命令名、是否只能在admin庫操作等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③ BasicCommand類:認證相關接口實現、定義虛擬run接口。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"④ 具體命令類:每個命令都有一個相應的類定義,都是在該層實現,真正的命令run接口實現在該層完成。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"4. command命令註冊核心代碼實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 前面分析提到,當解析到對應命令字符串(如:”insert”、”update”等)後,從全局map表中_commands查找,找到說明支持該命令,找不到則不支持。全局_commands表中保存了實例支持的command命令信息,不同命令需要提前註冊到該map表中,註冊方式有兩種:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① 每個命令定義一個對應全局類變量","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② new()一個該命令類信息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 類註冊過程源碼實現由command類初始化構造接口完成,註冊過程核心代碼如下所示:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.//命令註冊,所有註冊的命令最終全部保存到_commands全局map表中 \n2.//name和oldName實際上是同一個command,只是可能因爲歷史原因,命令名改名了 \n3.Command::Command(StringData name, StringData oldName) \n4. //命令名字符串 \n5. : _name(name.toString()), \n6. //對應命令執行統計,total代表總的,failed代表執行失敗的次數 \n7. _commandsExecutedMetric(\"commands.\" + _name + \".total\", &_commandsExecuted), \n8. _commandsFailedMetric(\"commands.\" + _name + \".failed\", &_commandsFailed) { \n9. //如果_commands map表還沒有生成,則new一個 \n10. if (_commands == 0) \n11. _commands = new CommandMap(); \n12. ...... \n13. //把name命令對應的command添加到map表中 \n14. Command*& c = (*_commands)[name]; \n15. if (c) \n16. log() <; \n10. ...... \n11. //獲取命令名 \n12. const std::string& getName() const final { \n13. return _name; \n14. } \n15. ...... \n16. //應答保留填充字段長度 \n17. std::size_t reserveBytesForReply() const override { \n18. return 0u; \n19. } \n20. //該命令是否只能在admin庫執行,默認不可以 \n21. bool adminOnly() const override { \n22. return false; \n23. } \n24. //該命令是否需要權限認證檢查?默認不需要 \n25. bool localHostOnlyIfNoAuth() override { \n26. return false; \n27. } \n28. //該命令執行後是否進行command操作計數 \n29. bool shouldAffectCommandCounter() const override { \n30. return true; \n31. } \n32. //該命令是否需要認證 \n33. bool requiresAuth() const override { \n34. return true; \n35. } \n36. //help幫助信息 \n37. void help(std::stringstream& help) const override; \n38. //執行計劃信息 \n39. Status explain(...) const override; \n40. //日誌信息相關 \n41. void redactForLogging(mutablebson::Document* cmdObj) override; \n42. BSONObj getRedactedCopyForLogging(const BSONObj& cmdObj) override; \n43. //該命令是否爲maintenance模式,默認false \n44. bool maintenanceMode() const override { \n45. return false; \n46. } \n47. //maintenance是否支持,默認支持 \n48. bool maintenanceOk() const override { \n49. return true; \n50. } \n51. //本地是否支持非本地ReadConcern,默認不支持 \n52. bool supportsNonLocalReadConcern(...) const override { \n53. return false; \n54. } \n55. //是否允許AfterClusterTime,默認允許 \n56. bool allowsAfterClusterTime(const BSONObj& cmdObj) const override { \n57. return true; \n58. } \n59. //3.6版本默認opCode=OP_MSG,所以對應邏輯操作op爲LogicalOp::opCommand \n60. LogicalOp getLogicalOp() const override { \n61. return LogicalOp::opCommand; \n62. } \n63. //例如find就是kRead,update delete insert就是kWrite,非讀寫操作就是kCommand \n64. ReadWriteType getReadWriteType() const override { \n65. return ReadWriteType::kCommand; \n66. } \n67. //該命令執行成功統計 \n68. void incrementCommandsExecuted() final { \n69. _commandsExecuted.increment(); \n70. } \n71. \n72. //該命令執行失敗統計 \n73. void incrementCommandsFailed() final { \n74. _commandsFailed.increment(); \n75. } \n76. \n77. //真正得命令運行 \n78. bool publicRun(OperationContext* opCtx, const OpMsgRequest& request, BSONObjBuilder& result); \n79. \n80. //獲取支持的所有命令信息 ListCommandsCmd獲取所有支持的命令 db.listCommands() \n81. static const CommandMap& allCommands() { \n82. return *_commands; \n83. } \n84. \n85. //沒用 \n86. static const CommandMap& allCommandsByBestName() { \n87. return *_commandsByBestName; \n88. } \n89. \n90. //收到不支持命令的統計,例如mongo shell敲一個mongodb無法識別得命令,這裏就會統計出來 \n91. static Counter64 unknownCommands; \n92. //根據命令字符串名查找對應命令 \n93. static Command* findCommand(StringData name); \n94. //執行結果 \n95. static void appendCommandStatus(...); \n96. //是否啓用了command test功能 \n97. static bool testCommandsEnabled; \n98. //help幫助信息 \n99. static bool isHelpRequest(const BSONElement& helpElem); \n100. static const char kHelpFieldName[]; \n101. //認證檢查,檢查是否有執行該命令得權限 \n102. static Status checkAuthorization(Command* c, \n103. OperationContext* opCtx, \n104. const OpMsgRequest& request); \n105. ...... \n106.private: \n107. //添加地方見Command::Command( \n108. //所有的command都在_commands中保存 \n109. static CommandMap* _commands; \n110. //暫時沒用 \n111. static CommandMap* _commandsByBestName; \n112. //執行對應命令run接口 \n113. virtual bool enhancedRun(OperationContext* opCtx, \n114. const OpMsgRequest& request, \n115. BSONObjBuilder& result) = 0; \n116. //db.serverStatus().metrics.commands命令查看,本命令的執行統計,包括執行成功和執行失敗的 \n117. Counter64 _commandsExecuted; \n118. Counter64 _commandsFailed; \n119. //命令名,如\"find\" \"insert\" \"update\" \"createIndexes\" \"deleteIndexes\" \n120. const std::string _name; \n121. \n122. //每個命令執行是否成功通過MetricTree管理起來,也就是db.serverStatus().metrics.commands統計信息 \n123. //通過MetricTree特殊二叉樹管理起來 \n124. ServerStatusMetricField _commandsExecutedMetric; \n125. ServerStatusMetricField _commandsFailedMetric; \n126.}; ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" command作爲默認接口類,主要完成一些命令基本接口初始化操作及默認配置設置,該類最基本的接口主要如下:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"reserveBytesForReply","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ReserveBytesForReply()接口主要完成該命令應答填充字段長度,默認值爲0。對應命令可以在具體命令類中修改。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"adminOnly","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 該命令是否只能在admin庫操作,默認爲false。也可以在對應命令繼承類中修改,例如\"moveChunk\"命令則在MoveChunkCommand繼承類中設置爲true,也就是該命令只能在admin庫操作。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"localHostOnlyIfNoAuth","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 該命令是否支持在實例所在本機不認證操作,默認值false。對應命令可以在具體繼承類中修改。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"l shouldAffectCommandCounter","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 該命令是否需要command統計,也就是mongostat中的command統計計數是否需要使能。默認值true,也就是該命令會進行command計數統計。對應命令可以在具體繼承類中修改。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"requiresAuth","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 該命令是否需要認證,默認爲true。對應命令可以在具體繼承類中修改。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"allowsAfterClusterTime","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 該命令是否支持AfterClusterTime,默認爲true。對應命令可以在具體繼承類中修改。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"getLogicalOp","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 該命令是否爲邏輯opCommand命令。3.6版本默認opCode=OP_MSG,所以對應邏輯操作op爲LogicalOp::opCommand。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"getReadWriteType","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 如果爲讀命令則type對應kRead,寫命令type對應kWrite,其他讀寫以外的命令對應kCommand。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"incrementCommandsExecuted","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 該命令執行成功統計,通過db.serverStatus().metrics.commands獲取該命令統計。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"_commandsFailed","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 該命令執行失敗統計,通過db.serverStatus().metrics.commands獲取該命令統計。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上列舉除了command基類的幾個核心功能默認值信息,如果繼承類中沒有修改這些接口值,則該命令對應功能就是這些默認值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":" 說明:","attrs":{}},{"type":"text","text":"各種不同命令如果不適用command基類的默認接口,則可以在繼承類中修改對應接口值即可更改對應功能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 命令除了上面提到的基本功能是否支持外,command類還有其他幾個核心接口功能。例如,該命令是否認證成功、是否有操作權限、允許對應run命令等。command類函數接口功能總結如下表所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b8/b84e51f5d800e358f33c13c194b22707.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"7. 命令run","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 結合《命令處理模塊源碼實現一》和本章節對command處理流程可以得出,runCommandImpl接口通過如下調用流程最終執行特定命令的run接口,這裏以insert寫入和讀取流程爲例,mongod實例寫入調用過程如下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/95/955d985bec1064f7cefa226c905c08fe.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 最終,mongod和mongos實例調用相關命令得run接口完成具體的command命令處理操作。mongos、mongod(shardServer)、mongod(configServer)相關常用的操作命令(以最基本的讀寫命令爲例)入口及功能說明總結如下表所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/41/41aae387bf0c0228ad76f785ac56e241.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"8. command模塊統計信息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongodb command命令處理模塊相關統計包含三類:單個命令統計、彙總型統計、讀寫時延統計。其中,單個命令統計針對所有接受到的命令名字符串進行統計,彙總型統計則是把同一類型的命令總結爲一個整體統計(例如commands統計)。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"8.1單個命令統計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongodb會收集所有操作命令執行結果,如果本次命令執行成功,則該命令成功統計自增加1,同理如果該命令執行過程失敗,則失敗統計自增加1,這些統一歸類爲”單個命令統計信息”。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 單個命令統計由command類的_commandsExecuted和_commandsFailed實現命令執行成功統計和失敗統計,相關核心代碼實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.//該命令執行成功統計 \n2.void incrementCommandsExecuted() final { \n3. _commandsExecuted.increment(); \n4.} \n5. \n6.//該命令執行失敗統計 \n7.void incrementCommandsFailed() final { \n8. _commandsFailed.increment(); \n9.} \n\n1.//命令入口 \n2.void execCommandDatabase(...) \n3.{ \n4. ...... \n5. //該命令執行次數統計 db.serverStatus().metrics.commands可以獲取統計信息 \n6. command->incrementCommandsExecuted(); \n7. ...... \n8. //真正的命令執行在這裏面 \n9. retval = runCommandImpl(opCtx, command, request, replyBuilder, startOperationTime); \n10. \n11. //該命令失敗次數統計 \n12. if (!retval) { \n13. command->incrementCommandsFailed(); \n14. } \n15. ...... \n16.} ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongodb默認會統計每個客戶端發往服務端的命令,即使是無法識別的命令也會統計,命令統計可以通過db.serverStatus().metrics.commands獲取,如下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a3/a392161d98367db2101cb0abe9c0e425.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"8.2彙總型commands命令統計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 從前面的單個命令統計可以看出,單個命令會記錄所有發送給mongodb的命令信息。mongodb支持的命令百餘個,由於命令衆多,因此mongodb爲了更加直觀明瞭的獲取統計信息,除了提供單個命令統計外,還對外提供彙總型命令統計。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 彙總型命令統計可以通過db.serverStatus().opcounters命令獲取,mongostat中的增刪改查等信息也來自於該統計,如下圖:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/05/05182f8de9521d9d4ca86389914aa5db.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 從上圖可以看出,整個mongostat監控統計可以歸類爲小表:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b1/b113916ad98106ecab53af67351ee7f0.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" insert、delete、update、find分別對應增刪改查四個命令操作,getMore對應批量遊標操作命令。這五個命令,對應命令執行的時候統計信息自增,核心代碼實現如下:","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"8.2.1 insert操作統計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" insert操作統計在代理mongos和分片存儲節點mongod都會統計,兩種角色的insert統計核心代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 代理mongos insert統計核心代碼實現","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1. bool insertBatchAndHandleErrors(...) { \n2. ...... \n3. //一次性一條一條插入,上面的固定集合是一次性插入 \n4. for (auto it = batch.begin(); it != batch.end(); ++it) { \n5. //insert操作計數 \n6. globalOpCounters.gotInsert(); \n7. } \n8. ...... \n9.} ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 分片存儲節點mongod insert統計核心代碼實現","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.//mongod代理insert統計核心流程 \n2.bool ClusterWriteCmd::enhancedRun(...) { \n3. ...... \n4. if (_writeType == BatchedCommandRequest::BatchType_Insert) { \n5. //insert計數 \n6. for (size_t i = 0; i shouldAffectCommandCounter()) { \n5. OpCounters* opCounters = &globalOpCounters; \n6. opCounters->gotCommand(); \n7. } \n8. ...... \n9.} ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 存儲節點mongod commands統計核心代碼實現:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.void execCommandDatabase(...) { \n2. ...... \n3. //是否進行command統計 \n4. if (command->shouldAffectCommandCounter()) { \n5. OpCounters* opCounters = &globalOpCounters; \n6. //commands計數自增\n7. opCounters->gotCommand(); \n8. } \n9. ...... \n10.} ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 從上面的代碼可以看出,只有對應命令類中shouldAffectCommandCounter()爲true的命令纔會進行commands計數。前面章節中我們提到,所有命令都有一個對應類實現相應功能,所有命令實現類都繼承一個功能class command {}類,該類對shouldAffectCommandCounter()接口進行初始化。代碼實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.class Command : { \n2. ...... \n3. //該命令是否進行command操作計數,默認需要。如果不需要進行command統計,可在命令繼承類中置爲false \n4. bool shouldAffectCommandCounter() const override { \n5. return true; \n6. } \n7. ...... \n8.} ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 該接口默認爲true,如果對應命令不需要進行commands計數統計,則需要在對應命令實現類中把該接口置爲false。通過分析代碼,可以看出,只有以下命令子類把shouldAffectCommandCounter()接口設置爲false,搜索結果如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bd/bd462ab5621a43385fda68149bcbedfe.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 分析代碼可以得出如下結論:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1) mongos代理中的clase Cluster","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"find","attrs":{}},{"type":"text","text":"cmd { }類和class Cluster","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"getmore","attrs":{}},{"type":"text","text":"cmd {}類的shouldAffectCommandCounter()接口置爲false,這兩個類分別對應代理的“find”和“getMore”命令操作,也就說明mongos代理de這兩個命令操作不會統計到commands中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2) mongod分片存儲節點的clase Find","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"cmd{}、class Getmore","attrs":{}},{"type":"text","text":"cmd {}、class Write_commands{}三個類中把shouldAffectCommandCounter()接口置爲false。這三個類分別對應mongod存儲實例的如下幾個命令:“find”、“getMore”、“insert”、“update”、“delete”五個命令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongos和mongod實例commands統計信息總結如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/52/526e7e8e2b5fecadda59edfd08e34d89.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"8.3慢日誌、時延統計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 每次客戶端請求執行實踐如果超過了log level配置的最大慢日誌時間,則會把該操作詳細信息記錄下來,同時把本操作執行時間添加到對應的讀或者寫計數及時延統計中。命令處理模塊中,時延相關統計包括以下兩種統計:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① 慢日誌統計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② 讀寫計數及時延統計","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"8.3.1慢日誌統計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 當啓用了慢日誌記錄功能後,mongod會把執行時間超過指定閥值的慢日誌記錄下來。慢日誌默認記錄到服務日誌文件(","attrs":{}},{"type":"link","attrs":{"href":"#systemLog.path","title":"systemLog.path"},"content":[{"type":"text","text":"systemLog.path","attrs":{}}]},{"type":"text","text":"配置項設置),同時會記錄日誌到”system.profile”集合中。慢日誌核心代碼實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.DbResponse ServiceEntryPointMongod::handleRequest(...) { \n2. ...... \n3. //記錄開始時間 \n4. //獲取當前操作對應curop \n5. CurOp& currentOp = *CurOp::get(opCtx); \n6. \n7. ...... \n8. //執行請求對應命令 \n9. runCommands(opCtx, m); \n10. ...... \n11. \n12. //記錄結束時間 \n13. currentOp.ensureStarted(); \n14. currentOp.done(); \n15. //獲取開始和結束時間差,也就是命令執行時間 \n16. debug.executionTimeMicros = durationCount \n17. (currentOp.elapsedTimeExcludingPauses()); \n18. //記錄超過閥值的慢日誌到日誌文件 \n19. if (shouldLogOpDebug || (shouldSample && debug.executionTimeMicros > logThresholdMs * 1000LL)) { \n20. ...... \n21. //記錄慢日誌到日誌文件 \n22. log() < \n17. (currentOp.elapsedTimeExcludingPauses()); \n18. ...... \n19. //記錄慢日誌到system.profile集合 \n20. //mongod讀寫的時間延遲統計 db.serverStatus().opLatencies獲取 \n21. Top::get(opCtx->getServiceContext()) \n22. .incrementGlobalLatencyStats( //讀寫統計\n23. opCtx, \n24. //時延 \n25. durationCount(currentOp.elapsedTimeExcludingPauses()), \n26. currentOp.getReadWriteType()); //讀寫類型\n27. ...... \n28.} ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongod實例讀、寫、command操作計數及其各自時延統計可以通過db.serverStatus()接口獲取,用戶可用採樣來計算對應的tps和平均時延信息。獲取操作統計和時延統計的命令如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" db.serverStatus().opLatencies","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"9. 問題回顧","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 《Mongodb command命令處理模塊源碼實現一》一文中提到的commands統計信息到這裏就可以得到答案了,如下表所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongos和mongod實例commands統計信息總結如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/52/526e7e8e2b5fecadda59edfd08e34d89.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f8/f8a71989227db75e6e47e6d1b25a2899.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"mongos代理mongostat統計可以彙總爲下圖所示:","attrs":{}}]}],"attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/45/459c1cb5c532bada32710c819822374c.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"mongod代理mongostat統計可以彙總爲下圖所示:","attrs":{}}]}],"attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/22/22e6e893c363ef5f7cd256eea54b0e31.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.