记一次线上服务器CPU占用100%事故

由于一些突发状况,本应在四月底完成的这篇博客拖到现在才完成。这篇博客要讲述的是一个午夜惊魂的故事,请各位看官自备薯条可乐。

在一个月黑风高的凌晨,我刚刚完成了一个订单导出功能的SQL优化,准备卷铺盖安寝了。在就寝前习惯性地看了一眼服务器数据,突然发现CPU飙升到100%!我马上打开订单列表,查看了一下订单状态,发现大量订单出现未处理堆积,这下问题大发了!果不其然,过了一会客户的电话就打过来了。

虽然我真的很困,但是越到这种时候越不能慌,于是我开始冷静地排查问题。

处理流程

  1. 通过top ^P 查看是哪个进程占用了CPU,结果发现是MySQL。同时查看了一下rabbitMQ的队列,里面也同样是堆积了大量的消息,并且消耗得非常慢,分析了一下,感觉根本问题还是出在数据库上。
    cpu
  2. 使用Navicat执行命令show full processlist;结果发现MySQL中有大量正在执行的Query,这些执行中的Query就是慢SQL。
    MySQL
  3. 将这些慢SQL使用explain命令查看执行计划,发现是一个查询订单状态的SQL在被频繁执行。这个SQL其实非常简单,就是根据客户订单号来查询某个订单的状态,但是由于订单表数据量已经超过了15万,而客户订单号字段没有索引,所以执行一次的耗时达到了一两秒左右。
    explain
  4. 解决方法其实非常简单:给客户订单号字段加上BTREE索引即可。

问题反思

1. 根因分析

其实这种耗时一两秒的慢SQL起初并不会造成数据库的僵直,那么到底是什么导致了这次事故呢?于是我查看了一下订单列表和下单接口的accesslog,发现在短时间内产生了大量订单,并且有一个下游每隔5秒左右就会请求一次订单状态查询接口(也就是执行上面的那个慢SQL的接口)。于是不难推断出,本次事故的诱因是短时间内的大量下单冲击,数据库的执行速度开始变慢,而此时每5秒执行一次的慢SQL的问题则被放大了,反过来占用了更多的CPU资源,导致下单速度更慢了,在互相影响的作用下,不幸发生了雪崩效应。所以,在给客户订单号字段添加了索引之后,慢SQL的执行效率提升了一万倍,瞬间就将堆积的查询请求执行完了,而当CPU占用下降后,其他SQL的执行也迅速恢复正常,堆积在消息队列中的下单请求也迅速被清空。

2. 防控措施

之前真的没有想到MySQL会在十几万数据量级时就遇到性能瓶颈。这也给自己积累了一点经验:在数据量可能短时间内膨胀的项目中,在表设计阶段一定要提前考虑好索引设计,并通过技术手段全面检查慢SQL。据说腾讯云的MySQL就提供了慢SQL监控服务,到时候可以实践一下。