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":"1. 背景","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":null,"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Command命令處理模塊分爲:mongos操作命令、mongod操作命令、mongodb集羣內部命令,具體定義如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① mongos操作命令,客戶端可以通過mongos訪問集羣相關的命令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② mongod操作命令:客戶端可以通過mongod複製集和cfg server訪問集羣的相關命令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③ mongodb集羣內部命令:mongos、mongod、mongo-cfg集羣實例之間交互的命令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"     Command命令處理模塊核心代碼實現如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c3/c3f5d427967a6604c8774df314678a93.png","alt":null,"title":null,"style":null,"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/8d/8def4e17ddb6debddc89c6128e5c5ff4.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"     《command命令處理模塊源碼實現》相關文章重點分析命令處理模塊核心代碼實現,也就是上面截圖中的命令處理源碼文件實現。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"2. <>銜接回顧","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" <>一文中,我們對service_state_machine狀態機調度子模塊進行了分析,該模塊中的dealTask任務進行mongodb內部業務邏輯處理,其核心實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.//dealTask處理  \n2.void ServiceStateMachine::_processMessage(ThreadGuard guard) {  \n3. ......\n4.    //command處理、DB訪問後的數據通過dbresponse返回  \n5.    DbResponse dbresponse = _sep->handleRequest(opCtx.get(), _inMessage);  \n6. ......\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 上面的","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"sep對應mongod或者mongos實例的服務入口實現,該","attrs":{}},{"type":"text","text":"seq成員分別在如下代碼中初始化爲ServiceEntryPointMongod和ServiceEntryPointMongod類實現。SSM狀態機的_seq成員初始化賦值核心代碼實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.//mongos實例啓動初始化  \n2.static ExitCode runMongosServer() {  \n3.    ......  \n4.    //mongos實例對應sep爲ServiceEntryPointMongos  \n5.    auto sep = stdx::make_unique(getGlobalServiceContext());  \n6.    getGlobalServiceContext()->setServiceEntryPoint(std::move(sep));  \n7.    ......  \n8.}  \n9.  \n10.//mongod實例啓動初始化  \n11.ExitCode _initAndListen(int listenPort) {  \n12.    ......  \n13.    //mongod實例對應sep爲ServiceEntryPointMongod  \n14.    serviceContext->setServiceEntryPoint(  \n15.        stdx::make_unique(serviceContext));  \n16.    ......  \n17.}  \n18.  \n19.//SSM狀態機初始化  \n20.ServiceStateMachine::ServiceStateMachine(...)  \n21.    : _state{State::Created},  \n22.      //mongod和mongos實例的服務入口通過這裏賦值給_seq成員變量  \n23.      _sep{svcContext->getServiceEntryPoint()},  \n24.      ......  \n25.} ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 通過上面的幾個核心接口把mongos和mongod實例的服務入口與狀態機SSM(ServiceStateMachine)聯繫起來,最終和下面的command命令處理模塊關聯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"  dealTask進行一次mongodb請求的內部邏輯處理,該處理由_sep->handleRequest()接口實現。由於mongos和mongod服務入口分別由ServiceEntryPointMongos和ServiceEntryPointMongod兩個類實現,因此dealTask也就演變爲如下接口處理:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① mongos實例:ServiceEntryPointMongos::handleRequest(...)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② Mongod實例::ServiceEntryPointMongod::handleRequest(...)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 這兩個接口入參都是OperationContext和Message,分別對應操作上下文、請求原始數據內容。下文會分析Message解析實現、OperationContext服務上下文實現將在後續章節分析。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Mongod和mongos實例服務入口類都繼承自網絡傳輸模塊中的ServiceEntryPointImpl類,如下圖所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9c/9c8da6e726550d847bc2a8b266a1fbbb.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Tips: mongos和mongod服務入口類爲何要繼承網絡傳輸模塊服務入口類?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 原因是一個請求對應一個鏈接session,該session對應的請求又和SSM狀態機唯一對應。所有客戶端請求對應的SSM狀態機信息全部保存再ServiceEntryPointImpl._sessions成員中,而command命令處理模塊爲SSM狀態機任務中的dealTask任務,通過該繼承關係,ServiceEntryPointMongod和ServiceEntryPointMongos子類也就可以和狀態機及任務處理關聯起來,同時也可以獲取當前請求對應的session鏈接信息。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"3. Mongodb協議解析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 在《transport_layer網絡傳輸層模塊源碼實現二》中的數據收發子模塊完成了一個完整mongodb報文的接收,一個mongodb報文由Header頭部+opCode包體組成,如下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/aa/aa6ca4e0c1ac1cb244059055d43d6b25.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 上圖中各個字段說明如下表:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fd/fdd17cbdb587077176b3367627126d02.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" opCode取值比較多,早期版本中OP","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"INSERT、OP","attrs":{}},{"type":"text","text":"DELETE、OP","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"UPDATE、OP","attrs":{}},{"type":"text","text":"QUERY分別針對增刪改查請求,Mongodb從3.6版本開始默認使用OP","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"MSG操作作爲默認opCode,是一種可擴展的消息格式,旨在包含其他操作碼的功能,新版本讀寫請求協議都對應該操作碼。本文以OP","attrs":{}},{"type":"text","text":"MSG操作碼對應協議爲例進行分析,其他操作碼協議分析過程類似,OP_MSG請求協議格式如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.OP_MSG {  \n2.    //mongodb報文頭部  \n3.    MsgHeader header;            \n4.    //位圖,用於標識報文是否需要校驗 是否需要應答等  \n5.    uint32 flagBits;           // message flags  \n6.    //報文內容,例如find write等命令內容通過bson格式存在於該結構中  \n7.    Sections[] sections;       // data sections  \n8.    //報文CRC校驗  \n9.    optional checksum; // optional CRC-32C checksum  \n}  ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" OP_MSG各個字段說明如下表:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e2/e2fcd04848d86890c39e68e083506d50.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 一個完整OP_MSG請求格式如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/be/be8074e3f3b535c63ed4d3023163d5c9.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 除了通用頭部header外,客戶端命令請求實際上都保存於sections字段中,該字段存放的是請求的原始bson格式數據。BSON是由10gen開發的一個數據格式,目前主要用於MongoDB中,是MongoDB的數據存儲格式。BSON基於JSON格式,選擇JSON進行改造的原因主要是JSON的通用性及JSON的schemaless的特性。BSON相比JSON具有以下特性:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① Lightweight(更輕量級)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② Traversable(易操作)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③ Efficient(高效性能)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 本文重點不是分析bson協議格式,bson協議實現細節將在後續章節分享。bson協議更多設計細節詳見:","attrs":{}},{"type":"link","attrs":{"href":"http://bsonspec.org/","title":null},"content":[{"type":"text","text":"http://bsonspec.org/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 總結:一個完整mongodb報文由header+body組成,其中header長度固定爲16字節,body長度等於messageLength-16。Header部分協議解析由message.cpp和message.h兩源碼文件實現,body部分對應的OP_MSG類請求解析由op_msg.cpp和op_msg.h兩源碼文件實現。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"4. mongodb報文通用頭部解析及封裝源碼實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Header頭部解析由src/mongo/util/net目錄下message.cpp和message.h兩文件完成,該類主要完成通用header頭部和body部分的解析、封裝。因此報文頭部核心代碼分爲以下兩類:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① 報文頭部內容解析及封裝(MSGHEADER命名空間實現)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② 頭部和body內容解析及封裝(MsgData命名空間實現)","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4.1 mongodb報文頭部解析及封裝核心代碼實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" mongodb報文頭部解析由namespace MSGHEADER {...}實現,該類主要成員及接口實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.namespace MSGHEADER {  \n2.//header頭部各個字段信息  \n3.struct Layout {  \n4.    //整個message長度,包括header長度和body長度  \n5.    int32_t messageLength;     \n6.    //requestID 該請求id信息  \n7.    int32_t requestID;         \n8.    //getResponseToMsgId解析  \n9.    int32_t responseTo;        \n10.    //操作類型:OP_UPDATE、OP_INSERT、OP_QUERY、OP_DELETE、OP_MSG等  \n11.    int32_t opCode;  \n12.};  \n13.  \n14.//ConstView實現header頭部數據解析  \n15.class ConstView {   \n16.public:  \n17.    ......  \n18.    //初始化構造  \n19.    ConstView(const char* data) : _data(data) {}  \n20.    //獲取_data地址  \n21.    const char* view2ptr() const {  \n22.        return data().view();  \n23.    }  \n24.    //TransportLayerASIO::ASIOSourceTicket::_headerCallback調用  \n25.    //解析header頭部的messageLength字段  \n26.    int32_t getMessageLength() const {  \n27.        return data().read>(offsetof(Layout, messageLength));  \n28.    }  \n29.    //解析header頭部的requestID字段  \n30.    int32_t getRequestMsgId() const {  \n31.        return data().read>(offsetof(Layout, requestID));  \n32.    }  \n33.    //解析header頭部的getResponseToMsgId字段  \n34.    int32_t getResponseToMsgId() const {  \n35.        return data().read>(offsetof(Layout, responseTo));  \n36.    }  \n37.    //解析header頭部的opCode字段  \n38.    int32_t getOpCode() const {  \n39.        return data().read>(offsetof(Layout, opCode));  \n40.    }  \n41.  \n42.protected:  \n43.    //mongodb報文數據起始地址  \n44.    const view_type& data() const {  \n45.        return _data;  \n46.    }  \n47.private:  \n48.    //數據部分  \n49.    view_type _data;  \n50.};  \n51.  \n52.//View填充header頭部數據  \n53.class View : public ConstView {  \n54.public:  \n55.    ......  \n56.    //構造初始化  \n57.    View(char* data) : ConstView(data) {}  \n58.    //header起始地址  \n59.    char* view2ptr() {  \n60.        return data().view();  \n61.    }  \n62.    //以下四個接口進行header填充  \n63.    //填充header頭部messageLength字段  \n64.    void setMessageLength(int32_t value) {  \n65.        data().write(tagLittleEndian(value), offsetof(Layout, messageLength));  \n66.    }  \n67.    //填充header頭部requestID字段  \n68.    void setRequestMsgId(int32_t value) {  \n69.        data().write(tagLittleEndian(value), offsetof(Layout, requestID));  \n70.    }  \n71.    //填充header頭部responseTo字段  \n72.    void setResponseToMsgId(int32_t value) {  \n73.        data().write(tagLittleEndian(value), offsetof(Layout, responseTo));  \n74.    }  \n75.    //填充header頭部opCode字段  \n76.    void setOpCode(int32_t value) {  \n77.        data().write(tagLittleEndian(value), offsetof(Layout, opCode));  \n78.    }  \n79.private:  \n80.    //指向header起始地址  \n81.    view_type data() const {  \n82.        return const_cast(ConstView::view2ptr());  \n83.    }  \n84.};  \n85.}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 從上面的header頭部解析、填充的實現類可以看出,header頭部解析由MSGHEADER::ConstView實現;header頭部填充由MSGHEADER::View完成。實際上代碼實現上,通過offsetof來進行移位,從而快速定位到頭部對應字段。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4.2 mongodb報文頭部+body解析封裝核心代碼實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Namespace MSGHEADER{...}命名空間只負責header頭部的處理,namespace MsgData{...}命名空間相對MSGHEADER命名空間更加完善,除了處理頭部解析封裝外,還負責body數據起始地址維護、body數據封裝、數據長度檢查等。MsgData命名空間核心代碼實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.namespace MsgData {  \n2.struct Layout {  \n3.    //數據填充組成:header部分  \n4.    MSGHEADER::Layout header;  \n5.    //數據填充組成: body部分,body先用data佔位置  \n6.    char data[4];  \n7.};  \n8.  \n9.//解析header字段信息及body其實地址信息  \n10.class ConstView {  \n11.public:  \n12.    //初始化構造  \n13.    ConstView(const char* storage) : _storage(storage) {}  \n14.    //獲取數據起始地址  \n15.    const char* view2ptr() const {  \n16.        return storage().view();  \n17.    }  \n18.  \n19.    //以下四個接口間接執行前面的MSGHEADER中的頭部字段解析  \n20.    //填充header頭部messageLength字段  \n21.    int32_t getLen() const {  \n22.        return header().getMessageLength();  \n23.    }  \n24.    //填充header頭部requestID字段  \n25.    int32_t getId() const {  \n26.        return header().getRequestMsgId();  \n27.    }  \n28.    //填充header頭部responseTo字段  \n29.    int32_t getResponseToMsgId() const {  \n30.        return header().getResponseToMsgId();  \n31.    }  \n32.    //獲取網絡數據報文中的opCode字段  \n33.    NetworkOp getNetworkOp() const {  \n34.        return NetworkOp(header().getOpCode());  \n35.    }  \n36.    //指向body起始地址  \n37.    const char* data() const {  \n38.        return storage().view(offsetof(Layout, data));  \n39.    }  \n40.    //messageLength長度檢查,opcode檢查  \n41.    bool valid() const {  \n42.        if (getLen() <= 0 || getLen() > (4 * BSONObjMaxInternalSize))  \n43.            return false;  \n44.        if (getNetworkOp()  30000)  \n45.            return false;  \n46.        return true;  \n47.    }  \n48.    ......  \n49.protected:  \n50.    //獲取_storage  \n51.    const ConstDataView& storage() const {  \n52.        return _storage;  \n53.    }  \n54.    //指向header起始地址  \n55.    MSGHEADER::ConstView header() const {  \n56.        return storage().view(offsetof(Layout, header));  \n57.    }  \n58.private:  \n59.    //mongodb報文存儲在這裏  \n60.    ConstDataView _storage;  \n61.};  \n62.  \n63.//填充數據,包括Header和body  \n64.class View : public ConstView {  \n65.public:  \n66.    //構造初始化  \n67.    View(char* storage) : ConstView(storage) {}  \n68.    ......  \n69.    //獲取報文起始地址  \n70.    char* view2ptr() {  \n71.        return storage().view();  \n72.    }  \n73.  \n74.    //以下四個接口間接執行前面的MSGHEADER中的頭部字段構造  \n75.    //以下四個接口完成msg header賦值  \n76.    //填充header頭部messageLength字段  \n77.    void setLen(int value) {  \n78.        return header().setMessageLength(value);  \n79.    }  \n80.    //填充header頭部messageLength字段  \n81.    void setId(int32_t value) {  \n82.        return header().setRequestMsgId(value);  \n83.    }  \n84.    //填充header頭部messageLength字段  \n85.    void setResponseToMsgId(int32_t value) {  \n86.        return header().setResponseToMsgId(value);  \n87.    }  \n88.    //填充header頭部messageLength字段  \n89.    void setOperation(int value) {  \n90.        return header().setOpCode(value);  \n91.    }  \n92.  \n93.    using ConstView::data;  \n94.    //指向data  \n95.    char* data() {  \n96.        return storage().view(offsetof(Layout, data));  \n97.    }  \n98.private:  \n99.    //也就是報文起始地址  \n100.    DataView storage() const {  \n101.        return const_cast(ConstView::view2ptr());  \n102.    }  \n103.    //指向header頭部  \n104.    MSGHEADER::View header() const {  \n105.        return storage().view(offsetof(Layout, header));  \n106.    }  \n107.};  \n108.  \n109.......  \n110.//Value爲前面的Layout,減4是因爲有4字節填充data,所以這個就是header長度  \n111.const int MsgDataHeaderSize = sizeof(Value) - 4;  \n112.  \n113.//除去頭部後的數據部分長度  \n114.inline int ConstView::dataLen() const {   \n115.    return getLen() - MsgDataHeaderSize;  \n116.}  \n117.}  // namespace MsgData  ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"     和MSGHEADER命名空間相比,MsgData這個namespace命名空間接口實現和前面的MSGHEADER命名空間實現大同小異。MsgData不僅僅處理header頭部的解析組裝,還負責body部分數據頭部指針指向、頭部長度檢查、opCode檢查、數據填充等。其中,MsgData命名空間中header頭部的解析構造底層依賴MSGHEADER實現。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4.3 Message/DbMessage核心代碼實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 在《transport_layer網絡傳輸層模塊源碼實現二》中,從底層ASIO庫接收到的mongodb報文是存放在Message結構中存儲,最終存放在ServiceStateMachine._inMessage成員中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在前面第2章我們知道mongod和mongso實例的服務入口接口handleRequest(...)中都帶有Message入參,也就是接收到的Message數據通過該接口處理。Message類主要接口實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.//DbMessage._msg成員爲該類型  \n2.class Message {  \n3.public:  \n4.    //message初始化  \n5.    explicit Message(SharedBuffer data) : _buf(std::move(data)) {}  \n6.    //頭部header數據  \n7.    MsgData::View header() const {  \n8.        verify(!empty());  \n9.        return _buf.get();  \n10.    }  \n11.    //獲取網絡數據報文中的op字段  \n12.    NetworkOp operation() const {  \n13.        return header().getNetworkOp();  \n14.    }  \n15.    //_buf釋放爲空  \n16.    bool empty() const {  \n17.        return !_buf;  \n18.    }  \n19.    //獲取報文總長度messageLength  \n20.    int size() const {  \n21.        if (_buf) {  \n22.            return MsgData::ConstView(_buf.get()).getLen();  \n23.        }  \n24.        return 0;  \n25.    }  \n26.    //body長度  \n27.    int dataSize() const {  \n28.        return size() - sizeof(MSGHEADER::Value);  \n29.    }  \n30.    //buf重置  \n31.    void reset() {  \n32.        _buf = {};  \n33.    }  \n34.    // use to set first buffer if empty  \n35.    //_buf直接使用buf空間  \n36.    void setData(SharedBuffer buf) {  \n37.        verify(empty());  \n38.        _buf = std::move(buf);  \n39.    }  \n40.     //把msgtxt拷貝到_buf中  \n41.    void setData(int operation, const char* msgtxt) {  \n42.        setData(operation, msgtxt, strlen(msgtxt) + 1);  \n43.    }  \n44.    //根據operation和msgdata構造一個完整mongodb報文  \n45.    void setData(int operation, const char* msgdata, size_t len) {  \n46.        verify(empty());  \n47.        size_t dataLen = len + sizeof(MsgData::Value) - 4;  \n48.        _buf = SharedBuffer::allocate(dataLen);  \n49.        MsgData::View d = _buf.get();  \n50.        if (len)  \n51.            memcpy(d.data(), msgdata, len);  \n52.        d.setLen(dataLen);  \n53.        d.setOperation(operation);  \n54.    }  \n55.    ......  \n56.    //獲取_buf對應指針  \n57.    const char* buf() const {  \n58.        return _buf.get();  \n59.    }  \n60.  \n61.private:  \n62.    //存放接收數據的buf  \n63.    SharedBuffer _buf;  \n64.};  ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Message是操作mongodb收發報文最直接的實現類,該類主要完成一個完整mongodb報文封裝。有關mongodb報文頭後面的body更多的解析實現在DbMessage類中完成,DbMessage類包含Message類成員","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"msg。實際上,Message報文信息在handleRequest(...)實例服務入口中賦值給DbMessage.","attrs":{}},{"type":"text","text":"msg,報文後續的body處理繼續由DbMessage類相關接口完成處理。DbMessage和Message類關係如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.class DbMessage {  \n2.    ......  \n3.    //包含Message成員變量  \n4.    const Message& _msg;  \n5. //mongodb報文起始地址\n6. const char* _nsStart; \n7. //報文結束地址\n8. const char* _theEnd; \n9.}  \n10.  \n11.DbMessage::DbMessage(const Message& msg) : _msg(msg),   \n12.  _nsStart(NULL), _mark(NULL), _nsLen(0) {  \n13.    //一個mongodb報文(header+body)數據的結束地址  \n14.    _theEnd = _msg.singleData().data() + _msg.singleData().dataLen();  \n15.    //報文起始地址 [_nextjsobj, _theEnd ]之間的數據就是一個完整mongodb報文  \n16.    _nextjsobj = _msg.singleData().data();  \n17.    ......  \n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" DbMessage._msg成員爲DbMessage 類型,DbMessage的_nsStart和_theEnd成員分別記錄完整mongodb報文的起始地址和結束地址,通過這兩個指針就可以獲取一個完整mongodb報文的全部內容,包括header和body。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":" 注意:","attrs":{}},{"type":"text","text":"DbMessage是早期mongodb版本(version<3.6)中用於報文body解析封裝的類,這些類針對opCode=[dbUpdate, dbDelete]這個區間的操作。在mongodb新版本(version>=3.6)中,body解析及封裝由op_msg.h和op_msg.cpp代碼文件中的clase OpMsgRequest{}完成處理。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4.4 OpMsg報文解析封裝核心代碼實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"      Mongodb從3.6版本開始默認使用OP_MSG操作作爲默認opCode,是一種可擴展的消息格式,旨在包含其他操作碼的功能,新版本讀寫請求協議都對應該操作碼。OP_MSG對應mongodb報文body解析封裝處理由OpMsg類相關接口完成,OpMsg::parse(Message)從Message中解析出報文body內容,其核心代碼實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.struct OpMsg {   \n2.      ......  \n3.    //msg解析賦值見OpMsg::parse     \n4.    //各種命令(insert update find等)都存放在該body中  \n5.    BSONObj body;    \n6.    //sequences用法暫時沒看懂,感覺沒什麼用?先跳過  \n7.    std::vector sequences; //賦值見OpMsg::parse  \n8.}  ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.//從message中解析出OpMsg信息  \n2.OpMsg OpMsg::parse(const Message& message) try {  \n3.    //message不能爲空,並且opCode必須爲dbMsg  \n4.    invariant(!message.empty());  \n5.    invariant(message.operation() == dbMsg);  \n6.    //獲取flagBits  \n7.    const uint32_t flags = OpMsg::flags(message);  \n8.    //flagBits有效性檢查,bit 0-15中只能對第0和第1位操作  \n9.    uassert(ErrorCodes::IllegalOpMsgFlag,  \n10.            str::stream() <(flags).to_string(),  \n12.            !containsUnknownRequiredFlags(flags));  \n13.  \n14.    //校驗碼默認4字節  \n15.    constexpr int kCrc32Size = 4;  \n16.    //判斷該mongo報文body內容是否啓用了校驗功能  \n17.    const bool haveChecksum = flags & kChecksumPresent;  \n18.    //如果有啓用校驗功能,則報文末尾4字節爲校驗碼  \n19.    const int checksumSize = haveChecksum ? kCrc32Size : 0;  \n20.    //sections字段內容  \n21.    BufReader sectionsBuf(message.singleData().data() + sizeof(flags),  \n22.                          message.dataSize() - sizeof(flags) - checksumSize);  \n23.  \n24.    //默認先設置位false  \n25.    bool haveBody = false;  \n26.    OpMsg msg;  \n27.    //解析sections對應命令請求數據  \n28.    while (!sectionsBuf.atEof()) {  \n29.     //BufReader::read讀取kind內容,一個字節  \n30.        const auto sectionKind = sectionsBuf.read
();  \n31.     //kind爲0對應命令請求body內容,內容通過bson報錯  \n32.        switch (sectionKind) {  \n33.         //sections第一個字節是0說明是body  \n34.            case Section::kBody: {  \n35.                //默認只能有一個body  \n36.                uassert(40430, \"Multiple body sections in message\", !haveBody);  \n37.                haveBody = true;  \n38.         //命令請求的bson信息保存在這裏  \n39.                msg.body = sectionsBuf.read>();  \n40.                break;  \n41.            }  \n42.  \n43.         //DocSequence暫時沒看明白,用到的地方很少,跳過,後續等  \n44.            //該系列文章主流功能分析完成後,從頭再回首分析  \n45.            case Section::kDocSequence: {  \n46.                  ......  \n47.            }  \n48.        }  \n49.    }  \n50.    //OP_MSG必須有body內容  \n51.    uassert(40587, \"OP_MSG messages must have a body\", haveBody);  \n52.    //body和sequence去重判斷  \n53.    for (const auto& docSeq : msg.sequences) {  \n54.        ......  \n55.    }  \n56.    return msg;  \n}  ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" OpMsg類被OpMsgRequest類繼承,OpMsgRequest類中核心接口就是解析出OpMsg.body中的庫信息和表信息,OpMsgRequest類代碼實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.//協議解析得時候會用到,見runCommands  \n2.struct OpMsgRequest : public OpMsg {  \n3.    ......  \n4.    //構造初始化  \n5.    explicit OpMsgRequest(OpMsg&& generic) : OpMsg(std::move(generic)) {}  \n6.    //opMsgRequestFromAnyProtocol->OpMsgRequest::parse   \n7.    //從message中解析出OpMsg所需成員信息  \n8.    static OpMsgRequest parse(const Message& message) {  \n9.        //OpMsg::parse  \n10.        return OpMsgRequest(OpMsg::parse(message));  \n11.    }  \n12.    //根據db body extraFields填充OpMsgRequest  \n13.    static OpMsgRequest fromDBAndBody(... {  \n14.        OpMsgRequest request;  \n15.        request.body = ([&] {  \n16.            //填充request.body  \n17.            ......  \n18.        }());  \n19.        return request;  \n20.    }  \n21.    //從body中獲取db name  \n22.    StringData getDatabase() const {  \n23.        if (auto elem = body[\"$db\"])  \n24.            return elem.checkAndGetStringData();  \n25.        uasserted(40571, \"OP_MSG requests require a $db argument\");  \n26.    }  \n27.    //find  insert 等命令信息  body中的第一個elem就是command 名  \n28.    StringData getCommandName() const {  \n29.        return body.firstElementFieldName();  \n30.    }  \n}; ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" OpMsgRequest通過OpMsg::parse(message)解析出OpMsg信息,從而獲取到body內容,GetCommandName()接口和getDatabase()則分別從body中獲取庫DB信息、命令名信息。通過該類相關接口,命令名(find、write、update等)和DB庫都獲取到了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" OpMsg模塊除了OP","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"MSG相關報文解析外,還負責OP","attrs":{}},{"type":"text","text":"MSG報文組裝填充,該模塊接口功能大全如下表:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f7/f730feeb5fce4ef2221fb5354d09eb7f.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"5. Mongod實例服務入口核心代碼實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Mongod實例服務入口類ServiceEntryPointMongod繼承ServiceEntryPointImpl類,mongod實例的報文解析處理、命令解析、命令執行都由該類負責處理。ServiceEntryPointMongod核心接口可以細分爲:opCode解析及回調處理、命令解析及查找、命令執行三個子模塊。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5.1 opCode解析及回調處理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"     OpCode操作碼解析及其回調處理由ServiceEntryPointMongod::handleRequest(...)接口實現,核心代碼實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.//mongod服務對於客戶端請求的處理    \n2.//通過狀態機SSM模塊的如下接口調用:ServiceStateMachine::_processMessage  \n3.DbResponse ServiceEntryPointMongod::handleRequest(OperationContext* opCtx, const Message& m) {  \n4.    //獲取opCode,3.6版本對應客戶端默認使用OP_MSG  \n5.    NetworkOp op = m.operation();   \n6.    ......  \n7.    //根據message構造DbMessage  \n8.    DbMessage dbmsg(m);  \n9.    //根據操作上下文獲取對應的client  \n10.    Client& c = *opCtx->getClient();    \n11.    ......  \n12.    //獲取庫.表信息,注意只有dbUpdate(currentOp.elapsedTimeExcludingPauses());  \n47.    ......  \n48.    //慢日誌記錄  \n49.    if (shouldLogOpDebug || (shouldSample && debug.executionTimeMicros > logThresholdMs * 1000LL)) {  \n50.        Locker::LockerInfo lockerInfo;    \n51.        //OperationContext::lockState  LockerImpl<>::getLockerInfo  \n52.        opCtx->lockState()->getLockerInfo(&lockerInfo);   \n53.  \n54.    //OpDebug::report 記錄慢日誌到日誌文件  \n55.        log() <done();  \n28.    //responseLength賦值  \n29.    CurOp::get(opCtx)->debug().responseLength = response.header().dataLen();  \n30.    // 返回  \n31.    return DbResponse{std::move(response)};  \n} ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" RunCommands(...)接口從message中解析出OpMsg信息,然後獲取該OpMsg對應的command命令信息,最後執行該命令對應的後續處理操作。主要功能說明如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① 獲取該OpCode對應replyBuilder,OP_MSG操作對應builder爲OpMsgReplyBuilder。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② 根據message解析出OpMsgRequest數據,OpMsgRequest來中包含了真正的命令請求bson信息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③ opCtx初始化操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"④ 通過request.getCommandName()返回命令信息(如“find”、“update”等字符串)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑤ 通過Command::findCommand(command name)從CommandMap這個map表中查找是否支持該 command命令。如果沒找到說明不支持,如果找到說明支持。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑥ 調用execCommandDatabase(...)執行該命令,並獲取命令的執行結果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑦ 根據command執行結果構造response並返回","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5.3命令執行","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.void execCommandDatabase(...) {  \n2.    ......  \n3.    //獲取dbname  \n4.    const auto dbname = request.getDatabase().toString();  \n5.    ......  \n6.    //mab表存放從bson中解析出的elem信息  \n7.    StringMap topLevelFields;  \n8.    //body elem解析  \n9.    for (auto&& element : request.body) {  \n10.        //獲取bson中的elem信息  \n11.        StringData fieldName = element.fieldNameStringData();  \n12.        //如果elem信息重複,則異常處理  \n13.        ......  \n14.    }  \n15.    //如果是help命令,則給出help提示  \n16.    if (Command::isHelpRequest(helpField)) {  \n17.        //給出help提示  \n18.        Command::generateHelpResponse(opCtx, replyBuilder, *command);  \n19.        return;  \n20.    }  \n21.    //權限認證檢查,檢查該命令執行權限  \n22.    uassertStatusOK(Command::checkAuthorization(command, opCtx, request));  \n23.    ......  \n24.  \n25.    //該命令執行次數統計  db.serverStatus().metrics.commands可以獲取統計信息  \n26.    command->incrementCommandsExecuted();  \n27.    //真正的命令執行在這裏面  \n28.    retval = runCommandImpl(opCtx, command, request, replyBuilder, startOperationTime);  \n29.    //該命令執行失敗次數統計  \n30.    if (!retval) {  \n31.        command->incrementCommandsFailed();  \n32.     }  \n33.     ......  \n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" execCommandDatabase(...)最終調用RunCommandImpl(...)進行對應命令的真正處理,該接口核心代碼實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.bool runCommandImpl(...) {  \n2.    //獲取命令請求內容body  \n3.    BSONObj cmd = request.body;  \n4.    //獲取請求中的DB庫信息  \n5.    const std::string db = request.getDatabase().toString();  \n6.    //ReadConcern檢查  \n7.    Status rcStatus = waitForReadConcern(  \n8.        opCtx, repl::ReadConcernArgs::get(opCtx), command->allowsAfterClusterTime(cmd));  \n9.    //ReadConcern檢查不通過,直接異常提示處理  \n10.    if (!rcStatus.isOK()) {  \n11.         //異常處理  \n12.         return;  \n13.    }  \n14.    if (!command->supportsWriteConcern(cmd)) {  \n15.        //命令不支持WriteConcern,但是對應的請求中卻帶有WriteConcern配置,直接報錯不支持  \n16.        if (commandSpecifiesWriteConcern(cmd)) {  \n17.            //異常處理\"Command does not support writeConcern\"  \n18.            ......  \n19.            return result;  \n20.        }  \n21.    //調用Command::publicRun執行不同命令操作  \n22.        result = command->publicRun(opCtx, request, inPlaceReplyBob);  \n23.    }  \n24.    //提取WriteConcernOptions信息  \n25.    auto wcResult = extractWriteConcern(opCtx, cmd, db);  \n26.    //提取異常,直接異常處理  \n27.    if (!wcResult.isOK()) {  \n28.        //異常處理  \n29.        ......  \n30.        return result;  \n31.    }  \n32.    ......  \n33.    //執行對應的命令Command::publicRun,執行不同命令操作  \n34.    result = command->publicRun(opCtx, request, inPlaceReplyBob);  \n35.    ......  \n36.}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"     RunCommandImpl(...)接口最終調用該接口入參的command,執行 command->publicRun(...)接口,也就是命令模塊的公共publicRun。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5.4總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Mongod服務入口首先從message中解析出opCode操作碼,3.6版本對應客戶端默認操作碼爲OP_MSQ,解析出該操作對應OpMsgRequest信息。然後從message原始數據中解析出command命令字符串後,繼續通過全局Map表種查找是否支持該命令操作,如果支持則執行該命令;如果不支持,直接異常打印,同時返回。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"6. Mongos實例服務入口核心代碼實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"       mongos服務入口核心代碼實現過程和mongod服務入口代碼實現流程幾乎相同,mongos實例message解析、OP_MSG操作碼處理、command命令查找等流程和上一章節mongod實例處理過程類似,本章節不在詳細分析。Mongos實例服務入口處理調用流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ServiceEntryPointMongos::handleRequest(...)->Strategy::clientCommand(...)-->runCommand(...)->execCommandClient(...)","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/51/511238a22e3d645d35c8977bc9db1185.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 最後的接口核心代碼實現如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"1.void runCommand(...) {  \n2.    ......  \n3.    //獲取請求命令name  \n4.    auto const commandName = request.getCommandName();  \n5.    //從全局map表中查找  \n6.    auto const command = Command::findCommand(commandName);  \n7.    //沒有對應的command存在,拋異常說明不支持該命令  \n8.    if (!command) {   \n9.        ......  \n10.        return;  \n11.    }   \n12.    ......  \n13.    //執行命令  \n14.    execCommandClient(opCtx, command, request, builder);   \n15.    ......  \n16.}  \n17.\n18.void execCommandClient(...)  \n19.{   \n20.    ......  \n21.    //認證檢查,是否有操作該command命令的權限,沒有則異常提示  \n22.    Status status = Command::checkAuthorization(c, opCtx, request);    \n23.    if (!status.isOK()) {  \n24.        Command::appendCommandStatus(result, status);  \n25.        return;  \n26.    }  \n27.    //該命令的執行次數自增,代理上面也是要計數的  \n28.    c->incrementCommandsExecuted();   \n29.    //如果需要command統計,則加1  \n30.    if (c->shouldAffectCommandCounter()) {  \n31.        globalOpCounters.gotCommand();  \n32.    }  \n33.    ......  \n34.    //有部分命令不支持writeconcern配置,報錯  \n35.    bool supportsWriteConcern = c->supportsWriteConcern(request.body);  \n36.    //不支持writeconcern又帶有該參數的請求,直接異常處理\"Command does not support writeConcern\"  \n37.    if (!supportsWriteConcern && !wcResult.getValue().usedDefault) {  \n38.        ......  \n39.        return;  \n40.    }  \n41.    //執行本命令對應的公共publicRun接口,Command::publicRun  \n42.    ok = c->publicRun(opCtx, request, result);   \n43.    ......  \n44.}  ","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tips: mongos和mongod實例服務入口核心代碼實現的一點小區別","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① Mongod實例opCode操作碼解析、OpMsg解析、command查找及對應命令調用處理都由class ServiceEntryPointMongod{...}類一起完成。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② mongos實例則把opCode操作碼解析交由class ServiceEntryPointMongos{...}類實現,OpMsg解析、command查找及對應命令調用處理放到了clase Strategy{...}類來處理。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"7.總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"   Mongodb報文解析及組裝流程總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① 一個完整mongodb報文由通用報文header頭部+body部分組成。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② Body部分內容,根據報文頭部的opCode來決定不同的body內容。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③ 3.6版本對應客戶端請求opCode默認爲OP_MSG,該操作碼對應body部分由flagBits + sections + checksum組成,其中sections中存放的是真正的命令請求信息,已bson數據格式保存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"④ Header頭部和body報文體封裝及解析過程由class Message {...}類實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑤ Body中對應command命令名、庫名、表名的解析在mongodb(version<3.6)低版本協議中由class DbMessage {...}類實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑥ Body中對應command命令名、庫名、表名的解析在mongodb(version<3.6)低版本協議中由struct OpMsgRequest{...}結構和struct OpMsg {...}類實現","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":"   Mongos和mongod實例的服務入口處理流程大同小異,整體處理流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① 從message解析出opCode操作碼,根據不同操作碼執行對應操作碼回調。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② 根據message解析出OpMsg request信息,mongodb報文的命令信息就存儲在該body中,該body已bson格式存儲。","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":"   圖形化總結如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ef/efc040eb2e9f6a9947b61bc25f6cde82.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":" 說明:","attrs":{}},{"type":"text","text":"第3章的協議解析及封裝過程實際上應該算是網絡處理模塊範疇,本文爲了分析command命令處理模塊方便,把該部分實現歸納到了命令處理模塊,這樣方便理解。","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","marks":[{"type":"strong","attrs":{}}],"text":" Tips: ","attrs":{}},{"type":"text","text":"下期繼續分享不同command命令執行細節。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"8.遺留問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"     第1章節中的統計信息,將在command模塊核心代碼分析完畢後揭曉答案,《mongodb command命令處理模塊源碼實現二》中繼續分析,敬請關注。","attrs":{}}]},{"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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章