關於做者
前滴滴出行技術專家,現任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()
不一樣時間段對應有那些操做,例如那些操做時延比較高,能夠經過時延範圍分區桶統計接口獲取: