0
点赞
收藏
分享

微信扫一扫

访问数据库超时问题排障

1 问题描述

系统从圣诞节晚开始,每晚固定十点多到十一点多时段,大概瘫痪1h,过这时段系统自动恢复。系统瘫痪现象就是,网页和App都打不开,请求超时。系统架构:

访问数据库超时问题排障_缓存

整个系统托管在公有云,Nginx前置网关承接前端所有请求,后端按业务划微服务。数据保存在MySQL,部分数据Memcached前置缓存。数据并没按微服务最佳实践要求,做严格划分和隔离,而是为方便,存一起。这对一个业务变化极快的创业公司合理。因为它每个微服务,随时都在随业务改变,若严格数据隔离,反而不利应对需求变化。

2 场景分析

第一反应每天晚上十点到十一点这个时段,是绝大多数内容类App的访问量高峰,因为这个时候大家都躺在床上玩儿手机。初步判断和访问量有关。每天访问量图可说明:

访问数据库超时问题排障_SQL_02

排查重点应在那些服务于用户访问的功能。如首页、商品列表页、内容推荐。在访问量峰值时,请求全部超时,随访问量减少,系统能自动恢复,基本排除后台服务被大量请求打死的可能性,因为若进程被打死,一般不会自动恢复。

排查重点应在MySQL。观察MySQL CPU利用率:

访问数据库超时问题排障_MySQL_03

故障时段MySQL CPU利用率一直100%,MySQL基本处不可用态,执行所有SQL都超时。这种CPU利用率高,大多慢SQL导致,优先排查。MySQL和各大云厂商提供的RDS都能提供慢SQL日志,分析慢SQL日志,是查找类似问题原因最有效方法。

一般慢SQL日志有信息:SQL、执行次数、执行时长。通过分析慢SQL找问题,没标准方法,纯靠经验。

数据库忙时,执行任何一个SQL都很慢。所以,不是说慢SQL日志中记录的这些慢SQL都是有问题SQL。大部分导致问题的SQL只是一或几条。不能简单依据执行次数和执行时长判断,但单次执行时间特长的SQL,仍是重点排查对象。

于是就找到一个特别慢SQL:大V排行榜,列出粉丝数最多TOP10大V。

select fo.FollowId as vid, count(fo.id) as vcounts
from follow fo, user_info ui
where fo.userid = ui.userid
and fo.CreateTime between
str_to_date(?, '%Y-%m-%d %H:%i:%s')
and str_to_date(?, '%Y-%m-%d %H:%i:%s')
and fo.IsDel = 0
and ui.UserState = 0
group by vid
order by vcounts desc
limit 0,10

这种排行榜查询,一定要做缓存。排行榜是新上线功能,可能忘记做缓存,通过增加缓存可有效解决。

给排行榜加缓存后,新版本立即上线。本以为问题解决,当天晚上系统仍一样现象,晚高峰各种请求超时,页面打不开。再分析慢SQL日志,排行榜慢SQL不见了,说明缓存生效。日志中其他慢SQL,查询次数和查询时长分布的都很均匀,也没看出明显问题SQL。

再看MySQL CPU利用率:

访问数据库超时问题排障_缓存_04

放大后规律:

  1. CPU利用率20min为周期规律波动
  2. 总体趋势与访问量正相关

猜测对MySQL CPU利用率“贡献”来自:

  • 红线以下,正常处理日常访问请求的部分,和访问量正相关
  • 红线以上,来自某20min为周期定时任务,和访问量关系不大

访问数据库超时问题排障_SQL_05

排查整个系统,没发现20min为周期定时任务,继续扩大排查范围,排查周期小于20min定时任务,最终定位问题。

App首页聚合非常多,像精选商品、标题图、排行榜、编辑推荐等。这些内容包含很多数据库查询。当初设计时,给首页做个整体缓存TTL=10min。但需求不断变化,首页要查询内容越来越多,导致查询首页全部内容越来越慢。

通过检查日志发现,刷新一次缓存时间竟要15min。缓存每隔10min整点刷一次,因为10min内刷不完,所以下次刷新就推迟到20min后,这就导致上面这个图中,红线以上每20min规律波形。由于缓存刷新慢,很多请求无法命中缓存,请求直接穿透缓存打到DB,这部分请求给上图红线以下部分,做很多“贡献”。

找到原因,做针对优化:

访问数据库超时问题排障_缓存_06

3 复盘

问题原因在开发犯错,编写SQL没考虑数据量和执行时间,缓存使用也不合理。最终导致在忙时,大量查询打到MySQL,MySQL繁忙无法提供服务。总结经验:

编写SQL要谨慎评估:

  • SQL涉及表,数据规模多少
  • SQL可能遍历数据量多少
  • 尽量避免写慢SQL

能不能利用缓存减少DB查询次数?使用缓存时,还要注意缓存命中率,要尽量避免请求命中不了缓存,穿透到DB。

优秀的系统架构,可一定程度减轻故障对系统的影响。针对这次事故,我给这个系统在架构层面,提了建议。

上线一个定时监控和杀掉慢SQL的脚本。这个脚本每分钟执行一次,检测上一分钟内,有没有执行时间超过一分钟(这个阈值可以根据实际情况调整)的慢SQL,如果发现,直接杀掉这会话。这有效避免一个慢SQL拖垮整个数据库。即使慢SQL,数据库也可以在至多1分钟内自动恢复,避免数据库长时间不可用。代价是,可能会有些功能,之前运行是正常的,这个脚本上线后,就会出现问题。但是,这个代价还是值得付出的,并且,可以反过来督促开发人员更加小心,避免写慢SQL。

做一个简单的静态页面首页作为降级方案,只要包含商品搜索栏、大的品类和其他顶级功能模块入口的链接。在Nginx做个策略,如果请求首页数据超时的时候,直接返回这个静态的首页作为替代。这样后续即使首页再出现任何的故障,也可以暂时降级,用静态首页替代。至少不会影响到用户使用其他功能。

这两个改进建议都是非常容易实施的,不需要对系统做很大的改造,也立竿见影。

当然,这个系统的存储架构还有很多可以改进的地方,比如说对数据做适当的隔离,改进缓存置换策略,做数据库主从分离,把非业务请求的数据库查询迁移到单独的从库上等等,只是这些改进都需要对系统做比较大的改动升级,需要从长计议,在系统后续的迭代过程中逐步地去实施。

4 总结

  1. 根据故障时段在系统忙时,推断出故障是跟支持用户访问的功能有关。
  2. 根据系统能在流量峰值过后自动恢复这一现象,排除后台服务被大量请求打死的可能性。
  3. 根据CPU利用率曲线的规律变化,推断出可能和定时任务有关。

在故障复盘阶段,除了对故障问题本身做有针对性的预防和改进以外,更重要的是,在系统架构层面进行改进,让整个系统更加健壮,不至于因为某一个小的失误,就导致全站无法访问。

我给系统提出的第一个自动杀慢SQL的建议,它的思想是:系统的关键部分要有自我保护机制,避免外部的错误影响到系统的关键部分。第二个首页降级的建议,它的思想是:当关键系统出现故障的时候,要有临时的降级方案,尽量减少故障带来的影响。

这些架构上的改进,虽然并不能避免故障,但是可以很大程度上减小故障的影响范围,减轻故障带来的损失,希望你能仔细体会,活学活用。

举报

相关推荐

0 条评论