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}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章