前言

通过 top 命令,可以看到 MongoDB 的 CPU 使用率过高,CPU 过高会导致数据读写、处理异常缓慢,还会出现被系统抹杀进程的风险,这个问题 99.9999% 的可能性是用户使用上不合理导致的,本文介绍如何从应用的角度如何排查 MongoDB CPU 利用率过高的问题。

分析数据库正在执行的请求

执行 db.currentOp() 命令,可以查看数据库当前正在执行的操作

该命令的输出示例如下:

{
"desc" : "conn632530",
"threadId" : "140298196924160",
"connectionId" : 632530,
"client" : "11.192.159.236:57052",
"active" : true,
"opid" : 1008837885,
"secs_running" : 0,
"microsecs_running" : NumberLong(70),
"op" : "update",
"ns" : "mygame.players",
"query" : {
"uid" : NumberLong(31577677)
},
"numYields" : 0,
"locks" : {
"Global" : "w",
"Database" : "w",
"Collection" : "w"
},
....
},

需要重点关注以下几个字段

字段 返回值说明
client 该请求是由哪个客户端发起的。
opid 操作的唯一标识符。 说明 如果有需要,可以通过db.killOp(opid)直接终止该操作。
secs_running 表示该操作已经执行的时间,单位为秒。如果该字段返回的值特别大,需要查看请求是否合理。
microsecs_running 表示该操作已经执行的时间,单位为微秒。如果该字段返回的值特别大,需要查看请求是否合理。
ns 该操作目标集合。
op 表示操作的类型。通常是查询、插入、更新、删除中的一种。
locks 跟锁相关的信息,详情请参见并发介绍

分析数据库慢请求

MongoDB 支持 profiling 记录慢查询日志功能,将请求的执行情况记录到同 DB 下的 system.profile 集合里,它可以帮助我们进行优化我们的 sql,并提高我们系统的稳定性和流畅性。

默认情况下是关闭的,我们可以在数据库级别上或者是节点级别上配置。

profiling 有3种模式,状态码及相关描述:

  • 0:表示关闭 profiling 慢查询,默认情况下
  • 1:表示超过阈值的查询收集,针对所有请求开启 profiling,将所有请求的执行都记录到 system.profile 集合
  • 2:为所有数据库开启慢查询记录,收集所有的数据 针对慢请求 profiling,将超过一定阈值的请求,记录到 system.profile 集合

MongoDB慢查询两种启动方式

通过 MongoDB shell 启用

为所有数据库开启慢查询记录

db.setProfilingLevel(2)

禁用慢查询

db.setProfilingLevel(0)

指定 test 数据库,并指定阈值慢查询 ,超过20毫秒的查询被记录

use test

db.setProfilingLevel(1, { slowms: 20 })

随机采集慢查询的百分比值,sampleRate 值默认为1,表示都采集,0.42 表示采集42%的内容

db.setProfilingLevel(1, { sampleRate: 0.42 })

查询慢查询级别和其它信息

db.getProfilingStatus()

仅返回慢查询级别

db.getProfilingLevel()

通过配置文件启用

在 ini 配置文件 mongodb.conf 添加以下参数, profile 参数是设置开启等级,slowms 是设置阈值

profile = 1

slowms = 300

在 YAML 文件配置

operationProfiling:
mode: <string> # 默认为 off,可选值 off、slowOp(对应上面的等级 1)、all(对应上面的等级 2)
slowOpThresholdMs: <int> # 阈值,默认值为100,单位毫秒
slowOpSampleRate: <double> # 随机采集慢查询的百分比值,sampleRate 值默认为1,表示都采集,0.42 表示采集42%的内容

常用命令和示例

# 查询慢请求日志
db.system.profile.find().pretty() # 查询最近的10个慢查询日志
db.system.profile.find().limit(10).sort( { ts : -1 } ).pretty() # 查询除命令类型为 command 的日志
db.system.profile.find( { op: { $ne : 'command' } } ).pretty() # 查询数据库为 mydb 集合为 test 的 日志
db.system.profile.find( { ns : 'mydb.test' } ).pretty() # 查询 低于 5毫秒的日志
db.system.profile.find( { millis : { $gt : 5 } } ).pretty() # 查询时间从 2012-12-09 3点整到 2012-12-09 3点40分之间的日志
db.system.profile.find({
ts : {
$gt: new ISODate("2012-12-09T03:00:00Z"),
$lt: new ISODate("2012-12-09T03:40:00Z")
}
}).pretty()

MongoDB慢日志解析

官方文档:https://docs.mongodb.com/manual/reference/database-profiler/index.html

{
"op" : "query", # 操作类型,值可为command、count、distinct、geoNear、getMore、group、insert、mapReduce、query、remove、update
"ns" : "test.report", # 操作的数据库和集合
"command" : { # 命令
"find" : "report", # 操作的集合
"filter" : { "a" : { "$lte" : 500 } }, # 查询条件
"lsid" : {
"id" : UUID("5ccd5b81-b023-41f3-8959-bf99ed696ce9") #用户的会话id
},
"$db" : "test" # 操作的数据库
},
"cursorid" : 33629063128, # query和getmore 的游标id
"keysExamined" : 101, # MongoDB为执行操作而扫描的索引键的数量
"docsExamined" : 101, # MongoDB为了执行操作而扫描的集合中的文档数。
"numYield" : 2, # 让步次数,操作时让其他的操作完成的次数。
"nreturned" : 101, # 操作返回的文档数
"queryHash" : "811451DD", # 查询的hash值
"planCacheKey" : "759981BA",
"locks" : { # 操作期间的锁和所的类型
"Global" : { #表示全局锁定
"acquireCount" : { #锁定的次数
"r" : NumberLong(3) # 表示共享锁
}
},
"Database" : { # 数据库锁
"acquireCount" : { "r" : NumberLong(1) },
"acquireWaitCount" : { "r" : NumberLong(1) },
"timeAcquiringMicros" : { "r" : NumberLong(69130694) }
},
"Collection" : { # 集合锁
"acquireCount" : { "r" : NumberLong(1) }
}
},
"storage" : { # 储存
"data" : {
"bytesRead" : NumberLong(14736), #操作 从磁盘放到缓存的数据的字节数
"timeReadingMicros" : NumberLong(17) # 操作 花费在磁盘读取的时间,以微妙为单位
}
},
"responseLength" : 1305014, # 操作返回结果的文档长度,单位为字节
"protocol" : "op_msg", # 消息的协议
"millis" : 69132, # 从 MongoDB 操作开始到结束耗费的时间
"planSummary" : "IXSCAN { a: 1, _id: -1 }", # 摘要
"execStats" : { # 操作执行过程中的详细信息
"stage" : "FETCH", # 操作形式 ,COLLSCAN 用于集合扫描,IXSCAN 用于扫描索引键,FETCH 用于检索文档
"nReturned" : 101, # 返回的文档数量
"executionTimeMillisEstimate" : 0,
"works" : 101,
"advanced" : 101,
"needTime" : 0,
"needYield" : 0,
"saveState" : 3,
"restoreState" : 2,
"isEOF" : 0,
"invalidates" : 0,
"docsExamined" : 101,
"alreadyHasObj" : 0,
"inputStage" : {
...
}
},
"ts" : ISODate("2019-01-14T16:57:33.450Z"), #操作的时间戳
"client" : "127.0.0.1", # 客户端的ip
"appName" : "MongoDB Shell", #客户端应用标识符
"allUsers" : [
{
"user" : "someuser", # 用户
"db" : "admin" # 验证的数据库
}
],
"user" : "someuser@admin" # 经过验证的用户
}

CPU杀手1:全表扫描

全集合(表)扫描 COLLSCAN,当一个查询(或更新、删除)请求需要全表扫描时,是非常耗CPU资源的,所以当你在 system.profile 集合 或者 日志文件发现 COLLSCAN 关键字时,就得注意了,很可能就是这些查询吃掉了你的 CPU 资源;确认一下,如果这种请求比较频繁,最好是针对查询的字段建立索引来优化。

一个查询扫描了多少文档,可查看 system.profile 里的 docsExamined 的值,该值越大,请求CPU开销越大。

关键字:COLLSCAN、 docsExamined

CPU杀手2:不合理的索引

有的时候,请求即使查询走了索引,执行也很慢,通常是因为合理建立不太合理(或者是匹配的结果本身就很多,这样即使走索引,请求开销也不会优化很多)。

通过查看 keysExamined 字段,可以查看到一个使用了索引的查询,扫描了多少条索引。该值越大,CPU开销越大。

如果索引建立的不太合理,或者是匹配的结果很多。这样即使使用索引,请求开销也不会优化很多,执行的速度也会很慢。

如下所示,假设某个集合的数据,x字段的取值很少(假设只有1、2),而y字段的取值很丰富。

{ x: 1, y: 1 }
{ x: 1, y: 2 }
{ x: 1, y: 3 }
......
{ x: 1, y: 100000}
{ x: 2, y: 1 }
{ x: 2, y: 2 }
{ x: 2, y: 3 }
......
{ x: 1, y: 100000}

要实现 {x: 1: y: 2} 这样的查询

db.createIndex( {x: 1} )         效果不好,因为x相同取值太多
db.createIndex( {x: 1, y: 1} ) 效果不好,因为x相同取值太多
db.createIndex( {y: 1 } ) 效果好,因为y相同取值很少
db.createIndex( {y: 1, x: 1 } ) 效果好,因为y相同取值少

一个走索引的查询,扫描了多少条索引,可查看 system.profile 里的 keysExamined 字段,该值越大,CPU 开销越大。

关键字:IXSCAN、keysExamined

  • 索引不是越多越好,索引过多会影响写入、更新的性能。
  • 如果您的应用偏向于写操作,索引可能会影响性能。

CPU杀手3:大量数据排序

当查询请求里包含排序的时候,如果排序无法通过索引满足,MongoDB 会在内存李结果进行排序,而排序这个动作本身是非常耗 CPU 资源的,优化的方法仍然是建立索引,对经常需要排序的字段,建立索引。

当你在 system.profile 集合 或者 日志文件发现 SORT 关键字时,就可以考虑通过索引来优化排序。当请求包含排序阶段时, system.profile 里的 hasSortStage 字段会为 true

关键字:SORT、hasSortStage

其他还有诸如建索引,aggregationv 等操作也可能非常耗 CPU 资源,但本质上也是上述几种场景;建索引需要全表扫描,而 vaggeregation 也是遍历、查询、更新、排序等动作的组合。

服务能力评估

经过上述分析数据库正在执行的请求和分析数据库慢请求两轮优化之后,你发现整个数据库的查询非常合理,所有的请求都是高效的走了索引,基本没有优化的空间了,那么很可能是你机器的服务能力已经达到上限了,应该升级配置了(或者通过 sharding 扩展)。

当然最好的情况时,提前对 MongoDB 进行测试,了解在你的场景下,对应的服务能力上限,以便及时扩容、升级,而不是到 CPU 资源用满,业务已经完全撑不住的时候才去做评估。

巨人的肩膀

https://www.cnblogs.com/operationhome/p/10728654.html

https://help.aliyun.com/document_detail/62224.html

https://mongoing.com/archives/3998

解决排查 mongodb cpu使用率过高的更多相关文章

  1. 排查MongoDB CPU使用率高的问题

    1.公司业务调整,把一部分数据由Redis转至MongoDB,业务在测试环境正常,生产环境上线后发现压力一上来MongoDB的服务直接把CPU占满了,和开发的同学分析了一下也参考了一下百度上类似的问题 ...

  2. 服务器CPU使用率过高排查与解决思路

    发现服务器的cpu使用率特别高 排查思路: -使用top或者mpstat查看cpu的使用情况# mpstat -P ALL 2 1Linux 2.6.32-358.el6.x86_64 (linux— ...

  3. 排查tomcat服务器CPU使用率过高

    tomcat要运行依赖于JDK,tomcat服务器的CPU使用率过高,大多都是因为部署的web程序的问题. 一.现象描述 在一次线上环境,前台访问页面的速度越来越慢,从浏览器F12中看到发出的请求都是 ...

  4. kubelet CPU 使用率过高问题排查

    kubelet CPU 使用率过高问题排查 问题背景 客户的k8s集群环境,发现所有的worker节点的kubelet进程的CPU使用率长时间占用过高,通过pidstat可以看到CPU使用率高达100 ...

  5. MongoDB优化之三:如何排查MongoDB CPU利用率高的问题

    遇到这个问题,99.9999% 的可能性是「用户使用上不合理导致」,本文主要介绍从应用的角度如何排查 MongoDB CPU 利用率高的问题. Step1: 分析数据库正在执行的请求 用户可以通过 M ...

  6. mysql cpu使用率过高解决方法

    mysql cpu使用率过高解决方法 1 mysql查看正在运行的语句 并且查看运行最多的mysql语句 MySQL 打开 general log 后,所有的查询语句都会记录在 general log ...

  7. 空循环导致CPU使用率很高

    业务背景 业务背景就是需要将多张业务表中的数据增量同步到一张大宽表中,后台系统基于这张大宽表开展业务,所以就开发了一个数据同步工具,由中间件采集binlog消息到kafka里,然后我去消费,实现增量同 ...

  8. 06讲案例篇:系统的CPU使用率很高,但为啥却找不到高CPU的应用

    小结 碰到常规问题无法解释的 CPU 使用率情况时,首先要想到有可能是短时应用导致的问题,比如有可能是下面这两种情况. 第一,应用里直接调用了其他二进制程序,这些程序通常运行时间比较短,通过 top ...

  9. 《Troubleshooting SQL Server》读书笔记-CPU使用率过高(下)

    <Troubleshooting SQL Server>读书笔记-CPU使用率过高(下) 第三章 High CPU Utilization. CPU使用率过高的常见原因 查询优化器会尽量从 ...

  10. Oracle查询语句导致CPU使用率过高问题处理

    解决此问题的关键在于如何找到造成CPU使用率过高的SQL语句.步骤如下: 1.使用Process Explorer工具查看到Oracle进程,双击Oracle进程,在弹出的属性窗口的Threads选项 ...

随机推荐

  1. UML之模型、包及包的版型(构造型)

    包是UML模型的组织结构,也是UML项目的配置管理结构.包存在多个层级,除了顶层包,所有包隶属于一个且仅隶属于一个上层包.在项目不同阶段实际推进与配置过程中,通常以不同层级的包为单位进行check-i ...

  2. 在CLion中如何为CMakeLists.txt文件添加第三方依赖库

    cmake_minimum_required(VERSION 3.5)project(ImageBasedModellingEdu)set(CMAKE_MODULE_PATH "${CMAK ...

  3. [转]Vetur can't find `tsconfig.json` or `jsconfig.json` in d:\VueProjects\myroute.

    vue界面启动项目 visual code报错 如下图,找到 Ignore Project Warning 前边打上对勾

  4. Python学习(一)——配套《PyTorch深度学习实战》

    记录一下Python学习过程中的一些问题: 1. 在JupyterLab中查询当前文件的地址 import os print(os.getcwd()) #查询该文件的地址 2. 新建cell 在 Ju ...

  5. 零基础Windows Server搭建部署Word Press 博客系列教程(2):从菜鸡到高手之Windows Server 环境配置

    上一篇:零基础Windows Server搭建部署Word Press 博客系列教程(1):从萌新到菜鸡之云主机配置与备案 本篇教程主要介绍在云主机上安装好相关组件并配置好环境,直至网站上线. 1.之 ...

  6. Wfurent 语语法概述

    \[\textit{Litar!} \newcommand{\wd}[2]{\texttt{#1}^{#2}} \] 让神明白   文字产生于史前的祭祀,史前的人们改造了原始的语言规则,使得句子拥有& ...

  7. WPFMediaKit --WPF项目中 调用摄像头拍照

    <Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/200 ...

  8. w3cschool-HBase官方文档-2数据模型

    HBase数据模型 2018-03-03 15:20 更新 HBase数据模型 在 HBase 中,数据模型同样是由表组成的,各个表中又包含数据行和列,在这些表中存储了 HBase 数据.在本节中,我 ...

  9. runoob-scipy(python)

    https://www.runoob.com/scipy/scipy-tutorial.html SciPy 教程 SciPy 是一个开源的 Python 算法库和数学工具包. Scipy 是基于 N ...

  10. centos8网络配置问题

    由于RHEL8与centos8基本一样,所以以下方法同样适用于RHEL8 在centos8上进行网络配置时,出现以下问题: 意思是无法找到network.service 出现错误的原因是centos8 ...