故障描述html
做为一个老牌OTA公司,公司早些年订单主要来源是PC网站和呼叫中心。我在入职公司大约半年后,遇到一次很是诡异的故障。有一天早上,大概也是这个季节,阳光明媚,程序猿刚起床,洗洗涮涮,准备去迎接初恋般的工做日,却忽然收到一大堆报警,线上消息队列大量积压;固然,我仍是一如既往的很是勤奋地在9点以前就到公司的;可是做为一名新员工,环视四周,组内其余员工都还没到公司,运维也都在路上,故障就这样忽然降临了。我赶忙开机登陆堡垒机,链接线上机器,tail 错误日志。可是线上10几个系统,我看了好几个系统,都没有发现有什么错误,这就尴尬了。可是统计消息队列,超过好几千的消息待消费。我当时就在想,这些消息都是什么鬼。截图以下:mysql
图一spring
看到这里,你必定会问数量为604和881个的消息是作什么?知道这些消息的逻辑不就解决问题了么?话说当时我也是这么想的,但是当时我做为一名新人,才开始接触业务不到3个月,还彻底没有这么深的业务积累(这个时候知道业务是多么重要)。sql
既然系统看不到任何错误,我也没有什么办法了,当时由于刚入职没多久,还有点寄但愿于领导来解决。转眼间半个小时已通过去,故障仍然没有恢复,从业务反馈来看,微信支付宝等支付方式不受影响。受影响的只是信用卡支付(其实当时信用卡量占比挺高)和分销支付(后来了解到,其实这两种模式都是信用卡支付模式)。领导还在堵车,运维也只是到了几个小兵,我找运维把几个机器的stack打印了一下,也没有发现什么问题;运维也陆续到岗,运维准备出大招,重启系统。可是就在此时,忽然系统自动恢复了。全部积压的消息自动被消费,信用卡支付也能够了。好,系统居然有自我修复功能,佩服;数据库
故障缘由分析编程
后来,通过一番努力,仍是找到一点蛛丝马迹,我发现系统的一个消费消息的定时任务,在故障期间一直在报错,由于是高可用的job机制,4台机器,只有抢占到锁的服务器才能获取到访问数据库消息权利,因此报错信息比较分散,4台机器都有。服务器
图二微信
能够断定,这个sql一直异常致使job根本没法获取到消息,而另外的生产者又不断的往队列放消息,进而致使消息积压。两个系统关系以下:网络
图三框架
虽然故障总结了,可是咱们内心也不踏实,如何找到系统故障的根本缘由,以防止之后再次出现这种故障呢?
方法有两种:
一、去查代码,全部跟这个表相关的sql,都须要仔细review一下,可是你也不必定能查到缘由,由于这个场景确定是很差复现的,要否则早就发现这个问题了。
二、借助外力,从DB层面查致使这个sql没法执行成功的缘由;
方法1看似简单,其实很是不可行。首先,虽然跟这个表相关的sql,只有几十个,可是都是正常的sql,没有使用for update锁死表的sql。也没有存在未关闭的事务,由于事务是经过AOP配置的;
因此只能寄但愿于方法2了,让DBA去查;
好歹咱们的DBA足够给力,只用了1天多的时间就查出来了。
DBA回复以下:
一、有事务没有及时提交,且链接也没有关闭,致使该事务一直处于开启状态并持有锁,后续update操做是全表扫描,所以会有锁等待。
二、最后该链接后续一直没有操做,达到空闲超时3600秒(咱们的故障时间正好也是1小时)后被mysql server断开,锁才被释放。(mysql设置:wait_timeout = 3600)
最牛B的是DBA贴出了没有提交事务的SQL;sql我就不贴出来了,咱们根据DBA提供的线索,找到了代码的问题;
故障根本缘由
后来咱们查看代码,如上面DBA所说,消息没有被消费处理,是由于有一个mysql客户端,即咱们的支付应用程序,在进行快捷支付的时候,向队列插入一条记录,而后在事务中向第三方发起了调用。使用的是httpclient工具发起的调用,可是设置超时时,只设置了链接超时时间(connectionTimeout)为30秒,没有设置响应超时时间(soTimeout),这样当出现网络问题时,程序就会一直等第三方响应,而后事务也一直没有提交。而在job程序中,须要将这个queue的全部记录给更新,可是又取不到表锁(见图三),就不断的报lock wait timeout的错误;其实对使用spring AOP框架的研发,很容易犯这种错误。咱们从 https://tech.meituan.com/2018/04/19/trade-high-availability-in-action.html 这篇总结里面的1.5段也能看出,美团支付也在这块也栽过坑;
图四
到这里,其实故障缘由已经很清楚了,咱们在代码层面也确实查到了问题。由于DBA提供的sql中,连insert sql的主机名也列了出来,而且现场没有被破坏,咱们使用jstack应该还能找到正在等待的线程才对;因而在时隔故障2天后,咱们又让运维把那台机器的jvm stack给打印了一下,果真发现等待的线程仍然存在。
堆栈以下:
图五
与之对应的代码,我就不贴了;
解决方法
一、临时解决方法,将响应超时时间设置上,但这没法根除问题,只是下降再次出现问题的几率;
二、长久解决方案,修改框架,使用编程式事务,将全部远程调用从事务中剥离出来。
知识点
一、事务,spring AOP
二、httpclient,超时设置
求关注