今天记录一下前段时间生产环境产生的一个严重问题,希望如果大家可以避免此类问题产生,如果遇到类似的问题可以更快止损
目录
首先通过结构化拆分讲述分为以下3点:
1.什么问题导致出现了这个情况
一个内存服务发布重启过程中,在启动加载数据的时候出现了慢sql导致服务启动超时不断重启
select * from table where a = {a} and b = {b} and id > 0 order by id limit 1000
2.这个情况带来了哪些影响
a服务启动期间一直在加载数据库数据,导致mysql cpu急速上升,其他服务由于调用a服务接口失败降级查询mysql 使 mysql cpu上涨直至打满后 所有服务线程池被打满,最终导致全栈服务不可用。
影响时长:36分钟
总处理耗时:90分钟
资损:大约 5w+美金
客诉数:1200+
3.后续如何规避
(1)避免产生慢sql
(2)服务必要数据不应该在服务启动(主从切换)时加载
对细节不感兴趣的同学看到这里就可以撤了
详细描述:
1.问题触发
(1)背景:
本次有需求发布,并且发布时间在晚上11点多。正常情况是不应该在这个时间点发布的,但是他们发布服务较多,并且觉得自己的改动风险可控所以临时申请了晚上发布。
但是开发、测试、运维、产品、项管都没有考虑当前发布时间点为用户访问量相对较高,如果出现问题则可能影响较大。
(2)需求发布:
其他服务发布后都没有问题,在发布此服务时发现服务正常启动后发生重启,等待第二次重启后仍然启动失败再一次触发重启,并且有相应的告警信息触发:
【1】mysql cpu告警
【2】http请求超时告警
【3】消息消费积压告警
【4】tomcat线程池打满告警
2.应急处理
(1)运维先回滚服务,并在应急群中拉起会议 并拉相关部门负责人分析情况并协助处理
(2)发现回滚服务无用,开发快速根据当前服务和监控情况和告警信息分析原因并作出判断是否可以通过扩容mysql节点应急
(3)运维、DBA、开发 初步分析是在服务启动初始化时数据加载过程mysql查询过慢导致服务启动失败,联系DBA负责人先开启扩容mysql新接点承接流量
(4)扩容mysql新节点后发现cpu又很快被打满,先下线不断重启服务,并在扩容两个新接点承接流量,此时流量基本可以承接
3.服务关系介绍 及 问题排查
(1)服务介绍:
本次发布的服务a有两个节点
服务a从节点
只做为备用节点不提供服务和其他功能,保证在主节点发布期间可以快速替换,降低服务发布影响
服务a主节点
【1】启动加载数据库数据
【2】消费mq消息并在内存中维护数据一致性并持久化到mysql
【3】定时任务 根据消费的mq处理到达条件的数据并发送mq或http请求让其他服务进行处理
服务发布顺序为先发布从节点,然后在发布主节点,主节点发布时会触发主从切换导致从节点变为主节点,然后触发数据加载逻辑,加载完成后开始消费积压的mq消息并提供数据查询服务
(2)问题排查:
【1】由于问题产生期间做了服务发布,先根据告警信息排查sql产生位置查看代码,发现不是新增sql
【2】排查sql使用场景,发现此sql只在服务初始化内存数据时使用
【3】分析sql,发现此sql 走了主键索引导致的影响,并且走主键索引与预期不符
【4】尝试在sql中增加忽略主键索引,explan索引与预期一致,并且在生产环境读库执行响应速度很快,确认紧急修复改动方案
4.紧急修复
复制原有sql新增一个dao方法,防止原有sql有其他调用位置被改动后产生其他问题
select * from table ignore index(primary)
where a = {a} and b = {b} and id > 0
order by id limit 1000
修改代码后提交并发布服务,服务启动成功
启动成功后发现积压消息量还没有降低等待消息消费,消息积压处理完成验证所有服务都没问题,紧急处理完成。
5.原因分析
(1)除非必要或问题修复,不应该在流量大的时候进行服务发布
(2)此sql上线1年多都没有进行修改并且查询条件没有修改的情况下为什么会突然产生慢sql
【1】是否由于mysql版本升级导致:
mysql最后一次升级时间为1个月前,在问题发生和升级时间内此服务还发布过两次均没有产生问题
【2】是否由于pod节点或物理机升级导致:
最近一次升级为半个月前,并且升级时就重启过服务,没有产生此类问题
【3】是否为数据量增长导致:
dba同学分析sql说,此类sql本身就有一定的可能会走到主键索引产生此类问题,跟数据量没关系。
但是我觉得可能就是由于数据量增长和索引覆盖不全导致的,并提出建议让相关的同学去测试环境造相同数据模型并测试(后来好像他没做,不过跟我没啥关系,提出来他不听就不听呗)
但是我还是想让大家参考一下这个问题引发的mysql原理相关内容:
分页加载优化,不过我们项目中不适合用子查询因为这个表是个分表,我们使用了sharding jdbc 在sql中不支持分表查询会导致表名注入失败,用${}的话可能会有安全风险,所以也不能这么改
有兴趣的话大家可以看下往期内容,此类sql的大概优化方案参考
MySQL优化三:查询性能优化之基础部分_慢日志扫描行数仅为1-优快云博客
MySQL优化三:查询性能优化之优化特定类型的查询-优快云博客
我觉得根因在于:当表数据量达到一定量级时,此sql的where条件和索引匹配度会主键降低到达某一个阈值的时候,mysql的优化器觉得这个sql命中的几个索引中,主键索引的效率最高所以才触发了使用主键索引,并触发了全表扫描
MySQL优化三:查询性能优化之SQL查询执行过程_单词传输排序-优快云博客
可惜,由于这个不是我负责的,所以我也没办法调用公司资源真正的去验证一下我的这个想法
【4】为什么一个服务的启动数据加载最终产生了这么大的影响
① 首先服务初始化多线程加载数据期间,由于数据量过多会导致mysql cpu上升
② 其他服务在此期间调用此服务接口超时或访问不通的情况下,会走fallback直接读取数据库数据降低业务影响,这更进一步增加了mysql cpu的负担,如果一切正常的话主从替换逻辑几秒内就可以恢复正常
③ 由于a服务的主节点启动失败导致大量的请求打到mysql查询数据,mysql cpu及连接池都被打满,其他服务请求由于都被阻塞在mysql查询从而自己服务的tomcat线程池被打满
④ 为什么其他服务查询数据库会造成这么大的影响呢:因为开始时候说了,这个表是个分表,并且是可以跟着业务不断扩张而增长的表,所以导致一个用户请求进来会查询几十个甚至几百个表数据,从而造成了mysql cpu剧烈增长的情况
⑤ 就算再慢一个sql慢查询也不应该造成这样的后果,为什么一直没有加载进来呢:
这就要说到一个普遍的问题了,在性能优化过程中,我们的各种监控和控制会越来越全面,并且由松到紧的一个过程,这个过程是不可避免的,如果这一整套系统没有任何控制的话,可能服务几次重启就起来了,毕竟mysql还有查询缓存,第一次不能命中,第二次也就命中了。
具体原因是dba监控发现这个sql 30s (1年前是60s,这个sql执行需要58s好像,记不清了 要不然就是68s)还没有响应判定为慢sql就执行了kill命令结束了线程执行,导致没有一次查询可以执行完成,也就永远命中不了查询缓存了。
这个问题也不能怨他们,只能说这是一个必然的控制,让数据库及时止损
6.优化方案
(1)修改sql:
加载数据sql 忽略主键索引,并且排查所有服务sql是否有类似也可能走主键索引的sql,提前防治。
(2)提高数据库扩容速度:
调整自动扩容规则,并且触发规则后直接扩容当前节点数*2的容灾高配节点,降低业务影响和资损
(3)从节点从冷备改为热备:
从冷备改为热备,进一步降低服务启动可能产生的影响,从节点也可以提供查询支持降低服务请求压力;并且可以进一步提高消息消费速度,防止消费积压。
(4)降低db压力:
修改分表方案,防止由于业务扩张,数据表持续增长,再次发生此类问题
(5)限制发布时间:
此服务以后如果没有特殊要求,要在流量最低时发布(周末加班发/(ㄒoㄒ)/~~)
(其实当天刚好流量比平时都大一些,mysql cpu本来就高了一点进一步诱发了这个问题的产生,以前没问题跟发布时候都避开了行情可能也有一点关系,谁家好人非不在规定时间发,加班也要发版啊,真是的~ )