凌晨两点,我在睡梦中被手机震醒了。拿出来一看,三分钟内收到了十几条报警——网站首页响应时间从正常的200毫秒飙到了三十多秒,三分之一用户的请求返回了502错误。
说实话,做运维这几年最怕的就是半夜被手机震醒。但既然醒了,就得干活。
第一件事不是去看代码,是先恢复服务。我登录到服务器上看了下负载,CPU跑满了,内存占用也在百分之九十以上。最紧急的情况最忌讳"先查查原因再说"——应该先止血,再找病因。我扩展了一下后端实例,同时把CDN的缓存策略从之前的五分钟改成了一小时,让静态资源尽可能从CDN走,减少源站压力。大概五分钟之后,502的比例降到了百分之五以下,算是暂时稳住了。
然后开始查原因。翻了一下日志,发现一台后端服务器的错误日志里有一行报错反复出现,是一个数据库连接池的异常。这个异常本身不大,但因为那台服务器扛不住之后,请求被打到另一台服务器上,导致连锁的超负荷。我锁定了这台问题服务器,从负载均衡里摘掉它,让它下线。剩余的请求被最后两台服务器分担,虽然CPU还是偏高,但至少不会大面积502了。
凌晨三点半,我大致判断出根因了。是上周上线的一个新功能里有一段SQL查询没有做分页,也没有加索引,查询一张数据量超过五十万行的表,直接把数据库的连接池吃满了。这个功能在白天的访问量下不明显,到了凌晨服务器进行日切任务的时候,两个操作撞在一起,把系统资源抢完了。
修复不难,给那张表加上联合索引,同时给那个查询加了个LIMIT限制。但从这次故障里我学到两件事。第一,上线前必须审查SQL语句,不只是看功能逻辑,还要看数据量级下的执行计划。第二,报警阈值设得太宽了。我们的报警只设了"响应时间超过2秒"这一档,但其实应该在1秒的时候就出一个预警,这样我就能在白天处理,不用凌晨爬起来。
第二天我跟开发团队开了一个短会,定了两条规则:以后所有SQL上线之前必须走一遍慢查询检测,另外增加一档"黄色预警",响应时间超过800毫秒就通知,不紧急,但给时间提前干预。
服务器运维托管