ITPub博客

首页 > 数据库 > NoSQL > mongodb内核源码实现、性能调优、最佳运维实践系列-command命令处理模块源码实现三

mongodb内核源码实现、性能调优、最佳运维实践系列-command命令处理模块源码实现三

原创 NoSQL 作者:y123456yzzyz 时间:2020-11-30 21:12:24 0 删除 编辑

关于作者

前滴滴出行技术专家,现任OPPO 文档数据库 mongodb 负责人,负责 oppo 千万级峰值 TPS/ 十万亿级数据量文档数据库 mongodb 内核研发及运维工作,一直专注于分布式缓存、高性能服务端、数据库、中间件等相关研发。后续持续分享《 MongoDB 内核源码设计、性能优化、最佳运维实践》, Github 账号地址 : https://github.com/y123456yz

1. command 默认接口类核心代码实现及基本接口功能说明

每个命令都对应一个command 基类,该类中完成命令的一些基本接口功能初始化,核心接口实现如下 :

1. //命令模块基类基础接口初始化实现  
2. class Command : public CommandInterface {  
3. public:  
4.     //获取集合名collection  
5.     static std::string parseNsFullyQualified(...);  
6.     //获取DB.COLLECTION  
7.     static NamespaceString parseNsCollectionRequired(...);  
8.     //map表结构  
9.     using CommandMap = StringMap<Command*>;  
10.     ......  
11.     //获取命令名  
12.     const std::string& getName() const final {  
13.         return _name;  
14.     }  
15.     ......  
16.     //应答保留填充字段长度  
17.     std::size_t reserveBytesForReply() const override {  
18.         return 0u;  
19.     }  
20.     //该命令是否只能在admin库执行,默认不可以  
21.     bool adminOnly() const override {  
22.         return false;  
23.     }  
24.     //该命令是否需要权限认证检查?默认不需要  
25.     bool localHostOnlyIfNoAuth() override {  
26.         return false;  
27.     }  
28.     //该命令执行后是否进行command操作计数  
29.     bool shouldAffectCommandCounter() const override {  
30.         return true;  
31.     }  
32.     //该命令是否需要认证  
33.     bool requiresAuth() const override {  
34.         return true;  
35.     }  
36.     //help帮助信息  
37.     void help(std::stringstream& help) const override;  
38.     //执行计划信息  
39.     Status explain(...) const override;  
40.     //日志信息相关  
41.     void redactForLogging(mutablebson::Document* cmdObj) override;  
42.     BSONObj getRedactedCopyForLogging(const BSONObj& cmdObj) override;  
43.     //该命令是否为maintenance模式,默认false  
44.     bool maintenanceMode() const override {  
45.         return false;  
46.     }  
47.     //maintenance是否支持,默认支持  
48.     bool maintenanceOk() const override {  
49.         return true;   
50.     }  
51.     //本地是否支持非本地ReadConcern,默认不支持  
52.     bool supportsNonLocalReadConcern(...) const override {  
53.         return false;  
54.     }  
55.     //是否允许AfterClusterTime,默认允许  
56.     bool allowsAfterClusterTime(const BSONObj& cmdObj) const override {  
57.         return true;  
58.     }  
59.     //3.6版本默认opCode=OP_MSG,所以对应逻辑操作op为LogicalOp::opCommand  
60.     LogicalOp getLogicalOp() const override {  
61.         return LogicalOp::opCommand;  
62.     }  
63.     //例如find就是kRead,update  delete insert就是kWrite,非读写操作就是kCommand  
64.     ReadWriteType getReadWriteType() const override {  
65.         return ReadWriteType::kCommand;  
66.     }  
67.     //该命令执行成功统计  
68.     void incrementCommandsExecuted() final {  
69.         _commandsExecuted.increment();  
70.     }  
71.   
72.     //该命令执行失败统计  
73.     void incrementCommandsFailed() final {  
74.         _commandsFailed.increment();  
75.     }  
76.   
77.     //真正得命令运行  
78.     bool publicRun(OperationContext* opCtx, const OpMsgRequest& request, BSONObjBuilder& result);  
79.   
80.     //获取支持的所有命令信息  ListCommandsCmd获取所有支持的命令 db.listCommands()  
81.     static const CommandMap& allCommands() {  
82.         return *_commands;  
83.     }  
84.   
85.     //没用  
86.     static const CommandMap& allCommandsByBestName() {  
87.         return *_commandsByBestName;  
88.     }  
89.   
90.     //收到不支持命令的统计,例如mongo shell敲一个mongodb无法识别得命令,这里就会统计出来  
91.     static Counter64 unknownCommands;  
92.     //根据命令字符串名查找对应命令  
93.     static Command* findCommand(StringData name);  
94.     //执行结果  
95.     static void appendCommandStatus(...);  
96.     //是否启用了command test功能  
97.     static bool testCommandsEnabled;  
98.     //help帮助信息  
99.     static bool isHelpRequest(const BSONElement& helpElem);  
100.     static const char kHelpFieldName[];  
101.     //认证检查,检查是否有执行该命令得权限  
102.     static Status checkAuthorization(Command* c,  
103.                                      OperationContext* opCtx,  
104.                                      const OpMsgRequest& request);  
105.     ......  
106. private:  
107.     //添加地方见Command::Command(    
108.     //所有的command都在_commands中保存  
109.     static CommandMap* _commands;  
110.     //暂时没用  
111.     static CommandMap* _commandsByBestName;  
112.     //执行对应命令run接口  
113.     virtual bool enhancedRun(OperationContext* opCtx,  
114.                              const OpMsgRequest& request,  
115.                              BSONObjBuilder& result) = 0;  
116.     //db.serverStatus().metrics.commands命令查看,本命令的执行统计,包括执行成功和执行失败的  
117.     Counter64 _commandsExecuted;   
118.     Counter64 _commandsFailed;  
119.    //命令名,如"find" "insert" "update" "createIndexes" "deleteIndexes"  
120.     const std::string _name;  
121.   
122.     //每个命令执行是否成功通过MetricTree管理起来,也就是db.serverStatus().metrics.commands统计信息  
123.     //通过MetricTree特殊二叉树管理起来  
124.     ServerStatusMetricField<Counter64> _commandsExecutedMetric;  
125.     ServerStatusMetricField<Counter64> _commandsFailedMetric;  
};

command 作为默认接口类,主要完成一些命令基本接口初始化操作及默认配置设置,该类最基本的接口主要如下:

l  reserveBytesForReply

ReserveBytesForReply() 接口主要完成该命令应答填充字段长度,默认值为 0 。对应命令可以在具体命令类中修改。

l  adminOnly

该命令是否只能在admin 库操作,默认为 false 。也可以在对应命令继承类中修改,例如 "moveChunk" 命令则在 MoveChunkCommand 继承类中设置为 true ,也就是该命令只能在 admin 库操作。

l  localHostOnlyIfNoAuth

该命令是否支持在实例所在本机不认证操作,默认值false 。对应命令可以在具体继承类中修改。

l  shouldAffectCommandCounter

该命令是否需要command 统计,也就是 mongostat 中的 command 统计计数是否需要使能。默认值 true ,也就是该命令会进行 command 计数统计。对应命令可以在具体继承类中修改。

l  requiresAuth

该命令是否需要认证,默认为true 。对应命令可以在具体继承类中修改。

l  allowsAfterClusterTime

该命令是否支持AfterClusterTime ,默认为 true 。对应命令可以在具体继承类中修改。

l  getLogicalOp

该命令是否为逻辑opCommand 命令。 3.6 版本默认 opCode=OP_MSG, 所以对应逻辑操作 op 为 LogicalOp::opCommand 。

l  getReadWriteType

如果为读命令则type 对应 kRead ,写命令 type 对应 kWrite ,其他读写以外的命令对应 kCommand 。

l  incrementCommandsExecuted

该命令执行成功统计,通过db.serverStatus().metrics.commands 获取该命令统计。

l  _commandsFailed

该命令执行失败统计,通过db.serverStatus().metrics.commands 获取该命令统计。

以上列举除了command 基类的几个核心功能默认值信息,如果继承类中没有修改这些接口值,则该命令对应功能就是这些默认值。

说明:各种不同命令如果不适用command 基类的默认接口,则可以在继承类中修改对应接口值即可更改对应功能。

命令除了上面提到的基本功能是否支持外,command 类还有其他几个核心接口功能。例如,该命令是否认证成功、是否有操作权限、允许对应 run 命令等。 command 类函数接口功能总结如下表所示:

2.  命令run

结合《命令处理模块源码实现一》和本章节对command 处理流程可以得出, runCommandImpl 接口通过如下调用流程最终执行特定命令的run 接口,这里以 insert 写入和读取流程为例, mongod 实例写入调用过程如下图所示 :


最终,mongod 和 mongos 实例调用相关命令得 run 接口完成具体的 command 命令处理操作。 mongos 、 mongod(shardServer) 、 mongod(configServer) 相关常用的操作命令 ( 以最基本的读写命令为例 ) 入口及功能说明总结如下表所示:

3. command 模块统计信息

mongodb command 命令处理模块相关统计包含三类:单个命令统计、汇总型统计、读写时延统计。其中,单个命令统计针对所有接受到的命令名字符串进行统计,汇总型统计则是把同一类型的命令总结为一个整体统计 ( 例如 commands 统计 ) 。

3.1 单个命令统计

mongodb 会收集所有操作命令执行结果,如果本次命令执行成功,则该命令成功统计自增加 1 ,同理如果该命令执行过程失败,则失败统计自增加 1 ,这些统一归类为 ” 单个命令统计信息 ” 。

单个命令统计由command 类的 _commandsExecuted 和 _commandsFailed 实现命令执行成功统计和失败统计,相关核心代码实现如下:

1. //该命令执行成功统计  
2. void incrementCommandsExecuted() final {  
3.     _commandsExecuted.increment();  
4. }  
5.   
6. //该命令执行失败统计  
7. void incrementCommandsFailed() final {  
8.     _commandsFailed.increment();  
9. }  
 
1. //命令入口  
2. void execCommandDatabase(...)  
3. {  
4.     ......  
5.     //该命令执行次数统计  db.serverStatus().metrics.commands可以获取统计信息  
6.     command->incrementCommandsExecuted();  
7.     ......  
8.     //真正的命令执行在这里面  
9.     retval = runCommandImpl(opCtx, command, request, replyBuilder, startOperationTime);  
10.   
11.     //该命令失败次数统计  
12.     if (!retval) {  
13.         command->incrementCommandsFailed();  
14.     }  
15.     ......  
}


mongodb 默认会统计每个客户端发往服务端的命令,即使是无法识别的命令也会统计,命令统计可以通过 db.serverStatus().metrics.commands 获取,如下图所示:

3.2 汇总型 commands 命令统计

从前面的单个命令统计可以看出,单个命令会记录所有发送给mongodb 的命令信息。 mongodb 支持的命令百余个,由于命令众多,因此 mongodb 为了更加直观明了的获取统计信息,除了提供单个命令统计外,还对外提供汇总型命令统计。

汇总型命令统计可以通过db.serverStatus().opcounters 命令获取, mongostat 中的增删改查等信息也来自于该统计,如下图:

从上图可以看出,整个mongostat 监控统计可以归类为小表:

insert 、 delete 、 update 、 find 分别对应增删改查四个命令操作, getMore 对应批量游标操作命令。这五个命令,对应命令执行的时候统计信息自增,核心代码实现如下:

3.2.1 insert 操作统计

insert 操作统计在代理 mongos 和分片存储节点 mongod 都会统计,两种角色的 insert 统计核心代码如下:

1.  代理mongos insert 统计核心代码实现

1.    bool insertBatchAndHandleErrors(...) {  
2.     ......  
3.     //一次性一条一条插入,上面的固定集合是一次性插入  
4.     for (auto it = batch.begin(); it != batch.end(); ++it) {  
5.         //insert操作计数  
6.         globalOpCounters.gotInsert();   
7.     }  
8.     ......  
9. }

2.  分片存储节点mongod insert 统计核心代码实现

1. //mongod代理insert统计核心流程  
2. bool ClusterWriteCmd::enhancedRun(...) {  
3.     ......  
4.     if (_writeType == BatchedCommandRequest::BatchType_Insert) {  
5.         //insert计数  
6.         for (size_t i = 0; i < numAttempts; ++i) {  
7.             globalOpCounters.gotInsert();  
8.         }  
9.     }  
10.     ......  
11. }

3.2.2 query 操作统计

1.  代理mongos query 统计核心代码实现:

1.     bool ClusterFindCmd::run(...) {  
2.     //find操作统计,也就是query统计  
3.     globalOpCounters.gotQuery();  
4.     ......  
5. }

2.  分片存储节点mongod query 统计核心代码实现:

1. bool FindCmd::run(...) {  
2.     //find操作统计,也就是query统计  
3.     globalOpCounters.gotQuery();  
4.     ......  
5. }

3.2.3 update 操作统计

1.  代理mongos update 统计核心代码实现 :

1. bool ClusterWriteCmd::enhancedRun(...) {  
1.     //update操作统计
2.     globalOpCounters.gotUpdate();  
3.     ......  
4. }

2.  分片存储节点mongod update 统计核心代码实现 :

1. //mongod代理update统计核心流程  
2. bool ClusterWriteCmd::enhancedRun(...) {  
3.     ......  
4.     if (_writeType == BatchedCommandRequest::BatchType_Update) {  
5.         //insert计数  
6.     for (size_t i = 0; i < numAttempts; ++i) {  
7.          globalOpCounters.gotUpdate();  
8.        }  
9.     }  
10.     ......  
11. }

3.2.4 delete 操作统计

1.  代理mongos delete 统计核心代码实现 :

1. //mongos代理update统计核心流程  
2. bool ClusterWriteCmd::enhancedRun(...) {  
3.     ......  
4.     if (_writeType == BatchedCommandRequest::BatchType_Update) {  
5.         //insert计数  
6.     for (size_t i = 0; i < numAttempts; ++i) {  
7.          globalOpCounters.gotUpdate();  
8.        }  
9.     }  
10.     ......  
11. }

2.  分片存储节点mongod delete 统计核心代码实现 :

1. static SingleWriteResult performSingleDeleteOp(...) {  
2.     ......  
3.     //分片mongod实例delete操作统计  
4.     globalOpCounters.gotDelete();  
5.     ......  
6. }

3.2.5 getMore 操作统计

1.  代理mongos getMore 统计核心代码实现 :

1. //mongos代理getMore统计核心流程  
2. bool ClusterGetMoreCmd::enhancedRun(...) {  
3.     //代理getMore统计  
4.     globalOpCounters.gotGetMore();  
5.     ......  
6. }

2.  分片存储节点mongod getMore 统计核心代码实现 :

1.  //mongod分片存储节点getMore统计  

2.  bool ClusterGetMoreCmd::enhancedRun(...) {  

3.      //存储节点mongod getMore统计  

4.      globalOpCounters.gotGetMore();  

5.      ......  

6.  }  

3.2.6 command 操作统计

前面五种操作统计都很好理解,commands 统计由那些命令操作组成,本节将重点分析 commands 如何实现统计。 commands 统计核心代码实现如下:

1.  代理mongos commands 统计核心代码实现 :

1. //mongos代理commands统计  
2. void execCommandDatabase(...) {  
3.     ......  
4.     if (command->shouldAffectCommandCounter()) {  
5.         OpCounters* opCounters = &globalOpCounters;  
6.         opCounters->gotCommand();  
7.     }  
8.     ......  
9. }

2.  存储节点mongod commands 统计核心代码实现:

1. void execCommandDatabase(...) {  
2.     ......  
3.     //是否进行command统计  
4.     if (command->shouldAffectCommandCounter()) {  
5.         OpCounters* opCounters = &globalOpCounters;  
6.         //commands计数自增
7.         opCounters->gotCommand();  
8.     }  
9.     ......  
10. }

从上面的代码可以看出,只有对应命令类中 shouldAffectCommandCounter () 为 true 的命令才会进行 commands 计数。前面章节中我们提到,所有命令都有一个对应类实现相应功能,所有命令实现类都继承一个功能 class command {} 类,该类对 shouldAffectCommandCounter () 接口进行初始化。代码实现如下:

1. class Command : {  
2.     ......  
3.     //该命令是否进行command操作计数,默认需要。如果不需要进行command统计,可在命令继承类中置为false  
4.     bool shouldAffectCommandCounter() const override {  
5.         return true;  
6.     }  
7.     ......  
8. }

该接口默认为true ,如果对应命令不需要进行 commands 计数统计,则需要在对应命令实现类中把该接口置为 false 。通过分析代码,可以看出,只有以下命令子类把 shouldAffectCommandCounter () 接口设置为 false ,搜索结果如下 :

分析代码可以得出如下结论:

1)  mongos 代理中的 clase Cluster_find_cmd { } 类和 class Cluster_getmore_cmd {} 类的 shouldAffectCommandCounter () 接口置为 false ,这两个类分别对应代理的“ find ”和“ getMore ”命令操作,也就说明 mongos 代理 de 这两个命令操作不会统计到 commands 中。

2)  mongod 分片存储节点的 clase Find_cmd{} 、 class Getmore_cmd {} 、 class Write_commands{} 三个类中把 shouldAffectCommandCounter () 接口置为 false 。这三个类分别对应 mongod 存储实例的如下几个命令:“ find ”、“ getMore ”、“ insert ”、“ update ”、“ delete ”五个命令。

  mongos 和 mongod 实例 commands 统计信息总结如下:

3.3 慢日志、时延统计

每次客户端请求执行实践如果超过了log level 配置的最大慢日志时间,则会把该操作详细信息记录下来,同时把本操作执行时间添加到对应的读或者写计数及时延统计中。命令处理模块中,时延相关统计包括以下两种统计:

①  慢日志统计

②  读写计数及时延统计

3.3.1 慢日志统计

当启用了慢日志记录功能后,mongod 会把执行时间超过指定阀值的慢日志记录下来。慢日志默认记录到服务日志文件 ( systemLog.path 配置项设置) ,同时会记录日志到 ”system.profile” 集合中。慢日志核心代码实现如下:

1. DbResponse ServiceEntryPointMongod::handleRequest(...) {  
2.     ......  
3.     //记录开始时间  
4.     //获取当前操作对应curop  
5.     CurOp& currentOp = *CurOp::get(opCtx);  
6.   
7.     ......  
8.     //执行请求对应命令  
9.     runCommands(opCtx, m);  
10.     ......  
11.   
12.     //记录结束时间  
13.     currentOp.ensureStarted();  
14.     currentOp.done();   
15.     //获取开始和结束时间差,也就是命令执行时间  
16.     debug.executionTimeMicros = durationCount<Microseconds>  
17.                  (currentOp.elapsedTimeExcludingPauses());  
18.     //记录超过阀值的慢日志到日志文件  
19.     if (shouldLogOpDebug || (shouldSample && debug.executionTimeMicros > logThresholdMs * 1000LL)) {  
20.         ......  
21.     //记录慢日志到日志文件  
22.         log() << debug.report(&c, currentOp, lockerInfo.stats);   
23.     }  
24.     //记录慢日志到system.profile集合   
25.     if (currentOp.shouldDBProfile(shouldSample)) {  
26.         ......  
27.         //记录慢日志到system.profile集合中  
28.         profile(opCtx, op);  
29.     }  
30.     ......  
31. }


3.3.2 读写操作计数及时延统计

根据请求command 命令类型 ( 包含读命令、写命令、 command 命令 ) ,以及命令执行时间,可以计算出不同类型命令的读写执行时间,从而计算出集群的读时延、写时延、 command 时延。 mongodb 所有命令可以归纳为读、写、 command 三类,核心代码如下:

1. //获取操作类型  
2. Command::ReadWriteType CurOp::getReadWriteType() const {  
3.     ......  
4.     switch (_logicalOp) {  
5.     //getmore find归纳为读  
6.         case LogicalOp::opGetMore:  
7.         case LogicalOp::opQuery:  
8.             return Command::ReadWriteType::kRead;  
9.     //增删改统一归纳为写  
10.         case LogicalOp::opUpdate:  
11.         case LogicalOp::opInsert:  
12.         case LogicalOp::opDelete:  
13.             return Command::ReadWriteType::kWrite;  
14.     //增删改以外的归纳为command  
15.         default:  
16.             return Command::ReadWriteType::kCommand;  
17.     }  
18. }

从上面的代码可以看出,读、写、command 分别对应以下命令:

读(read) :包含getMore 、 find 。

写(write) :包含insert 、 delete 、 update 。

command :读和写以外的所有命令。

命令执行完计算出命令运行时间后,mongod 实例会记录下这个时延,累加到历史统计 OperationLatencyHistogram 中,读、写、 command 操作计数及时延统计分别记录到 _reads 、 _writes 、 _commands 三个变量成员中。该统计核心代码实现如下:

1. //Top::_incrementHistogram调用  
2. //操作和时延计数操作  
3. void OperationLatencyHistogram::increment(uint64_t latency, Command::ReadWriteType type) {  
4.     int bucket = _getBucket(latency);  
5.     switch (type) {  
6.         //读时延累加,读计数自增  
7.         case Command::ReadWriteType::kRead:  
8.             _incrementData(latency, bucket, &_reads);  
9.             break;  
10.         //写时延累加,写计数自增    
11.         case Command::ReadWriteType::kWrite:  
12.             _incrementData(latency, bucket, &_writes);  
13.             break;  
14.         //command时延累加,command计数自增    
15.         case Command::ReadWriteType::kCommand:  
16.             _incrementData(latency, bucket, &_commands);  
17.             break;  
18.         default:  
19.             MONGO_UNREACHABLE;  
20.     }  
21. }

命令请求执行过程及其对应的读写请求操作计数、时延累加衔接代码实现如下:

1. DbResponse ServiceEntryPointMongod::handleRequest(...) {  
2.     ......  
3.     //记录开始时间  
4.     //获取当前操作对应curop  
5.     CurOp& currentOp = *CurOp::get(opCtx);  
6.   
7.     ......  
8.     //执行请求对应命令  
9.     runCommands(opCtx, m);  
10.     ......  
11.   
12.     //记录结束时间  
13.     currentOp.ensureStarted();  
14.     currentOp.done();   
15.     //获取开始和结束时间差,也就是命令执行时间  
16.     debug.executionTimeMicros = durationCount<Microseconds>  
17.                  (currentOp.elapsedTimeExcludingPauses());  
18.     ......  
19.     //记录慢日志到system.profile集合   
20.     //mongod读写的时间延迟统计  db.serverStatus().opLatencies获取   
21.     Top::get(opCtx->getServiceContext())  
22.         .incrementGlobalLatencyStats(   //读写统计
23.             opCtx,  
24.              //时延             
25.             durationCount<Microseconds>(currentOp.elapsedTimeExcludingPauses()),  
26.             currentOp.getReadWriteType());  //读写类型
27.     ......  
28. }

mongod 实例读、写、 command 操作计数及其各自时延统计可以通过 db.serverStatus() 接口获取,用户可用采样来计算对应的 tps 和平均时延信息。获取操作统计和时延统计的命令如下 :

db.serverStatus().opLatencies

4.  问题回顾

    《Mongodb command 命令处理模块源码实现一》一文中提到的 commands 统计信息到这里就可以得到答案了,如下表所示:

  mongos 和 mongod 实例 commands 统计信息总结如下:

mongos 代理 mongostat 统计可以汇总为下图所示:

mongod 代理 mongostat 统计可以汇总为下图所示:

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69984922/viewspace-2738326/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
滴滴出行专家工程师,OPPO-mongodb负责人,负责近千万级峰值TPS/数万亿级mongodb研发和运维工作,专注于高性能中间件、数据库等研发,持续分享《MongoDB源码设计、性能优化、最佳运维实践》,git账号:https://github.com/y123456yz

注册时间:2020-10-04

  • 博文量
    47
  • 访问量
    418322