本文转载自微信公众号「跨界架构师」,作者Zachary 。转载本文请联系跨界架构师公众号。
作为程序员,相信有一件事是大家最不想见到的。那就是,线上运行的系统出现了技术性故障。(特别还是周末你正在外面happy的时候:D)
处理这类事情特别能体现一个人的综合能力。因为它会涉及到抗压能力、对外的沟通能力,以及排查问题所需的技术能力等等多个方面。
如果你还没机会成为核心开发,其实很少会有这样充满压力的经历。因为在这个情况下处理事情其实是很慌的,毕竟所有使用系统的人以及他们的老板、你的上级、你的老板等等无数双眼睛都在盯着这件事情。
我还记得有一年双11,我作为“首席问题处理官”正在紧急处理服务器扛不住压力的问题,老板默默走到我身后问到“什么问题啊?什么时候好?”。你脑补一下这画面,想象一下看看。
只要你接下去还会继续从事程序员这个职业,我想这样的场景你总归会有机会遇到的。因为一个著名的定律——墨菲定律。
墨菲定律:凡是可能出错的事就一定会出错。维基百科
如果没有一个清晰的应对思路,那么一旦发生线上问题就会像热锅上的蚂蚁一样,急得团团转,像无头苍蝇一样到处乱撞(试)。
所以,我这次就想分享一些我多年作为“首席问题处理官”所总结下来的经验。这可是承载了我N多汗水和脑细胞的经验~
在日常的项目开发迭代过程中我们遇到一个bug,大家的处理过程几乎是一样的:定位bug -> 解决bug。可能少部分人在解决bug之后会有一个思考、复盘,看看是否有类似bug的地方,一并处理掉。
这个“定位 -> 解决 -> 复盘”的过程也同样适用于线上问题的处理。但是必然不仅仅如此。
俗话说,解决一个问题最难的地方不是解决的过程,而是定位的过程。所以,针对线上问题我们等不起定位问题所花费的时间,因此要将「恢复」系统正常使用放在最首要的位置。所以,这个过程就变成了:“恢复 -> 定位 -> 解决 -> 复盘”。
这里多说一句,有一部分人的观点认为,将恢复系统作为首要目标,应当包括牺牲保留现场的动作,因为这个动作可能也需要耗费数分钟才行。
我对这个观点持反对意见。理由是,解决问题的时长的确是一个很重要的指标,但是问题既然已经发生,如果由于没有保留现场导致后续没有排查到根源,导致下次该问题再次出现,到时候场面将会更加难看。所以我对这事的观点是,保留现场最重要。因此,这个过程又变成了:“保留现场 -> 恢复 -> 定位 -> 解决 -> 复盘”。
当然了,保留现场也不是说非得面面俱到,花很多时间。用最快的方式保留你当下所能想到的所有相关线索的地方即可。如果事后还是由于线索不足导致未能排查到根本原因,那只能说经验不足,考虑一下以后需要多保留哪些现场数据才行。
好了,确定了这5个步骤,那么具体每个步骤可以做些什么呢?我来一个个说。
/01 保留现场/
保留现场最最最重要的一件事是保存异常程序的dump文件。有了它,你就可以摆脱盲人摸象式的分析问题,可以快速定位问题的发源地。
我用了三个“最”来强调它的重要性。如果你还没掌握它,那么后面我提到的东西都先放一放,先去掌握它。
另外,如果系统的监控体系并不完备的话,还需要将问题发生时,操作系统、各第三方组件自带的监控数据快速地通过截图保存下来。
保存监控数据的时候要特别留意一下网络相关的数据。如果发现网络相关的数据有异常,那么再把当下的网络连接情况通过命令保存下来。因为相对来说,网络出现问题的概率远远大于硬件,不管是程序导致的还是其他原因。规模越大的系统,越是如此。
/02 恢复/
恢复系统访问有很多方法。首先不得不提到一个适用于80%情况的神技了——重启。没错,根据多年的经验来看这招的确在大多数情况下很有效。
也正因为屡试不爽,所以很多人习惯性地会在第一时间去重启,导致现场忘记保存并受到破坏。
重启也分两种,强制重启和自然重启。当然优先考虑自然重启,这样能避免产生一些意料之外的脏数据。但是如果是系统出现资源耗用异常的话,就不要傻傻地等自然重启了,只能强制重启(kill掉进程)。
第二种常见的方法是「回滚」。当然它的前提条件是你判断下来问题的出现是由于最近一次发布。否则盲目的回滚不但起不到作用,还会越弄越乱,特别在分布式系统中。因为在分布式系统中,一旦上下游耦合的地方出现对接不上,轻则报错,重则出现大量的异常数据,够你后续折腾好久的。
第三种方法是「降级」。暂停出问题的模块,停止服务。当然,这个动作需要和业务方做好沟通,是否单独降级某个模块会导致业务不完整之类的问题。
第四种方法是「限流」或者「扩容」。如果你发现是系统扛不住突增的流量,如果有条件的话可以快速扩容几台机器和程序。如果没法扩容的话可以选择限流,将一定百分比的请求直接拒绝服务。毕竟所有无法提供服务和部分无法提供服务相比,肯定还是后者划算。
还有一些比较小众的方法是「切到备机」、「故障隔离」等,这里就不展开了。它们对环境、条件的要求更多一些。
有时候可能系统并未恢复到完全正常的状态,比如,读取数据是OK了,但是某些操作写入数据到时候还是有问题。在这样的情况下,不要着急定位问题,还是先尽最大努力恢复到最大程度的可用状态再进行下一步的动作,毕竟用户第一嘛。
/03 定位/
关于定位问题,如果有dump文件的话最方便了,通过dump文件分析工具来分析dump文件就可以快速定位到出问题的代码行,特别是程序阻塞、内存溢出、cpu100%之类明显是程序本身的问题。
不同的语言有不同的dump分析工具,可以自行网上搜一下教程。最终目的就是定位到异常点的堆栈信息,有了它就相当于直接把问题代码出现在哪里都给定位到了。
如果说分析dump文件是跳过抽丝剥茧的步骤,直击要害的话。通过监控数据、日志层层分析是个慢活。但是如果缺失dump文件或者从dump文件从未能分析出问题的情况下,也只能选择后者。
我们在看日志、监控数据的时候一定要有关联起来看的意识,而不能仅仅在单个维度上看。因为有时候你在单个维度上看到的数据像是正常的,但是你关联起来看就不一定了。比如,tcp连接数降低了一半,但是内存反而涨了100%,为什么?这里面可能就藏着故障的线索。
/04 解决/
定位到了问题,解决起来就很简单了。该改代码的改代码,该改配置的改配置文件。这里就不多说了,毕竟情况太多,大家遇到的可能都不太一样。
/05 复盘/
大家都知道复盘的好处,但是真正做复盘的人真不多。如果你不知道从何下手来做复盘的话,不妨从以下几个问题入手,
- 这次故障原因是什么?
- 是否有更快的方式在当时来恢复业务?
- 如何避免再出类似故障?
- 当前系统中是否还有类似的潜在风险?
如果你能回答这些问题,我觉得这个复盘就很到位了,剩下的就是执行。
当然了,不管如何优秀的处理故障,最理想的还是不要发生故障。所以我们需要在前期做更多的准备。
/01 了解你的程序/
我们很多人了解自己负责的程序只有通过coding这一种途径。除非该程序是个单体应用,否则这样的方式是远远不够的。
我建议你按照以下清单去了解你的程序:
- 程序包含有哪些模块,对应使用者是哪些?哪些是核心模块,哪些是可以“弃车保帅”的?
- 多个模块/系统间如何流转的?(尽量画一个流程图,加深记忆)
- 依赖了哪些中间件,谁负责维护他们?
- 依赖了哪些其他的程序,强依赖还是弱依赖,谁负责维护他们?
- 依赖的存储、消息队列背后又依赖了哪些存储,存储运维负责人是谁?
- 线上的程序部署在什么环境。你是否有条件独立进行部署并调优?
/02 做好监控/
大多数的故障不是突然发生,而是有一个逐渐积累的过程,直到爆发。所以监控的价值不仅仅是看看数据那么简单,对于异常识别特别有帮助。
一般监控分两个维度,系统维度和业务维度。监控指标分为三层,「环境指标」、「程序指标」、「业务指标」。具体怎么做我在之前的文章《分布式系统关注点——360°的全方位监控》有具体说明,这里就不赘述了。
如果是分布式系统,还可以搭建一个请求链路跟踪系统。有很多成熟的现成解决方案,CAT、SkyWalking、Zipkin、Pinpoint等等。
多说一句,我们在做监控预警的时候,除了设置阈值还要关注一下波动率。比如,某项资源日常使用率20%,除了设置超过80%的阈值进行预警之外,在它产生波动幅度100%(使用率40%)以上的时候也需要预警,提前让人关注。否则一旦以一个较快的速度增长到80%之后,留给你在故障爆发前消灭它的机会就非常渺茫了。
另外,针对常见的故障预设几套故障响应方案,以及进行定期的故障演练(一般上了一定规模的公司或者处于扩张期的公司才会考虑)可以让团队面对线上故障的时候更加地游刃有余。
假如你不幸成为了线上故障的解决者,如果上级不在旁边的话,需要定时向上级汇报问题处理情况,以便TA了解问题的严重程度、修复进度并作出决策。
反正就算你不上报,迟早也会被催。与其被动的被催,不如主动上报。
好了,总结一下。
这篇呢Z哥和你分享了一些处理线上故障的经验。
从思路上,分为五个步骤“保留现场 -> 恢复 -> 定位 -> 解决 -> 复盘”。除了第四步「解决」外,我都进行了展开。
- 保留现场。主要是保存dump文件,以及截图系统监控的数据(没有在外部搭建的监控系统内的部分)。
- 恢复。主要重启、回滚、降级、限流和扩容。还有一些小众的方法「切到备机」、「故障隔离」等
- 定位。分析dump文件、分析监控数据、程序运行日志。
- 解决。
- 复盘。搞清楚以下4个问题就够了:
- 这次故障原因是什么?
- 是否有更快的方式在当时来恢复业务?
- 如何避免再出类似故障?
- 当前系统中是否还有类似的潜在风险?
然后还建议了你要未雨绸缪,尽可能减少故障在线上爆发。
- 解你的程序
- 做好监控