故障原因归类分析及预防和应对措施

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

引语

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

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

故障,一般是指一段时间内较为密集的问题发生导致了一定的负面影响。业务量小的极少影响面的问题不算故障,否则就会混淆真正的故障,导致受限资源投入分配不合理,影响关键问题的解决进度;零星的非密集的问题可能不是故障,因为那可能是小概率事件触发了潜在BUG,需要解决,但定为故障有点勉强。

要避免故障,首先需要深入了解故障发生的原因。以下内容来自于对多起故障的分析、归类和总结。

故障原因

多发源

故障多发源,是指发生故障的最常见原因。谨防这几种情形,可以预防大部分的故障可能性。

核心流程出错

核心流程的某个环节出问题,导致整体流程失败,或者部分业务场景的整体流程失败,都会导致密集问题发生。通常是在主流程中添加了一段代码,而这段代码没有考虑到某个场景或者健壮性不佳,影响了整体。

预防措施:

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

真实案例:

缺乏健壮性

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

健壮性不佳,很容易导致由于未预料的局部细节、脏数据、局部调用失败影响整体的流程和展示。

预防措施:

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

真实案例:

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

瞬时大流量

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

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

预防措施:

  1. 集群环境:保证集群各机器或 Region 的负载均衡;
  2. 单机环境:有针对性地限流、限速和限数。
  3. 压测演练。容器化后的压测。

真实案例:

极端情况

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

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

预防措施:

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

真实案例:

依赖失败

依赖失败有如下情形:

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

预防措施:

  1. 当一个项目发布涉及多个系统或许多细节时,就需要编写发布文档,仔细指定发布配置和顺序,保证应用依赖的正确性。在具体发布时,则要严格执行发布文档里指定的检查点清单和发布顺序。检查依赖项:API 版本、Jar 版本、依赖服务、配置项、DB 字段。
  2. 自动降级。严格控制超时,隔离或去掉不必要的弱依赖。

资损

资产是客户非常敏感的私有产权。发生资损时,通常是最高故障级别。

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

预防措施: 1. 直接处理资金业务,注意幂等处理;2. 有依赖状态的资金业务处理? 3. 消除诱导性信息。

新旧迁移出错

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

新旧迁移存在一个权衡:彻底还是减少出错。更为彻底的迁移,出错和故障概率会更大,但新系统会更加清爽;向老系统作一些妥协,可以减少一些出错和故障概率,但新系统会带着老系统的包袱前行,后续依然会出问题。

预防措施:

  1. 分流。 分流可以确保新服务上线之后的影响面逐渐扩大,即使有未考虑的点,也会将影响面控制在最小范围。
  2. 充分测试,事先评估好测试用例并严格执行。
  3. 旧接口迁移到新接口时,返回值的结构和值约定最好一致。如果要改动,需要谨慎先评估好。

老代码

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

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

真实案例:

数据泄露

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

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

真实案例:

性能问题

低性能、低吞吐量在面临短时间内大业务量的冲击时,很容易出现阻塞、延迟,从而导致故障。

预防措施:1. 批量调用替换循环单个调用; 2. O(nlogn) 算法; 3. 多进程或多线程并发; 4. 减少不必要的访问和服务依赖。

其他原因

设备及网络

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

预防措施:

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

脏数据

由于脏数据缺乏整体的关联性约束,应用读取到脏数据,容易出错;如果应用有一连串的逻辑处理,可能生成更多的脏数据,引出更大的麻烦。

预防措施:

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


操作不当

操作不当主要有如下情形:

  1. 两种操作并发执行,导致出错;
  2. 代码合并冲突解决不当;
  3. 操作不规范,引发系统处理失常或失控。

预防措施:

  1. 代码合并冲突解决,双方确认。
  2. 同时更改系统配置,需要协调顺序,避免并发。
  3. 数据修复工作在业务低峰期进行。
  4. 数据修复方案要 check , 确保不引发新的问题。

故障处理

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

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

为了更好地减少故障的可能性,还需要事先做好故障应急预案。

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

小结

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

要预防故障:

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

猜你喜欢

转载自www.cnblogs.com/lovesqcc/p/11392064.html