20141031故障处理:XFire之HttpClient瓶颈
经历了无数次漏洞修复、N+次业务版本升级;由当初预计支持十多万月活跃用户的ssh架构到如今支持月活跃300万用户乃至向月活跃1000万进军的自主研发架构;从当初5人的开发小团队发展到如今还是5人的开发团队;当时只有两台刀片机发展到如今10台PC+小机的的系统。它陪我走过了IT职业生涯最难忘的青春;它见证了我的成长;它是职业生涯最难忘的“导师”。
确实,缘分让我毕业之际就与它相遇。由一个完全不知道干什么的需求发展到现在终于有一点点了解它要干什么的系统,把我从懵懂的土壤中生根发芽,让我找到了职业的方向。一路走来,确实不易。刚从培训机构出来还是新手的我就接手了这个从零开始的项目,还差点被连续两晚三天不眠不休的工作强度吓跑,幸好我坚持了下来,我知道,它会让我成长。
几年下来,随着系统推广力度的增强和用户量的急剧上升,系统遇到的瓶颈数不胜数,每个瓶颈都是我们成长的机会,我会异常兴奋,我相信我会把它“治”好,信心来源于我们之间的信任。我很感谢团队中几年下来不离不弃的战友们,因为大家的团结与融洽,才让我们不断前进和进步。
暴风雨前夕,显得异常的平静。果然,一个轻松愉悦的国庆假期过后,工作量急剧上升,一个5人的开发+运维+实施+数据分析与统计的小团队,几乎都在日均13小时的工作强度中度过,而且脑袋都处于100%的工作状态,没有休息的余地。我知道,大家都已经累到极点了。但是,由于责任心和相互理解,大家都毫无怨言,偶尔只是习惯性的吐槽几句。我知道,这是大家缓解压力的手段之一:“爆粗”。但,雪上加霜,通常会在这种片段出现。随着运营部门排山倒海的推广力度,系统再次陷入瓶颈,线程急剧上升,收到了很多的投诉和指责。由于商务上的复杂,得到的支持力度有限,永远只停留在舌战上,浪费人力物力财力,最浪费的还是时间。我们永远坚信,最可靠的只有我们自己。
在没有加班费和几乎无法调休的状态下,战友们都自愿加班加点兼顾项目进度和问题的解决,我真的很庆幸可以遇到他们。因为我们都知道,虽然辛苦,但这是难得的机会,这是我们成长的又一台阶,因为接下来将要遇到什么样的瓶颈或问题都无法预估。越是遇到没有遇到过的,我们就越兴奋,我们就是那么“犯贱”。
从发现问题到解决问题,我们只用了两天的时候,相比之前,无论效率还是功力,已经有很大的进步了。想起6月份的那次synchronized引起的瓶颈故障,我们花费了7天7夜才得已解决。这次瓶颈的起源在于webservice的XFire架构,接下来记录一下我们处理瓶颈的过程,以便日后慢慢回味和帮助有类似问题的人。
由于10月份的开发量确实很大,而且很多需求都涉及到与第三方的接口对接,这是最耗时间和口水的部分,不过这也造就了我们沟通能力和技巧提升的有效途径。由于大家都参与到开发中来,且人力资源的限制,所以在运维方面的工作量大大减少了,连系统监控维护的周期都拉长了。当我们都拼命在代码中挣扎的时候,系统的线程持续上升,我们还以为只是运营活动的一时推广造成的,没有太在意。几天过后,开始经常受到系统报警和领导的紧急邮件,说很多人反应系统失败率上升并且投诉越来越多。此时的我才停下手头上的工作,对系统进行了一轮监控。确实发现线程比以往都高,但整个系统还算可控。在对照了一下访问量和用户数,确实有持续上升的势头。我了解了一下相关运营部门在推广方面的情况,近期确实推广比较厉害。看来只好先放缓一下开发进度,跟紧一下系统的性能的监控。忽然某一天,系统线程完爆了线程池的最高值,很多请求都被拒绝了,我多次尝试重启服务器和调高线程池,但效果不佳,线程池很快就满了。我监控了一下80端口,发现每台服务器都平均达到了1000的有效连接。经过了上次瓶颈的经验,我立刻预计系统在某个地方遇到了瓶颈。于是开始“全局扫描”,一个个排查系统有可能遇到瓶颈的地方。
数据库是我开始排除的第一个地方,因为之前留下来的“案底”太多了,但是,数据库经过上一次的精心调优,性能提高了不少,监控数据没问题。然后,继续寻找下一个“罪犯”:与第三方平台对接的接口性能。因为系统涉及与第三方平台对接的有几个,所以还要一个个排查。首先进行了网络测试都没问题,再次查看和统计日志。因为现在整个系统的性能都有问题,所以日志反映出来的问题并不十分准确。在某一深夜,我关闭了负载总入口发现,服务的线程会慢慢下降,证明线程还没像上次那样挂死,应该是资源的竞争导致线程等待。我通过对javacore文件进行分析,果然让我发现问题:
上千个线程在等待资源,有12个线程挂死了,如果不解决这个资源竞争的问题,以后会有更多的线程会超时挂死导致系统崩溃。接下来我在对每个线程的运行情况进行分析,罪魁祸首终于出现了:
从监控线程可以看出,大量的线程都在等待httpconnection,这是一个访问量较大的第三方平台业务,我们系统的webservice客户端使用了XFire,从信息可以看出,XFire使用了Apache的一个httpclient工具包,而这个httpclient使用了MultiThreadedHttpConnectionManager进行管理。通过查询,发现MultiThreadedHttpConnectionManager的maxhostpool的默认链接数是2,而我们的XFire实例是单例的,那就意味着这个单例只有2个connection为那么多用户服务,开什么玩笑。因为这套业务webservice接口太庞大了,以前不用单例的时候导致过内存溢出而系统崩溃,所以我对这个业务平台的设计和webservice协议非常反感。
此刻的我心想,一个开源架构不可能那么不靠谱吧,堂堂一个线程池没可能就不能改了吧。因为是XFire代码内部调用了httpclient,所以修改线程只能从XFire的代码入手,看能不能找出解决方法。经过了半天的XFire代码的的反编译追踪,终于让我看到点希望:
从上图可以看出,XFire默认是设置了最大hostpool的连接数为6,而全局最大连接数为20。不过关键在于getIntValue(),进去这个方法一看,原来XFire的service会有一个参数Map,里面是可以设置这两个参数的,那就意味着我可以改变MultiThreadedHttpConnectionManager的http连接池的连接数。凭着这个希望,我继续追中,终于让我在XFire实例的service里面看到了这个方法:
这个HashMap就是整个XFire上下文的载体。我通过把改变http连接池的那两个参数加在这个HaspMap中,提高连接池连接数:
重启系统,线程回归稳定状态。经过两天的监控,线程还是一样稳定,瓶颈段告一段落。
下图为XFire的流程图,可以很直观地了解到XFire的工作原理,对我这次查找解决方法还是有很有帮助的。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。