以前我以为,线上系统的问题,只需要好好检查代码即可找出原因,可是工作后发现,现实并非如此,往往线上系统的问题来源于信息不对称。这种信息不对称体现在团队成员之间没有好好沟通,了解彼此对系统的改动,以及跨部门、跨系统、跨平台之间的信息不对称。
其实,当线上问题来临时,心态很重要。新人往往在系统出故障时(尤其是故障是自己负责的部分)感到很慌张,我一开始也是如何,但后来渐渐看开了。程序员进行代码开发总是会出现bug的,慌张对于解决系统故障并无好处,反而容易因为慌张而导致忽略了必要的流程,从而导致修复故障过程出错。除此之外,我在系统出现故障时,会思考这个故障对于系统业务的影响有多大(比如:对于一个订单处理系统,其排序错乱不符合预期,这种对业务影响不大,但是如果订单丢失,对业务影响是巨大的),故障对于数据造成的影响有多大(如果数据可以修复,那么影响不大,如果数据不可修复,那影响很大)。
当系统出现问题时,要冷静下来,用逻辑分析问题,绝对不能靠猜。往往可以借助排除法来分析问题所在。
比如:对于接口执行速度慢这个问题,第一步:先列出所有可能引起这个问题的因素,可能是数据库、外部系统接口调用、线程池、网络、内存、cpu、死锁、gc、接口代码本身效率低下等因素引起。第二步:将这些因素一个一个通过监控、调试、测试等手段证明或证伪其对问题的影响。如果最终排除了所有选项依然没有找到问题,这说明要么还有因素没有找到(往往是信息不对称),要么是第二步出错(信息错误)。
分析如何更快解决线上问题之前,我们先从反面想想如何阻碍程序员查找线上问题。阻碍查找问题的因素往往是:
1.团队成员之间没有好好交流
2.监控不到位或监控数据错误
3.没有权限查看数据
4.缺乏日志
要想更快解决线上问题,必须将这些阻碍解决。于是,我们可这样:
在重大问题发生时,如果一时无法解决,就让团队成员将各自对于系统版本变更以来所有的改动点进行充分交流,将这些改动点用排除法来确定哪些对故障产生有影响,需要注意的是,故障可以来源于代码之外,想快速确认是否是代码引起的问题,那就可以将上个版本的代码发布,如果问题依然存在,那就是代码之外的问题了。回顾我多次故障处理经历,往往我在代码中怎么也找不到原因时,可以在外部接口调用、数据库、容器、网络连接这些方面找到问题所在。
问题来临时,一定要好好在监控中对比异常状态跟平时的区别,从监控中寻找跟问题相关的数据异常,需要注意的是,不要忽略监控数据本身是错误的这个可能性。查问题时没有权限往往阻碍团队成员查找问题,主管需要进行适当的放权,帮助团队成员更便捷查找问题,而作为下级,应该积极向有权限的人求助,让他帮忙处理。
日志是定位问题不可或缺的利器。日志打印有些原则,对于外部接口调用,必须打印请求体和响应数据以及耗时,数据库耗时、多线程处理耗时也应该记录下来。异常错误应该在日志中能够搜索到。
我想列举下我多次处理线上事故的经历:
hashCode变化引起的Set内存泄露
该系统是一个websocket消息推送的服务,在一次次发版后,内存会缓慢增长,直到占用100%内存自动重启,重启后内存如一般一样降到30%,然后依然是内存不断增长。对比发版代码,发现代码中用到了一个Set<WebSocketServer>,仔细追查源码发现,WebSocketServer的Session成员变量的hashCode会变化,而WeboSocketServer的hashCode计算是每次获取其成员变量计算的,这样的话,这个Set占用空间会越来越大,永不释放内存。
解决办法:将hashCode缓存起来
特定数据库分库键设置不一致导致插入数据无法查询
由于我们的数据量特别大,一个月积累几十亿的数据,因此很多地方进行了分库,我们所有的库都是按照(假设为)mall_id分库的,唯独这一个特定数据库是按照另一个字段(假设为)spec,我们这个时候在进行数据库改造,插入数据库操作是利用数据库中间件来的,数据库中间件是按照spec来分库插入的,但是我们查询是直连数据库且按照mall_id来查的,这就导致插入的数据莫名丢失。问题关键是这个按照spec分库这个规则是我不知道的,团队成员信息不对称导致的问题。
解决方案:插入和查询按照相同的分库规则来
内网网络连接不稳定导致系统整体RT飙升
系统发版时改动挺多,有整体数据源的改动,有数据库连接池的改动,也有线程池的改动,而且RT飙升前依赖的外部平台挂了一段时间。从日志看有70%的接口调用耗时是正常的,偶尔有些好几秒的接口调用,而且从客服那得知反馈系统慢的主要是某一模块的接口慢。于是,一开始怀疑线程池配置有问题,改成以前的配置也无效,然后从监控看到慢的接口其有很多sql执行,并且数据源切换时间很长,我们一般是用线程池来进行批量执行sql,并且我们的分库很多,也就是说我们有上百个数据源。从监控看数据库cpu很闲,慢查询有所上涨但并不多,期间还发现有后台执行脚本同时删除数据库数据,kill掉脚本依然无济于事。在优化掉一部分慢查询sql后再上一版代码依然没用。这时候我们已经把所有可能的内部因素都排查了,于是我们开始怀疑是不是我们依赖的平台的问题,因为我们的服务器、数据库、网络都是在这个云平台。于是我们上线了一版以前的代码,发现依然RT很高。这时可以判断是云平台的问题。联系对方技术,他们说我们的连接数相比昨天增加了70%,继续排查,发现是他们的虚拟ip的有问题导致我们的网络连接不正常,有20%概率获取一次网络连接超过100ms,对方优化虚拟ip问题后,rt恢复正常。事后看数据源切换时间长暗示了网络连接不正常,但是我们当时被慢查询吸引了,没考虑到这个问题。
解决方案:联系云平台解决虚拟ip导致的网络连接问题