MongoDB CPU 利用率很高,都快跑满了,如何解决?=》遇到这个问题,99.9999% 的可能性是「用户使用上不合理导致。

从应用的角度如何排查原因如下:

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

mongos> db.currentOp()
{
 "inprog" : [
 {
 "host" : "wiki:27017",
 "desc" : "conn",
 "threadId" : "139653102868224",
 "connectionId" : 1,
 "client" : "127.0.0.1:44426",
 "appName" : "MongoDB Shell",
 "clientMetadata" : {
 "application" : {
 "name" : "MongoDB Shell"
 },
 "driver" : {
 "name" : "MongoDB Internal Client",
 "version" : "3.6.2"
 },
 "os" : {
 "type" : "Linux",
 "name" : "CentOS Linux release 7.2.1511 (Core) ",
 "architecture" : "x86_64",
 "version" : "Kernel 3.10.0-327.el7.x86_64"
 }
 },
 "active" : true,
 "currentOpTime" : "2018-04-25T14:01:23.855+0800",
 "opid" : 529740,
 "secs_running" : NumberLong(0),
 "microsecs_running" : NumberLong(252),
 "op" : "command",
 "ns" : "admin.$cmd.aggregate",
 "command" : {
 "currentOp" : 1,
 "$db" : "admin"
 },
 "numYields" : 0,
 "locks" : {
 
 },
 "waitingForLock" : false,
 "lockStats" : {
 
 }
 }
 ],
 "ok" : 1
}


查看到数据库当前正在执行的操作,重点关注几个字段:

  • client:请求是由哪个客户端发起的?
  • opid:操作的opid,有需要的话,可以通过 db.killOp(opid) 直接干掉的操作;
  • secs_running/microsecs_running: 这个值重点关注,代表请求运行的时间,如果这个值特别大,就得注意了,看看请求是否合理;
  • query/ns: 这个能看出是对哪个集合正在执行什么操作;
  • lock*:还有一些跟锁相关的参数,需要了解可以看官网文档,本文不做详细介绍。




{


                 "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"


                 }         ,


                 .         .         .         .


}         ,


这里先要明确一下,通过 db.currentOp() 查看正在执行的操作,目的是否有「意料之外」的耗时请求正在执行。

例如:业务平时 CPU 利用率不高,运维管理人员连到数据库执行了一些需要全表扫描的操作,然后突然 CPU 利用率飙高,导致你的业务响应很慢,那么就要重点关注下那些执行时间很长的操作。

一旦找到罪魁祸首,拿到对应请求的 opid,执行 db.killOp(opid) 将对应的请求干掉。

如果应用cpu利用率就很高,而且一直持续,通过 db.currentOp 的结果也没发现什么异常请求,可以进入到 Step2 进行更深入的分析。

Step2:分析数据库慢请求

MongoDB 支持 profiling 功能(慢查询详解),将请求的执行情况记录到同DB下的 system.profile 集合里,profiling 有3种模式:

  • 关闭 profiling;
  • 针对所有请求开启 profiling,将所有请求的执行都记录到 system.profile 集合;
  • 针对慢请求 profiling,将超过一定阈值的请求,记录到system.profile 集合。

默认请求下,MongoDB 的 profiling 功能是关闭,生产环境建议开启,慢请求阈值可根据需要定制,如不确定,直接使用默认值100ms。




operationProfiling:        


           mode         :                   slowOp


           slowOpThresholdMs:                   100


基于上述配置,MongoDB 会将超过 100ms 的请求记录到对应DB 的 system.profile 集合里,system.profile 默认是一个最多占用 1MB 空间的 capped collection。

查看最近3条 慢请求,{$natrual: -1} 代表按插入数序逆序

db.system.profile.find().sort({$natrual: -1}).limit(3)

在开启了慢请求 profiling 的情况下(MongoDB 云数据库是默认开启慢请求 profiling的),我们对慢请求的内容进行分析,来找出可优化的点,常见的包括。

 如:
db.system.profile.find( { millis : { $gt : 1000 } } )
就可以输出,查询时间大于1秒的慢查询。


#######################附加内容###################################

注:MongoDB Database Profiling说明


MongoDB Profiler是一个捕获数据库执行活动的系统,它可以帮助识别慢查询和操作。


有两种方式可以控制Profiling的开关和级别 ,


第一种是直接在启动参数中进行设置,如下:


如果想要全局开启Profiling,则可以在mongod启动时加上参数


1 --slowms 100         # 1表示追踪级别,100表示慢查询判定时间(ms)



1. operationProfiling:  
2.      slowOpThresholdMs: 100  
3.      mode: slowOp


第二种是在mongo shell进行实时配置,命令如下:





1. > db.setProfilingLevel(1,200)          ---1表示level,200表示慢查询时间(ms),也可以省略时间设置  
2. { "was" : 1, "slowms" : 100, "ok" : 1 }     ---100表示之前的慢查询时间设定值  
3. > db.getProfilingStatus()               ---查询当前慢查询的状态信息  
4. { "was" : 1, "slowms" : 200 }          ---was后的值表示级别  
5. > db.getProfilingLevel()               ---只查询Profiling级别可用此命令  
6. 1

Profiling级别可用的捕获级别意义如下:

级别 设置

0 禁用

1 启用,只记录慢操作

2 启用,记录所有操作

查看Profiling级别

> db.getProfilingLevel() 
0

启用Profiler

> db.setProfilingLevel(1)

完整命令为:

db.setProfilingLevel(level,slowms)

 EX:> db.setProfilingLevel(1,200) 
{ "was" : 0, "slowms" : 100, "sampleRate" : 1, "ok" : 1 }

当level为1的时候,慢操作的默认值为100ms,若指定慢操作为500ms:

> db.setProfilingLevel(1,500)


注意:

在默认情况下,mongod记录所有的慢查询(由showOpThresholdMs定义,默认值为100ms)到MongoDB日志文件中。

只在关键时候启用Profiling,尽量不要在生产环境启用它。

基于独立mongod实例启用Profiling。该设置将不会通过副本集或分片集群扩散到其他实例。

#############################################


CPU杀手1:全表扫描

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

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

> 关键字:COLLSCAN、 docsExamined

CPU杀手2:不合理的索引

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

如下所示,假设某个集合的数据,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相同取值少         ;


至于{y: 1} 与 {y: 1, x: 1} 的区别,可参考MongoDB索引原理 及 复合索引官方文档 自行理解。

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

>关键字:IXSCAN、keysExamined

CPU杀手3:大量数据排序

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

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

> 关键字:SORT、hasSortStage

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


Step3: 服务能力评估

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

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