故障常见缘由归类分析及预防和应对措施

每一次故障都是一次宝贵的学习机会。html

引语

故障是开发者头上悬着的一把剑。俗语曰:no zuo no die. 但是开发者很难作到 no zuo. 如何在 zuo 的时候防止 die 呢 ?程序员

知己知彼才能百战不殆。要避免故障,就须要对故障有一个相对深刻的理解。算法

故障,通常是指一段时间内较为密集的问题发生致使了必定的负面影响。业务量小的极少影响面的问题不算故障,不然就会混淆真正的故障,致使受限资源投入分配不合理,影响关键问题的解决进度;零星的非密集的问题可能不是故障,由于那多是小几率事件触发了潜在BUG,须要解决,但定为故障有点勉强。数据库

要避免故障,首先须要深刻了解故障发生的缘由。如下内容来自于对多起故障的分析、归类和总结。

后端

分析方法

拿到一个故障,如何分析它 ? 如何从中学到最大的收获 ? 如何给它进行归类呢 ?安全

首先,要确立分析目标。 我重点关注的是故障发生的主要缘由及预防措施,而不是现象及处理过程和时长。所以,能够概览故障现象描述、处理过程及时长、次要因素等,除非其中有重要价值的内容;网络

其次,软件的根本任务是处理数据。 故障的本质就是数据处理出错了。 或者是 数据处理成非预期的结果,或者是数据处理延迟,或者数据展现有问题,或者兼而有之 。 所以,数据是故障分析的一个重要关注视角。并发

再次,处理能够抽象为算法。 处理有问题,或者是选用了错误的算法,或者是算法里新增或修改的部分对某些场景不适配,破坏了原有约定 ,或者兼而有之。所以,算法是故障分析的另外一个重要关注视角。负载均衡

最后,若是一个故障的缘由有一个明确的断定,就能够为之定名;若是它不属于已有的任何一个定名,就要新建一个定名,将其放入其中。运维


故障缘由

多发源

故障多发源,是指发生故障的最多见缘由。谨防这几种情形,能够预防大部分的故障可能性。

核心流程出错

核心流程的某个环节出问题,致使总体流程失败,或者部分业务场景的总体流程失败,都会致使密集问题发生。一般是在主流程中添加了一段代码,而这段代码没有考虑到某个场景或者健壮性不佳,影响了总体;在测试的时候,只验证了改动点部分,没有回归核心流程,或者回归了核心流程,却遗漏了某个场景的回归。

预防措施:

  1. 评估改动点! 很是重要!哪怕只有一行,只要在主流程中,都要仔细评估其影响范围。在主流程中添加的代码越长,越要警戒。
  2. 增长必要的 try-catch 。若是增长的代码只有局部影响,能够添加必要的 try-catch,防止未预料的情形的处理异常影响总体流程。
  3. 最好不要轻易改动影响全局的通用方法和配置(影响面和回归面很是大); 尽量只新增而不是修改。
  4. 覆盖全面的核心流程的测试用例,每次发布都须要回归经过。
  5. 有风险性的改动,增长开关。一旦出错,当即关闭改动。

真实案例:

场景遗漏

业务会逐渐发展成庞然大物,随着人员的流动,不少业务知识和场景会逐渐被淡忘。新进的同窗若是没有充分评估到各类场景,就很容易觉得遗漏某个场景,致使问题。要解决这种缘由,是比较棘手的。

预防措施:

  1. 沉淀业务文档和业务场景。
  2. 有全景图意识,不限于眼前的一亩三分地。
  3. 有熟悉业务的同窗进行方案评估和 CodeReview 。

缺少健壮性

实现服务以后,健壮性是保证服务可以平稳运行、正确应对错误和异常的第一道关卡,也是合格程序员的必备代码素养之一。

健壮性不佳,很容易致使因为未预料的局部细节、脏数据、局部调用失败影响总体的流程和展现。

预防措施:

  1. 思考错误和异常,多多益善。
  2. 善用 try-catch 保驾护航。
  3. 使用空字符串、空列表替代 null 。
  4. 异常分支的测试覆盖。

真实案例:

  • 因为一个 null 值致使整个订单列表加载失败。
  • 因为一个次要的依赖出错致使整个详情页加载失败。
  • 异常分支的代码有问题,但没有测试;当流程走到异常分支代码,任务直接跪掉,反复重启和跪掉。

瞬时大流量

瞬时大流量是形成故障的一大杀手。 瞬时大流量,会致使机器资源短缺,CPU 飙升或内存爆满或网卡、链接数打满,直接影响总体服务的稳定性。

对于消息处理应用来讲,瞬时大流量会致使消息处理延迟,业务状态流转滞后,影响后续环节;对于非消息处理应用来讲,则会致使任务处理阻塞,接口响应变慢或不响应。

低性能、低吞吐量在面临持续多个较大业务量的冲击时,很容易出现阻塞、延迟;若是应用无限流,或限流失效,或限流不够精确,均可能难以抵挡大流量的侵袭。

预防措施:

  1. 集群环境:保证集群各机器或 Region 的负载均衡;
  2. 单机环境:有针对性地限流、限速和限数,并严格测试和验证。
  3. 压测演练。容器化后的压测。
  4. 减小或消除太重的锁逻辑。
  5. 大流量预警和感知。好比线程池队列阻塞预警,计算出的大数据集处理的预警 等。
  6. 批量调用替换循环单个调用;O(nlogn) 算法;减小没必要要的访问和服务依赖。

真实案例:

极端状况

极端状况是指,一些很罕见的事件的发生挑战了系统的某个局部极限,致使系统出了问题。

好比说,一个订单内的商品种数一般不会超过 10 ,但商家或买家刷单,致使大量含有 50 多个商品的订单,而后密集导出,就会致使应用 FullGC 严重,引发接口响应超时或任务没法进行下去。

预防措施:

  1. 思考极端情形及影响;
  2. 提早作好极端情形测试和设计方案。

真实案例:

依赖失败

依赖失败有以下情形:

  1. 所依赖的服务、配置或变量不存在或处于不合适的版本,致使应用启动失败,或者启动后的服务不能正常运行;
  2. 所依赖的基础服务不稳定出现大量报错时,会致使依赖它的高频应用也出现大量报错,致使雪崩效应。
  3. 配置或服务循环依赖,致使死循环。

预防措施:

  1. 当一个项目发布涉及多个系统或许多细节时,就须要编写发布文档,仔细指定发布配置和顺序,保证应用依赖的正确性。在具体发布时,则要严格执行发布文档里指定的检查点清单和发布顺序。检查依赖项:API 版本、Jar 版本、依赖服务、配置项、DB 字段。
  2. 自动降级。严格控制超时,隔离或去掉没必要要的弱依赖。
  3. 制定明确的依赖原则,上游依赖基础,避免循环依赖。
  4. 先后端对接口约定的返回值及格式沟通达成一致。

资损

资产是客户很是敏感的私有产权。发生资损时,一般是最高故障级别。

资损通常发生在:1. 直接资损: 系统处理未考虑幂等,致使重复消息屡次处理;2. 业务方根据基础服务方的某些字段进行资金业务处理,而字段返回值有误,致使少算或多算。3. 诱导性资损,因为某些展现信息,诱导用户作出某种难以追回的行为,好比已发货订单展现为待发货;

预防措施:

  1. 直接处理资金业务,注意幂等处理;
  2. 有依赖状态的资金业务处理?
  3. 消除诱导性信息;
  4. 对资金计算敏感,尤为注意边界值处理,避免 +1 或 -1 致使问题。

新旧迁移出错

多发生于技术重构优化的时候。好比旧的领域模型迁移新模型、旧的技术栈迁移新技术栈、旧的页面迁移新页面。作技术改造,侧重点每每在于新服务的测试,而容易忽略老服务的测试兼容。

新旧迁移存在一个权衡:完全仍是减小出错。更为完全的迁移,出错和故障几率会更大,但新系统会更加清爽;向老系统做一些妥协,能够减小一些出错和故障几率,但新系统会带着老系统的包袱前行,后续依然会出问题。

预防措施:

  1. 分流。 分流能够确保新服务上线以后的影响面逐渐扩大,即便有未考虑的点,也会将影响面控制在最小范围。
  2. 充分测试,事先评估好测试用例并严格执行。
  3. 旧接口迁移到新接口时,返回值的结构和值约定最好一致,确保 新对新,老对老,避免“新对老”的不兼容致使问题。
  4. 老的页面和功能要回归全面,避免重要场景遗漏。
  5. 新的和老的代码改动分开 CR ,分批 CR 。

老代码

不能否认,老代码在企业初创期曾立下汗马功劳。但是,随着时间推移,业务量愈来愈大,复杂度也在快速增长,不少老代码的简单处理就逐渐变成了“定时炸弹”,冷不防让地震一震,让人抖一抖。

预防措施: 按期梳理和清除。

真实案例:

数据泄露

数据安全性愈来愈成为企业的重要关注点。对于 SaaS 来讲,要保证各个租户的数据和操做互不影响,不能看到和操做未受权的数据。

预防措施: 1. 敏感数据脱敏; 2. 避免覆盖; 3. 权限控制; 4. XSS 安全问题。

真实案例:

设备及网络

设备及网络属于互联网的基础设施,位于最底层,一旦出现问题,影响面也是巨大的。当设备老旧出现硬件故障或宕机,或者网络抖动或忽然断开,也是很容易致使大面积失败。

预防措施:

  1. 按期检查和更换老旧设备。花点钱更换老旧设备,比宕机出现问题花费时间、精力和金钱补偿,要划算得多。
  2. 备用链路和机房。
  3. 避免单点故障。


操做不当

操做不当主要有以下情形:

  1. 两种操做同时进行,发生冲突致使出错;
  2. 代码合并冲突解决不当;
  3. 操做不规范,引起系统处理失常或失控。

预防措施:

  1. 代码合并冲突解决,双方确认。
  2. 同时更改系统配置,须要沟通协调顺序,避免并发。
  3. 批量数据修复方案要仔细 CodeReview , 当作正式发布处理;
  4. 批量数据修复在业务低峰期进行,除非是紧急修复。
  5. 变动操做的工具自动化,审批流程。

真实案例:

  • 在业务量比较大的情形下,对表进行 truncate 操做可能会致使数据库 hang 住。

其余缘由

资源未隔离

底层集群未能隔离不一样业务的资源, A 业务的大流量致使集群机器资源被打满,间接影响了 B 业务。

预防措施:

  • 规划和实现资源隔离策略:重要业务和次要业务的资源进行隔离;不一样业务的资源进行隔离。

脏数据

因为脏数据缺少总体的关联性约束,应用读取到脏数据,容易出错;若是应用有一连串的逻辑处理,可能生成更多的脏数据,引出更大的麻烦。

预防措施:

  1. 检测和消除脏数据。
  2. 避免在线上造测试数据。


故障处理

发生故障时,第一反应不是当即排查缘由,而是当即止损,将影响面最小化。

  • 若能肯定是发布致使,当即回滚发布。 回滚发布后,再仔细排查缘由。
  • 及时同步进度,让关注方知悉;
  • 创建快速同步机制,预防小问题演变成大故障。

为了更好地减小故障的可能性,还须要事先作好故障应急预案。

  • 梳理底层的强弱依赖,肯定强依赖不可用时致使的影响面;
  • 当强依赖不可用时,可以快速恢复的方案,将影响面下降到最小。
  • 故障演练。模拟大流量、极端状况和故障情形发生,检测应急预案是否生效和快速恢复。

根因探讨

故障层出不穷,现象眼花缭乱,究竟从何处来,去往何处呢 ? 是否有根本规律可循 ?

事实上,绝大多数的软件故障都是具有内在的逻辑关联的。从基础逻辑关联来推理,能够推断出不少本能够预防和避免的问题。与正常流程相比,故障自己也是一种路径,产生出特定的数据集,只是这些数据集及引起的现象是不符合人们的预期的。如下是部分基础逻辑关联分析:

  1. 依赖问题。 一个功能会依赖某个字段、配置、校验、接口约定等;当字段或配置变动不合理,或语义发生变化时,会致使问题; 两个业务 A 和 B 均依赖同一段代码 c ,根据对 A 的某个需求在代码 c 修改了一些逻辑 ,影响了 B ,结果致使 B 出了问题;在原有流程中多了没必要要的校验或少了必要的校验,会致使问题;原来依赖格式 A,迁移新服务后变成了不兼容的格式 B。 绝大部分功能性问题均可以归结为评估影响面不许确。
  2. 流量问题。一般是大流量或极端情形致使,超过了系统可以承载的阈值。对系统的阈值压测摸底并提早预估好容量,辅以通过严格测试和验证过的限流、限速等。
  3. 环境问题。依赖的环境假设出现问题,致使依赖链路阻断,从而引起各类问题。须要在运维层面保证环境的高可用高可靠性,避免单点。

小结

故障,是每一个开发者乃至企业法人都不肯意经历的事情。但是,每一次故障,都蕴含着不一样形式的疏忽、未知、真理,正向思考,实际上是一次很是珍贵的学习机会。故障,也会引导人抵达更深刻的境地,去理解事情的本质与关联。正视故障,从故障里学习真知,预防和避免故障,乃是更佳的姿式。

要预防故障:

  • 第一是细心。多个心眼,准确评估影响面,兼顾考虑老业务老功能的回归, 仔细检查依赖项,保证返回约定的一致性,规范执行;
  • 设计和实现要考虑健壮性、大流量和极端情形,避免低性能。
  • 有针对性避免安全性和资损问题。
  • 设置严密的监控报警,在问题的萌芽期掐灭。
相关文章
相关标签/搜索