事件背景
作者所在的公司核心业务是做政府信息化软件的,就是为政府部门开发信息化系统。其中有一款信息化软件是客户每天需要使用的,并且他们面向的客户就是老百姓。
某年某月,某地区信息化系统,周末升级系统以后,后面连续一周,持续出现系统不稳定、宕机、服务假死、数据库锁表等事件。甚至星期五下午,出现三个多小时无法恢复系统,造成恶劣影响。
系统整体运行架构
相对传统的一个负载均衡运行架构。系统建设时间比较早(2016年),没有分布式框架,就是传统的SpringMVC+MyBatis架构,并且服务器都是基于windows Server 2008的。
这套架构是出问题之前的架构,存在比较大的问题,在后续负载均衡章节会详细说明。事件过程
事件持续了一周左右,出现了好几类的严重问题。
事件造成的影响
9.21日上午,系统出现大面积服务假死,访问长时间没有响应。服务器网络延迟也非常严重。
分析过程
经查询,发现此次操作是一个名为更新“数据实例”功能,每次升级版本需要手动操作下此功能(情况比较复杂,无法做成自动处理)。所有的历史实例数据都会存在这个表,发生事故的时候,这张表大概有1亿3千多万的数据(Oracle比较能抗,数据量不算太大),更新实例这个功能又包含了表结构调整、索引重建、数据更新等功能(业务场景需要,原则上属于每次升级操作一次就可以),所以操作的时候会锁全表。
本来这个操作是要在系统升级完成以后操作的,但是升级的时候现场人员是新人不太熟悉情况,运行过程中客户发现数据有问题,经沟通后发现少了更新实例的操作,客户催的比较急,就直接在上线期间去操作了这个功能。因为这张表是核心表,所有业务都会走这张表的,就造成所有服务都卡死了。
后来是直接重启了数据库服务器(那时候DBA已经无法干掉进程,重启服务也失败,时间比较紧迫,经过沟通以后直接重启服务器), 之后系统恢复了,整个过程大约持续了30分钟。
解决措施
因为系统所在区域的业务量还算是比较庞大的,数据积累很快,所以很久以前的一些架构设计其实是存在问题的,后续针对类似这些数据表都做了相同的处理。
本来想改整体业务架构的,但是发现带来的代价太大,不仅仅涉及本身系统修改,还涉及一系列供应链上系统的修改,所以就放弃了这个方案。事件二之Tomcat内存溢出崩溃
9.21日下午,发现两个核心服务会不定时出现内存溢出,然后tomcat直接崩溃, 9.22日下午,发现另外一个服务不定期出现tomcat内存溢出问题,同样造成tomcat崩溃
做过软件的都知道,虽然异常表象是一样的,但是实际产生的原因可能是完全不一样的。分析过程
因为涉及到tomcat崩溃,在tomcat目录下产生了hs_err_pid.log日志,所以原因相对比较容易找,结合业务日志反查分析以后,就定位到了问题所在。
原因追溯死循环
9.21下午出现的两个核心服务不定时出现内存溢出,查询日志发现出问题时间段内会出现大量的服务访问日志,经过追溯代码以后,发现前端的判断逻辑存在问题,会造成服务死循环访问,即A-->B,A-->B,.....
历史原因,部分业务逻辑代码在前端进行处理,本来服务A访问成功以后,会继续访问B服务,然后B服务访问成功以后就结束了。但是这里的代码在特定条件下逻辑判断不严谨,会触发刷新服务,会一直重复访问A,B服务。模糊查询
9.22下午出现的另外一个服务内存溢出,经过同样的分析以后,发现是一个模糊查询的请求出现异常了,正常看代码没什么问题,但是分析日志以后发现,都是一些模糊条件比较简单的请求,初步怀疑是数据没做过滤,当时数据全部加载到内存里了。
因为分析的时候是上线期间,防止系统出现问题,没有直接抓出原请求去复现问题。后面是让DBA,基于查询的SQL语句,去数据库日志里查询当时的执行记录,发现这几个请求平均返回的记录数都在200万以上。至此基本上知道造成内存溢出的原因,程序里不仅仅是加载数据,还有进行数据处理的计算。
解决措施
后面问过当事人,为什么要这么查询,一问发现还是我们自己的问题(设计不合理)。这个版本上线增加了回车查询的功能,然后客户在一个词输完以后,习惯性地会敲回车,然后触发了查询,就造成了前面的事故。比如要查询XXXX省的某个坐落门牌号,然后在输入XXXX省以后输入按了回车,就变成把全部的XXXX省数据都查询出来了。
事后公司内部小组内也进行了案例分析,其实很多开发人员对这种问题不以为然,认为不是太大的问题,就改两句代码就行。但是实际造成的后果可以对标互联网的P0级事故,这个带来的可不是人力成本上的损失,还有很多看不见的诸如口碑,信任等损失。负载均衡问题
21号-24号中间出现几次问题,其实大部分时候都是某一台服务器上的服务出现问题,但是发现负载均衡没有发挥任何作用,服务都挂掉了,请求也一直过来。
刚好周五下午也出现了更重大的事故,所以在后面就把整体策略调整了。负载均衡设备的问题也算是为我们周五的大事故转移了一些视线吧,压力相对减轻了一些
原先的运行架构图
存在的问题
厂商其实是个小代理商,设备也比较low,前期的时候也比较拽,上面的几个问题据说前期都是沟通过的,但是人不配合。做过政府信息化软件的都清楚,内部会涉及多方博弈,有时候很多问题不是技术能解决的。
这次事故也算是一件好事,至少让甲方意识到设备是有问题的,叫上了设备供应商对负载均衡各项内容进行了调整;对我们来说,甲方的炮火也适当转移了一部分,我们的压力也减轻了。解决措施
服务和端口监测场景
服务访问场景
所有内部服务访问都通过负载去转发,做到每个服务都高可用。
需要承担的风险:
1、高度依赖负载均衡设备的稳定性,以前请求较少,现在请求都会经过。(后面出现过一次负载扛不住压力,系统全面崩盘的情况)。
2、会话保持需要依赖于软件架构的设计,如果会话保持无法做到,此套架构无法使用
当时负载均衡策略根据上述的方式做了调整,效现场果还是不行。
当时设置完成以后,厂商才告诉我们,老的设备监测有问题了,要10分钟才会切换(好像是老设备的问题),需要更换新的设备才行。 10分钟对业务来说太长了,这种效果根本没用。
回到当时内部的多方博弈,业务部门要求换,信息部门要求我们软件供应商写一个保证,更换设备以后,系统100%不出问题。这种说法就是耍流氓了,我敢说没有一家软件公司敢说自己产品100%没有Bug,再说硬件负载切换更换,为什么要软件公司来保障。后来这个事情就不了了之了。
网上经常说的那句话,“往往最朴素的方法就是最有效的方法”,甲方后来也知道他们硬件设备不行,采用了重启大法,他们会定期(差不多一个月)的样子,重启下所有设备,后面确实没发生什么大问题了。
后来我们在公司内部完全模拟了现场的情况,采用了国内知名硬件厂商的负载均衡设备(不打品牌了,有打广告的嫌疑), 发现效果非常理想,可以达到预期的切换的效果。说明说做的策略没有任何问题。系统宕机,无法恢复
9.25下午开始,那就是灾难性的了,也是那天,所有领导齐聚现场,安抚客户。 这天出现了将近三个小时无法恢复系统,业务直接停摆。
黑色的一个星期五,我也是在这天到现场的,前面都是远程指挥的(开发负责人在现场)
整个事件莫名其妙来,莫名其妙走,从星期五晚上开始(通宵了两晚),一直到星期六半夜里吧,经过多人的头脑风暴以后,才基本确定了问题的所在。
真的找到问题以后发现过程很可笑,但是代价太惨痛
前面我们是一直在怀疑是中午12:00左右更新的代码引起的问题,因为从各种现象来说,就是更新了以后出现的问题,但是经过一个多小时的反复验证和讨论,直接排除这方面的原因,那时候就直接开玩笑说了,敢用脑袋担保新加的代码是没问题的。
后面开始翻看各种本地日志,那时候日志文件很大,好几百兆(其实这个文件大小就是有问题了,只是所有人都没意识到,为了排查方便,是每天备份清理日志的,正常业务不应该产生这么大的日志,况且这天下午基本没有业务办理,只是那时候我刚到,不了解情况,也没多想),日志非常大,加载又慢,看了一大堆无用的运行日志,没发现任何有价值的信息
快到天亮的时候,翻看了很多代码和日志,也是毫无头绪。真的是毫无头绪,连疑似的原因都找不到,本来说让性能测试组过来,看能不能复现,但是源头都没有,性能测试总不可能一个个场景压过去去找问题吧。
白天休息了一个多小时吧,起来继续工作了。
因为我属于被拉过去的,正儿八经排查代码是有其他开发负责人在的,前面我大部分时间就是跟他们头脑风暴,然后翻翻日志文件什么的。那时候潜意识也是认为应该跟代码没什么关系,还是跟环境有关系。所以第二天一早,我开始查看服务器上的部署文件,算是漫无目的吧,因为根本没头绪。
细节决定成败, 我在翻看配置文件的时候,发现同级目录下的log4j.properties文件的修改时间不正常,是25号下午17:03分,这个时间差不多就是我们最后一次重启的时间,那时候本能的反应就是这个和这次事故有关系。打开文件看了下内容,看起来很正常,日志级别也是ERROR的(我们要求生产环境不允许开启debug日志的),但是这个修改时间表明当时这个文件被覆盖过或者修改过。那时候真的,一下子来了精神,看到曙光了。
我叫上相关的所有人,开始反推当时从12:00更新开始的全过程,也把当时更新服务器的开发人员叫上,努力回想当时干了什么事情。经过将近两个多小时左右的回溯和头脑风暴,终于把事情理清楚了。
到此为止,基本上已经分析清楚了造成问题的原因,也一步步复现了当时系统所有的异常现象。
为什么控制台刷日志会造成CPU异常
但是最关键的问题来了,为什么控制台刷日志会造成CPU异常,这个也是阻碍我们排查问题的最大原因,我们也在自己笔记本上做了大量模拟,包括通过JMeter做压测,都没发现控制台刷日志会造成CPU异常。
其实解决过程很简单,关闭debug日志,系统基本就是正常了,但是本着刨根究底的态度,这个问题卡在这里很难受,这类问题也就独此一家吧。
因为那天甲方人也在,硬件公司也来了,聊天的过程中终于发现了问题所在。
你看的没错,运行了系统这么久的服务器居然是两台VM虚拟机。
用过虚拟机的都知道,虚拟机是公用主机的内存和CPU,是通过动态计算分配出来的,经过和专业人士交流以后,虚拟机内部很多操作都是通过软件模拟出来的,比如网络。这也解释了9.21号服务器CPU异常以后,造成网络大面积延迟。
大概的过程基本就是CMD封装刷日志-->CPU异常-->进而挂起了其他所有操作(包括网络等),等待CMD日志刷完,释放CPU以后,又恢复正常了。
因为对操作系统原理其实不是特别了解,所以也一直分析不到到底是什么情况,到时cmd刷日志占用了所有的CPU,也不给我们权限去查看VM服务端相关的日志,这个属于硬件公司管辖范围
硬件公司死活不承认VM虚拟化做服务器是有问题的,但是从我的角度看,单单就是CPU飙高,网络会出现大面积延迟就是有问题的。可能硬件公司知道猫腻吧,所以也不给我们看服务端日志,他们一直强调服务器是没问题的。
前面也讲到,回公司以后,我们搭建过一套一模一样的环境(实体机),就是怎么也复现不了控制台疯狂刷日志,造成CPU异常,系统假死的情况。总结
人的习惯就是这样,第一印象把问题都会甩给“异常输出的平台”,所以这个事件基本上由我们软件平台全部背锅了,我们事后还写了很大一份“事故总结报告”,各种“保证书”等等。
事件总结
这是一次典型事件,碰到了我职业生涯没碰到过的异常,又积累了一笔经验。
大家喜欢可以关注我,每天只分享Java干货! 链接:https://juejin.cn/post/7152449257700589576