经常有用户咨询「mongodb cpu 利用率很高,都快跑满了」,应该怎么办?
遇到这个问题,99.9999% 的可能性是「用户使用上不合理导致」,本文主要介绍从应用的角度如何排查 mongodb cpu 利用率高的问题
用户可以通过 mongo shell 连接,并执行 <code>db.currentop()</code> 命令,能看到数据库当前正在执行的操作,如下是该命令的一个输出示例,标识一个正在执行的操作。重点关注几个字段
client:请求是由哪个客户端发起的?
opid:操作的opid,有需要的话,可以通过 db.killop(opid) 直接干掉的操作
secs_running/microsecs_running: 这个值重点关注,代表请求运行的时间,如果这个值特别大,就得注意了,看看请求是否合理
query/ns: 这个能看出是对哪个集合正在执行什么操作
lock*:还有一些跟锁相关的参数,需要了解可以看官网文档,本文不做详细介绍
<a href="https://docs.mongodb.com/manual/reference/method/db.currentop/">db.currentop 文档在这里,多看官网文档</a>
这里先要明确一下,通过 db.currentop() 查看正在执行的操作,目的到底是什么?
并不是说我们要将正在执行的操作都列出来,然后通过 <code>killop</code> 逐个干掉;这一步的目的是要看一下,是否有「意料之外」的耗时请求正在执行。
比如你的业务平时 cpu 利用率不高,运维管理人员连到数据库执行了一些需要全表扫描的操作,然后突然 cpu 利用率飙高,导致你的业务响应很慢,那么就要重点关注下那些执行时间很长的操作。
一旦找到罪魁祸首,拿到对应请求的 opid,执行 <code>db.killop(opid)</code> 将对应的请求干掉。
如果你的应用一上线,cpu利用率就很高,而且一直持续,通过 <code>db.currentop</code> 的结果也没发现什么异常请求,可以进入到 step2 进行更深入的分析。
mongodb 支持 profiling 功能,将请求的执行情况记录到同db下的 <code>system.profile</code> 集合里,profiling 有3种模式
<a href="https://docs.mongodb.com/manual/tutorial/manage-the-database-profiler/">profiling 设置文档在这里,多看官网文档</a>
关闭 profiling
针对所有请求开启 profiling,将所有请求的执行都记录到 <code>system.profile</code> 集合
针对慢请求 profiling,将超过一定阈值的请求,记录到<code>system.profile</code> 集合
默认请求下,mongodb 的 profiling 功能是关闭,生产环境建议开启,慢请求阈值可根据需要定制,如不确定,直接使用默认值100ms。
基于上述配置,mongodb 会将超过 100ms 的请求记录到对应db 的 <code>system.profile</code> 集合里,<code>system.profile</code> 默认是一个最多占用 1mb 空间的 capped collection。
<a href="https://docs.mongodb.com/manual/reference/database-profiler/">profiling的结果输出含义在这里,多看官网文档</a>
cpu杀手1:全表扫描
全集合(表)扫描 <code>collscan</code>,当一个查询(或更新、删除)请求需要全表扫描时,是非常耗cpu资源的,所以当你在 <code>system.profile</code> 集合 或者 日志文件发现 <code>collscan</code> 关键字时,就得注意了,很可能就是这些查询吃掉了你的 cpu 资源;确认一下,如果这种请求比较频繁,最好是针对查询的字段建立索引来优化。
一个查询扫描了多少文档,可查看 <code>system.profile</code> 里的 <code>docsexamined</code> 的值,该值越大,请求cpu开销越大。
关键字:collscan、 docsexamined
cpu杀手2:不合理的索引
有的时候,请求即使查询走了索引,执行也很慢,通常是因为索引建立不太合理(或者是匹配的结果本身就很多,这样即使走索引,请求开销也不会优化很多)。
如下所示,假设某个集合的数据,x字段的取值很少(假设只有1、2),而y字段的取值很丰富。
要服务 <code>{x: 1: y: 2}</code> 这样的查询
一个走索引的查询,扫描了多少条索引,可查看 <code>system.profile</code> 里的 <code>keysexamined</code> 字段,该值越大,cpu 开销越大。
关键字:ixscan、keysexamined
cpu杀手3:大量数据排序
当查询请求里包含排序的时候,如果排序无法通过索引满足,mongodb 会在内存李结果进行排序,而排序这个动作本身是非常耗 cpu 资源的,优化的方法仍然是建立索引,对经常需要排序的字段,建立索引。
当你在 <code>system.profile</code> 集合 或者 日志文件发现 <code>sort</code> 关键字时,就可以考虑通过索引来优化排序。当请求包含排序阶段时, <code>system.profile</code> 里的 <code>hassortstage</code> 字段会为 true。
关键字:sort、hassortstage
其他还有诸如建索引,aggregationv等操作也可能非常耗 cpu 资源,但本质上也是上述几种场景;建索引需要全表扫描,而vaggeregation 也是遍历、查询、更新、排序等动作的组合。
经过上述2步,你发现整个数据库的查询非常合理,所有的请求都是高效的走了索引,基本没有优化的空间了,那么很可能是你机器的服务能力已经达到上限了,应该升级配置了(或者通过 sharding 扩展)。
当然最好的情况时,提前对 mongodb 进行测试,了解在你的场景下,对应的服务能力上限,以便及时扩容、升级,而不是到 cpu 资源用满,业务已经完全撑不住的时候才去做评估。