关于做者
前滴滴出行技术专家,现任OPPO文档数据库mongodb负责人,负责oppo千万级峰值TPS/十万亿级数据量文档数据库mongodb内核研发及运维工做,一直专一于分布式缓存、高性能服务端、数据库、中间件等相关研发。后续持续分享《MongoDB内核源码设计、性能优化、最佳运维实践》,Github帐号地址:https://github.com/y123456yzgit
背景
mongodb内核代码中提供有完善的gotool工具,这些开源工具做用主要有:数据导出及恢复(mongodump、mongorestore、mongoexport、mongoimport)工具、客户端shell连接工具(mongo)、IO测试工具(mongoperf)、流量qps/时延等监控统计工具(mongostat、mongotop)。github
mongodb默认只提供mongostat和mongotop工具来完成流量和时延统计,这两个工具的主要功能以下:算法
- mongostat:监控整个集群的qps统计信息
- mongotop:监控表级的读写时延统计信息。
问题:mongodb
- 问题一:mongostat能够监控整个集群的qps信息,可是表级的qps信息如何监控?例如若是某一时刻读写流量忽然暴涨引发集群抖动,怎么知道是那个具体的表引发?
- 问题二:mongotop能够获取整个表的读写时延消耗,若是某个表写时延很高,咱们如何快速定位写时延高具体由增、删、改操做中的那个操做引发?
显然,mongostat和mongotop知足不了咱们怼上面的两个问题的需求。实际上,mongodb内部实现上提供有对应的表级别qps和表级别时延统计接口,拿到这些接口统计后,咱们就能够快速获取对应的数据结果,本文讲分析表级统计的实现原理及核心代码实现。shell
- mongostat、mongotop监控统计信息
mongodb官方对外开源的qps及时延监控主要有mongostat和mongotop,本章节分析这两个工具的用法及监控项。数据库
1.1 mongostat监控统计
mongodb提供了mongostat工具来监控当前集群的各类操做统计。Mongostat监控统计以下图所示:数组
其中,insert、delete、update、query这四项统计比较好理解,分别对应增、删、改、查,getMore记录批量拉数据时候的游标操做统计,command统计在mongos和mongod中有不一样的涵义,具体参考:mongodb内核源码实现、性能调优、最佳运维实践系列-command命令处理模块源码实现三缓存
mongostat help参数功能详细说明以下:性能优化
1.2 mongotop监控统计
mongotop实现对全部表的读写时延消耗统计,并按照总耗时排序直观输出,对应统计打印信息以下图所示:数据结构
mongotop监控输出项各字段说明以下:
- ns: 表名
- read:1秒钟内客户端对该表读操做消耗的总时间
- write:1秒钟内客户端对该表写操做消耗的总时间
- total:1秒钟内客户端对该表读写消耗的总时间
mongotop工具help参数信息说明以下表所示:
2. 表级详细操做统计及其时延监控统计
mongod实例会对表级别的增、删、改、查、getMore、command进行详细的操做统计,并对每种操做的时延进行统计。每一个表都拥有一个CollectionData结构,该结构中存储全部操做统计和时延统计;同一个操做的qps统计和时延统计经过UsageData结构实现,包含count和time两个成员。
2.1 表级统计实现原理
详细的表级统计经过如下几个类结构分层实现:
- 全局UsageMap表
UsageMap是一个StringMap表结构,该map表中的成员类型为CollectionData,一个CollectionData对应一个表名及其该表的各自详细qps和时延统计信息,核心代码定义以下:
typedef StringMap<CollectionData> UsageMap;
- CollectionData表统计信息
CollectionData结构中包含多个成员,包含了三个维度的统计,每一个维度中的成员对应一个操做统计项,统计维度及其操做类型以下表:
- UsageData
UsageData完成上面的锁维度和请求类型维度的操做计数和时延计数,UsageData包含count和time两个成员,分别用于操做计数和时延计数。
- OperationLatencyHistogram表级汇总型统计
OperationLatencyHistogram实现表级别的操做汇总计数和汇总型时延统计,在该汇总型统计中把请求类型维度中的六项操做(queries、getmore、insert、update、remove、commands)合并汇总为三项统计:_reads、_writes、_commands。
2.2 核心代码实现
mongodb表级详细统计实现主要由src/mongo/db/stats/目录中的top.cpp、top.h、operation_latency_histogram.cpp、operation_latency_histogram.h四个文件完成。
2.2.1 核心数据结构实现
核心数据结构代码实现以下:
1.class Top { 2. ...... 3. //map表中每一个表占用一个 4. struct CollectionData { 5. ...... 6. //锁维度 7. UsageData readLock; 8. UsageData writeLock; 9. 10. //表级别不一样操做的时延统计,粒度相比OperationLatencyHistogram更小 11. //请求类型维度,包含增、删、改、查、getMore、command六类 12. UsageData queries; 13. UsageData getmore; 14. UsageData insert; 15. UsageData update; 16. UsageData remove; 17. UsageData commands; 18. //总的,上面的[queries,commands] 19. UsageData total; 20. 21. //汇总型维度,包含读、写、command三个维度 22. OperationLatencyHistogram opLatencyHistogram; 23. }; 24. //锁类型,读锁仍是写锁 25. enum class LockType { 26. ReadLocked, 27. WriteLocked, 28. NotLocked, 29. }; 30. //Top._usage 各类命令的详细统计记录在该map表中 31. //map表中每一个表占用一个,参考Top::record 32. typedef StringMap<CollectionData> UsageMap; 33. 34.public: 35. //全局UsageMap表,表中每一个成员对应一个collection表 36. UsageMap _usage; 37. ...... 38.}
从上面的核心算法能够看出,UsageMap 为map表结构,包含有全部表名及其对应的表级请求统计和时延统计,每一个表的全部统计记录到struct CollectionData {} 结构中。
CollectionData 结构中的成员能够分为三类:锁统计、详细请求统计、汇总型统计,其中汇总型统计由class OperationLatencyHistogram {}类实现,核心成员以下:
1.class OperationLatencyHistogram { 2. ...... 3.private: 4. //能够用于记录历史统计,经过buckets来区分,最大能够记录kMaxBuckets个历史统计信息 5. struct HistogramData { 6. std::array<uint64_t, kMaxBuckets> buckets{}; 7. uint64_t entryCount = 0; 8. uint64_t sum = 0; 9. }; 10. ...... 11. HistogramData _reads, _writes, _commands; }
2.2.2 核心算法实现
按照不一样的维度,表级详细统计核心算法实现能够包含:锁及请求类型详细统计算法实现、汇总型表级详细统计算法实现。
- 锁类型统计和请求类型详细统计核心算法实现
mongodb按照不一样统计维度,同一个请求能够概括到不一样锁类型,同时也能够概括到不一样请求类型。例如,db.test.find({xxx})这个查询,在对test表详细统计的时候,该查询会同时对该表的读锁readLock统计及queries统计进行计数,也就是会同时记录该操做锁操做计数和查询操做计数。
锁类型统计及请求类型表级统计核心算法实现以下:
- 找出对应表统计存储结构CollectionData
1.void Top::record(...) { 2. ...... 3. 4. //根据表名从Map表种找到该表在表中对应hash位置 5. auto hashedNs = UsageMap::HashedKey(ns); 6. stdx::lock_guard<SimpleMutex> lk(_lock); 7. 8. //若是ns是已经删除的表,直接返回 9. if ((command || logicalOp == LogicalOp::opQuery) && ns == _lastDropped) { 10. _lastDropped = ""; 11. return; 12. } 13. //找到改表对应的CollectionData 14. CollectionData& coll = _usage[hashedNs]; 15. //开始表级计数统计 16. _record(opCtx, coll, logicalOp, lockType, micros, readWriteType); }
2. 对该表进行真正的计数统计操做
1.//Top::record调用 各个命令的op及时延统计 2.void Top::_record(...) { 3. //汇总型详细表级统计 4. _incrementHistogram(opCtx, micros, &c.opLatencyHistogram, readWriteType); 5. //该表总时延计数,包括增删改查getMore command六项 及其余全部的统计 6. c.total.inc(micros); 7. //写锁计数 8. if (lockType == LockType::WriteLocked) 9. c.writeLock.inc(micros); 10. //读锁计数 11. else if (lockType == LockType::ReadLocked) 12. c.readLock.inc(micros); 13. 14. //详细增 删 改 查 getMore command统计及时延 15. switch (logicalOp) { 16. //无效类型 17. case LogicalOp::opInvalid: 18. // use 0 for unknown, non-specific 19. break; 20. case LogicalOp::opUpdate: //增 21. c.update.inc(micros); 22. break; 23. case LogicalOp::opInsert: //插入 24. c.insert.inc(micros); 25. break; 26. case LogicalOp::opQuery: //查询 27. c.queries.inc(micros); 28. break; 29. case LogicalOp::opGetMore: //getMore游标 30. c.getmore.inc(micros); 31. break; 32. case LogicalOp::opDelete: //删除 33. c.remove.inc(micros); 34. break; 35. case LogicalOp::opKillCursors: // 36. break; 37. case LogicalOp::opCommand: 38. c.commands.inc(micros); 39. break; 40. default: 41. MONGO_UNREACHABLE; 42. } 43.}
- 表级汇总型操做及时延统计
汇总型操做详细统计主要实现读、写、command操做统计及对应时延统计,这类操做核心代码实现以下:
- 按照不一样操做分类
3.t64_t latency, Command::ReadWriteType type) { 4. //肯定latency时延对应在[0-2]、(2-4]、(4-8]、(8-16]、(16-32]、(32-64]、(64-128]...中的那个区间 5. int bucket = _getBucket(latency); 6. switch (type) { 7. //读时延累加,操做计数自增 8. case Command::ReadWriteType::kRead: 9. _incrementData(latency, bucket, &_reads); 10. break; 11. //写时延累加,操做计数自增 12. case Command::ReadWriteType::kWrite: 13. _incrementData(latency, bucket, &_writes); 14. break; 15. //command时延累加,操做计数自增 16. case Command::ReadWriteType::kCommand: 17. _incrementData(latency, bucket, &_commands); 18. break; 19. default: 20. MONGO_UNREACHABLE; 21. } 22.}
2. 对应分类操做计数、时延计数
1.//OperationLatencyHistogram::increment中调用 2.//读 写 command总操做自增,时延对应增长latency 3.void OperationLatencyHistogram::_incrementData(uint64_t latency, int bucket, HistogramData* data) { 4. //落在bucket桶指定时延范围的对应操做数自增 5. data->buckets[bucket]++; 6. //该操做总计数 7. data->entryCount++; 8. //该操做总时延计数 9. data->sum += latency; 10.}
3. 时延范围分区桶统计
mongodb进行汇总型操做及时延统计后,能够获取整体的读、写、command平均时延,可是没法获取例如最大时延、95%分位时延、99分位时延等。mongodb为了知足这些需求,同时下降代码实现难度,经过分区时延统计来知足业务的这些需求。
时延范围分区桶实现原理:根据时延值,按照以下时延范围和分区桶得对应关系来完成统计操做,时延和桶的对应关系以下图所示:
时延范围分区桶核心算法实现核心代码实现以下:
1.//桶计数 2.void OperationLatencyHistogram::_incrementData(uint64_t latency, int bucket, HistogramData* data) { 3. //落在bucket桶指定时延范围的对应操做数自增 4. data->buckets[bucket]++; 5. ...... 6.} 7. 8.//不一样请求归类参考getReadWriteType 9.//Top::_incrementHistogram 操做和时延计数操做 10.void OperationLatencyHistogram::increment(uint64_t latency, Command::ReadWriteType type) { 11. //肯定latency时延对应在[0-2]、(2-4]、(4-8]、(8-16]、(16-32]、(32-64]、(64-128]...中的那个区间 12. int bucket = _getBucket(latency); 13. switch (type) { 14. //读时延累加,操做计数自增 15. case Command::ReadWriteType::kRead: 16. _incrementData(latency, bucket, &_reads); 17. break; 18. //写时延累加,操做计数自增 19. case Command::ReadWriteType::kWrite: 20. _incrementData(latency, bucket, &_writes); 21. break; 22. //command时延累加,操做计数自增 23. case Command::ReadWriteType::kCommand: 24. _incrementData(latency, bucket, &_commands); 25. break; 26. default: 27. MONGO_UNREACHABLE; 28. } 29.}
从上面的代码能够看出,汇总型统计中的读、写、command操做统计及时延统计包含该请求类型中的全部时延范围分区桶统计,已下图中的collection表read统计为例:
- reads.ops=reads.histogram[]数组count之和
- histogram.micros表明时延范围分区桶的时延边界值,例如二、四、八、16,以此类推。
3. 表级详细统计对外接口
3.1 表级别锁维度及请求类型维度相关统计接口
表级别锁维度及请求类型维度相关统计对外接口能够经过下面的命令获取获得(注:只能在mongod实例执行):
use admin
db.runCommand( { top: 1 } )
3.2 汇总型表级别统计
表级别汇总型读、写、command相关操做及时延统计能够经过以下命令获取:
db.collection.latencyStats( { histograms:false}).pretty()
不一样时间段对应有那些操做,例如那些操做时延比较高,能够经过时延范围分区桶统计接口获取: