最近又遇到了一次慢查把 db(mariadb10) 几乎打挂的案例, 作为一个核心支付系统的技术负责人, 真是每日如履薄冰. 因为之前支付系统经常出问题, 现在各个 BG 对支付系统都盯得很紧. 这次要不是我及时让 DB 给暴力清理数据, 没准又提一个 P2 故障;
抱怨归抱怨, 事后复盘, 一丝都不能马虎. 首先, 描述一下故障的全过程. 起因是我们支付系统有一个异步队列, 这个队列使用的一张 MySQL 表存储, 异步回调业务线的任务 (姑且表名称叫 task), 都会首先放这里. 同时这个 task 表还有其他的异步任务, 不同的任务使用 task_type 字段来进行区分. 然后应用系统有一个定时任务, 扫描这张表是否有待消费的任务, 如果有, 则会取出来进行消费; 典型的生产者消费者模型;
这里的 task 说的再具体一点:
1, 所有的异步任务都在这张表, 有支付成功通知业务线消息, 有给结算系统推送支付信息的任务;
2, 消费者在任务处理成功后, 则会把任务从 task 表删除. 所以这张表经常是空的;
消费者根据不同的任务, 调用不同的上游订单系统和结算系统. 出故障时, 是因为推送支付信息的结算系统接口超时, 出了问题, 导致任务被积压到了 task 表.
任务积压之后, 消费者线程池很快就被积压的任务占满, 导致应该通知 BG 订单支付成功的任务也被 block 住, 进而影响到订单支付成功率.
当时我已经意识到, 我们的消费者线程池隔离没有做到位, 立刻找 DBA 将推送给结算系统的任务进行了备份并清理. 并且嘱咐 DBA 定时清理推送结算任务的数据. 这样才化解支付成功率继续下滑的趋势.
危机解除后, 我们和 DBA 配合进一步排查问题, 找出了事情的根本原因.
原来是推送结算信息的逻辑中, 有一个对 task 表的查询, 而这个查询的 sql, 没有建索引. 这样当这类任务数量积压的比较多时, 查询会越来越慢, 慢查导致 MySQL 堵塞. 堵塞导致消费者无法拉取任务, 进而影响到其他通知 BG 的任务的消费; 我们分析了一下日志, 其实我们的程序查询数量当时 3 分钟大概查询了 1 万多次, 可以说 qps 不多. 但是问题出现在 sql 无法命中索引, 把 MySQL 的 worker thread 都用完了. 给我们研发的感觉, MySQL 是如此的脆弱, 2w 多条数据, 查询没有索引, 几千个 select, 就能把它打挂.
几乎类似的案例, 一年前, 我们也碰到过一次. 当时支付系统有一个 bug, 用户每支付一次, 都会把支付客史中一个月之前的数据都清理一下 (1 月 1 日, 清 12 月 1 日之前的数据, 2 月 1 清理 1 月 1 日之前的数据). 这个 bug 藏的很深, 这块代码也很少有业务需求, 一直没有被发现. 但是, 是雷就会有爆炸的一天. 3 月 1 日凌晨, 支付系统突然所有接口都挂了. DBA 最终定位是删除支付客史的 sql. 这个问题, 我们研发一开始是不承认的, 毕竟这个 sql, 在线上跑了 2 年多, 一直没有出过问题. DBA 说这个 delete 语句删 3000w 数据, 而且在不断的请求, 数据库当然扛不住, 我们反驳说, 这个客史表一共才 3000w 数据. 事后我们发现, DBA 的描述有误, 不是说删除 3000w, 而是这个 delete 语句没有走索引, 每次要扫描 3000w 数据. 这样才能解释通, 也就是说, 这个 delete 进行了全表扫描. 而实际上, 这个 delete 是按照时间删除的, 并且时间字段是有单列索引的, 但是为什么这个 delete 没有走索引呢? 我们最后猜测, 可能是因为 2 月份天数太少导致. 以前, 可能数据比较少, 每次删一天, 或者 2 天的数据, MySQL 可能会走索引. 但是 3 月 1 日比较特殊, 因 2 月 28 日删的是 1 月 28 日一天的数据, 3 月 1 日却要删除 1 月 29,30,31 三天的数据, MySQL 可能认为删除这么多数据, 没有必要走索引了.
遇到类似问题如何解决?
1, 读写分离.
2, 提前消灭慢查询;
3, 对异步任务做好线程隔离;
关于 MySQL 的线程池, 我最近也了解了一下, 收获也不小, 给大家推荐一篇好文章;
https://www.jianshu.com/p/88e606eca2a5
关注我的公众号 "猿界汪汪队", 关注大并发架构实战.
来源: https://www.cnblogs.com/donlianli/p/11068095.html