千里之堤,溃于蚁穴

2020/06/02      8397 文(wén)章来源:优百 丨 作者:李磊


千里之堤,溃于蚁穴。这句话大意指一个工程巨大的堤坝,如果其中出现了被蚂蚁筑巢洞穴,当在特定的条件下,也会存在被摧毁的风险。这常常用(yòng)来形容生活中因小(xiǎo)失大,由小(xiǎo)问题逐渐酿成大风险的情况。借用(yòng)它来形容程序漏洞所造成的隐患或严重后果,也是非常贴切。

下面是筆(bǐ)者近期在排查一个服務(wù)器的相关问题时,所经历的真实事件和经验感受。

某天,项目收到来自客户方关于系统问题的消息,现象某系统服務(wù)无法进行访问,并且因系统属于企业的核心系统,问题需要紧急解决处理(lǐ)。当时头脑中的第一反应是诧异的,“因為(wèi)这个系统已经持续正常运行了一年多(duō)的实际,虽然出现过一些小(xiǎo)的问题,怎么会突然导致系统服務(wù)宕机?”。与客户方的现场人员进行了一番沟通与询问,诸如最近是否有(yǒu)人动过服務(wù)器?是否有(yǒu)人做过其他(tā)什么异常操作?但都告知一切正常,并无其他(tā)异常情况发生。

既然没有(yǒu)出现异常操作,还是先排查导致问题原因吧,于是经历下面的排查过程

此项目部署在tomcat容器下,项目地址无法打开,直接访问tomcat8081端口也没有(yǒu)响应,先查看端口情况:netstat -an |grep 8081

发现堆积了很(hěn)多(duō)连接,应该是很(hěn)多(duō)客户端连接过来,服務(wù)器无法处理(lǐ)请求,都在排队等待中。

再看看磁盘和内存情况:

l磁盘占用(yòng)情况:df -h

l内存占用(yòng)情况:free -h

发现内存和磁盘占用(yòng)空间都已经非常高了,判断有(yǒu)可(kě)能(néng)这里就是引起问题的原因。于是先清理(lǐ)服務(wù)器上过期的日志(zhì)文(wén)件、备份文(wén)件,将缓存释放。

l释放缓存

echo 1 > /proc/sys/vm/drop_caches

echo 2 > /proc/sys/vm/drop_caches

echo 3 > /proc/sys/vm/drop_caches

重启tomcat服務(wù)。不出所料,系统又(yòu)可(kě)以正常访问了,心里暗自窃喜……于是联系客户汇报问题已解决,并通知用(yòng)户继续使用(yòng)但是没想到的是,短短一个多(duō)小(xiǎo)时后,又(yòu)收到了来自客户那边的反馈,服務(wù)又(yòu)挂了!!!

啊?!!!这又(yòu)什么情况???脑子里顿时充满了无数问号!!!

看来缓存并不是问题的根源,只是其中的一个表象问题并没有(yǒu)想象的这么简单。还得从tomcat的配置入手看看,会不会tomcat的内存不足呢(ne)?于是进行查看,并给tomcat的配置文(wén)件增大了内存。

SET  JAVA_OPTS=-Xms512m -Xmx1024m

默认的内存设置是128MB可(kě)用(yòng)的最大内存设置1024MB先尝试修改為(wèi)初始化内存為(wèi)512MB

保存,重启,心想这下应该可(kě)以了吧… …

系统又(yòu)可(kě)以访问了,有(yǒu)了一次上面经历,觉得这次还是不能(néng)够大意,还得再观察观察… …于是每过十分(fēn)钟就对系统进行一次查看,确定系统是否正常,一个小(xiǎo)时过去了,没有(yǒu)出现什么问题,过了两个小(xiǎo)时,再检查也正常状态。

到了下午,虽然系统已经可(kě)以正常使用(yòng)了,但总感觉哪里不对!“简单的增加内存,真的能(néng)彻底解决问题吗?是不是又(yòu)是表象治标不治本呢(ne)系统好好的為(wèi)什么会突然瘫痪?明明刚清理(lǐ)的内存,為(wèi)什么内存会再次爆满?到底是什么原因导致的内存飙升?”这些问题都还没有(yǒu)找到答(dá)案,我有(yǒu)预感,系统还会再次出问题。果不其然,不久系统再次无法访问了!

看来问题必须深究,到底是哪里出了问题。外部的环境因素已经排除,问题就很(hěn)有(yǒu)可(kě)能(néng)出在程序内部,这只是感性的判断,但问题究竟是怎么发生的现在还一无所知。程序中的问题该怎么找?无法进行问题场景地再现是程序调试的最大障碍。既然无法进行情景再现,只能(néng)从日志(zhì)入手,慢慢地分(fēn)析并查找问题了。日志(zhì)文(wén)件有(yǒu)100M!!!查看起来还是很(hěn)费功夫的,使用(yòng)搜索查看报错信息,结果发现了面这个报错信息:

原来是他(tā)OutOfMemoryError”!终于找到问题根源了。找到了错误,问题就解决了一大半,接下来就只需要找到是什么原因导致的OutOfMemoryError。在报错的日志(zhì)部分(fēn)继续向上翻看日志(zhì),看能(néng)不能(néng)找到是哪个操作导致了错误的发生,发现了巨長(cháng)的查询结果集,根据结果集的信息,找到了隐藏在日志(zhì)缝隙中的那条sql语句:

SELECT DIS_ID, IU_ID id,IU_CODE, FULLNAME IU_FULLNAME, STATE,SYS_ID FROM CODE_NBDW_DIS WHERE STATE = '0' ORDER BY IU_CODE

这是一条查询待分(fēn)发数据的sql语句,仔细一看便能(néng)看出问题的端倪,这条sql语句并没有(yǒu)对结果集做限定,如果STATE = '0' 的数据有(yǒu)1000条,应该是没什么问题,但如果有(yǒu)100000+甚至更多(duō),结果会怎样,就会出现今天这种情况OutOfMemoryError

原来就是这么一条没有(yǒu)加限定条件的sql语句,就导致了整个系统的崩溃,真是“千里之堤,溃于蚁穴。”。在起初开发系统程序时,程序这么写可(kě)能(néng)并不会现问题,那时因為(wèi)数据量相对还比较小(xiǎo),问题不会暴露,但当数据量剧增时,带来的结果就是灾难性的。

所以,写代码,不只是算法和逻辑,一定还需要多(duō)一些思考,用(yòng)发展的思维去考虑问题,各种可(kě)能(néng)发生的情况,都要提前考虑到,保证程序的健壮性。

健壮性的思想

1正常运行的代码首要追求高效性

这个高效性如果从逻辑的角度来解释那么一方面是高效地对正确的数据执行正确的算法方法/策略),另一方面是高效地找出异常然后丢给异常处理(lǐ)代码去处理(lǐ)

2处理(lǐ)异常的代码首要追求健壮性

就是程序必须能(néng)从异常中自我恢复由于代码多(duō)数时间跑的是正常逻辑少数情况下才不得不处理(lǐ)异常”,所以异常处理(lǐ)的代码中首要任務(wù)是健壮跑不死而高效性则是次要的

如何提高健壮性

1、尽量少用(yòng)静态变量

2、尽量避免在类的构造函数里创建、初始化大量的对象

防止在调用(yòng)其自身类的构造器时造成不必要的内存资源浪费,尤其是大对象,JVM会突然需要大量内存,这时必然会触发GC优化系统内存环境;显示的声明数组空间,而且申请数量还极大。

3、对象池技术

尽量在合适的场景下使用(yòng)对象池技术以提高系统性能(néng),缩减开销但是要注意对象池的尺寸不宜过大,及时清除无效对象释放内存资源综合考虑应用(yòng)运行环境的内存资源限制,避免过高估计运行环境所提供内存资源的数量。

4、大集合对象拥有(yǒu)大数据量的业務(wù)对象的时候,可(kě)以考虑分(fēn)块进行处理(lǐ),然后解决一块释放一块的策略。

5、不要在经常调用(yòng)的方法中创建对象,尤其是忌讳在循环中创建对象。

可(kě)以适当的使用(yòng)hashtablevector创建一组对象容器,然后从容器中去取那些对象,而不用(yòng)每次new之后又(yòu)丢弃。

6、一般都是发生在开启大型文(wén)件或跟数据库一次拿(ná)了太多(duō)的数据造成Out Of Memory Error的状况,这时就大概要计算一下数据量的最大值是多(duō)少,并且设定所需最小(xiǎo)及最大的内存空间值。

7、尽量少用(yòng)finalize函数

因為(wèi)finalize()会加大GC的工作量,而GC相当于耗费系统的计算能(néng)力。

8、不要过滥使用(yòng)哈希表

有(yǒu)一定开发经验的开发人员经常会使用(yòng)hash表(hash表在JDK中的一个实现就是HashMap)来缓存一些数据,从而提高系统的运行速度。比如使用(yòng)HashMap缓存一些物(wù)料信息、人员信息等基础资料,这在提高系统速度的同时也加大了系统的内存占用(yòng),特别是当缓存的资料比较多(duō)的时候。

其实我们可(kě)以使用(yòng)操作系统中的缓存的概念来解决这个问题,也就是给被缓存的分(fēn)配一个一定大小(xiǎo)的缓存容器,按照一定的算法淘汰不需要继续缓存的对象,这样一方面会因為(wèi)进行了对象缓存而提高了系统的运行效率,同时由于缓存容器不是无限制扩大,从而也减少了系统的内存占用(yòng)。现在有(yǒu)很(hěn)多(duō)开源的缓存实现项目,比如ehcacheoscache等,这些项目都实现了FIFOMRU等常见的缓存算法。

以上就是筆(bǐ)者在工作中所经历的一次系统问题事件的整个过程,以及所引发的思考。对这次事件的一个全面的记录分(fēn)析和总结希望能(néng)给各位读者带来些有(yǒu)益的经验和借鉴。