关于作者
前滴滴出行技术专家,现任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;
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 _commandsExecutedMetric;
125. ServerStatusMetricField _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
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
17. (currentOp.elapsedTimeExcludingPauses());
18. ......
19. //记录慢日志到system.profile集合
20. //mongod读写的时间延迟统计 db.serverStatus().opLatencies获取
21. Top::get(opCtx->getServiceContext())
22. .incrementGlobalLatencyStats( //读写统计
23. opCtx,
24. //时延
25. durationCount(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 统计可以汇总为下图所示:
