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