全部产品
阿里云办公

MongoDB CPU利用率高,怎么破?

更新时间:2017-11-14 17:07:34

背景信息

在使用MongoDB云数据库的时候您可能经常遇到一个问题:MongoDB CPU利用率很高,都快跑满了,应该怎么办? 遇到这个问题,99.9999%的可能性是您使用上不合理导致。本文主要帮助您从应用的角度排查MongoDB CPU利用率高的问题。

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

您可以通过Mongo Shell连接数据库,并执行db.currentOp()命令,查看数据库当前正在执行的操作。如下是该命令的一个输出示例,标识一个正在执行的操作。

  1. {
  2. "desc" : "conn632530",
  3. "threadId" : "140298196924160",
  4. "connectionId" : 632530,
  5. "client" : "11.192.159.236:57052",
  6. "active" : true,
  7. "opid" : 1008837885,
  8. "secs_running" : 0,
  9. "microsecs_running" : NumberLong(70),
  10. "op" : "update",
  11. "ns" : "mygame.players",
  12. "query" : {
  13. "uid" : NumberLong(31577677)
  14. },
  15. "numYields" : 0,
  16. "locks" : {
  17. "Global" : "w",
  18. "Database" : "w",
  19. "Collection" : "w"
  20. },
  21. ....
  22. },

重点关注以下几个字段:

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

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

比如您的业务平时CPU利用率不高,运维管理人员连到数据库执行了一些需要全表扫描的操作,然后突然CPU利用率飙高,导致你的业务响应很慢,那么就要重点关注下那些执行时间很长的操作。拿到对应请求的opid,执行db.killOp(opid)终止对应请求。

如果您的应用一上线,cpu利用率就很高,而且一直持续,执行db.currentOp(),结果也没发现什么异常请求,可以进行更深入的分析即:分析数据库慢请求。

分析数据库慢请求

MongoDB支持profiling功能,将请求的执行情况记录到同DB下的system.profile集合里,profiling有三种模式:

  • 关闭profiling。

  • 针对所有请求开启profiling,将所有请求的执行都记录到system.profile集合。

  • 针对慢请求profiling,将超过一定阈值的请求,记录到system.profile集合。

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

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

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

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

  • 全表扫描(关键字:COLLSCAN、 docsExamined)

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

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

  • 不合理的索引(关键字:IXSCAN、keysExamined)

    有时请求即使查询使用了索引,执行也很慢,通常是因为索引建立不太合理(或者是匹配的结果本身就很多,这样即使使用索引,请求开销也不会优化很多)。如下所示,假设某个集合的数据,x字段的取值很少(假设只有1、2),而y字段的取值很丰富。

    1. { x: 1, y: 1 }
    2. { x: 1, y: 2 }
    3. { x: 1, y: 3 }
    4. ......
    5. { x: 1, y: 100000}
    6. { x: 2, y: 1 }
    7. { x: 2, y: 2 }
    8. { x: 2, y: 3 }
    9. ......
    10. { x: 1, y: 100000}

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

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

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

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

  • 大量数据排序(关键字:SORT、hasSortStage)

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

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

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

更多profiling的设置请参考:profiling官方文档

服务能力评估

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

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