ITPub博客

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

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

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

关于作者

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

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 作为默认接口类,主要完成一些命令基本接口初始化操作及默认配置设置,该类最基本的接口主要如下:

reserveBytesForReply

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

adminOnly

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

localHostOnlyIfNoAuth

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

shouldAffectCommandCounter

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

requiresAuth

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

allowsAfterClusterTime

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

getLogicalOp

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

getReadWriteType

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

incrementCommandsExecuted

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

_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 会把执行时间超过指定阀值的慢日志记录下来。慢日志默认记录到服务日志文件 ( 配置项设置) ,同时会记录日志到 ”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博客 ” ,链接:http://blog.itpub.net/69984922/viewspace-2738326/,如需转载,请注明出处,否则将追究法律责任。

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

注册时间:2020-10-04

  • 博文量
    37
  • 访问量
    94032