混沌工程:给你的前端系统添点乱

4,112 阅读10分钟

前言

你是否遇到过大半夜被电话叫醒,开始紧张地查验问题,处理故障以及恢复服务。也许就是因为睡前的一个很小的变更,因某种未预料到的场景,引起蝴蝶效应,导致大面积的系统混乱、故障和服务中断,对客户的业务造成影响。

特别是近几年,尽管有充分的监控告警和故障处理流程,这样的新闻在 IT 行业仍时有耳闻。问题的症结便在于,对投入生产的复杂系统有多少信心。监控告警和故障处理都是事后的响应与被动的应对,那有没有可能提前发现这些复杂系统的弱点呢?

混沌工程最早来自Netflix的内部实践,逐步发展壮大逐步在业界形成了大量实践,目前已经有专职的Choas Engineer了,是不是很高大?

刚听到混沌工程想必一脸懵逼,莫慌,我们先从三个直击心灵的问题入手:

  • 什么是混沌工程
  • 为什么要混沌工程
  • 如何实施混沌工程

最后我再结合前端场景,混沌工程如何可以在前端落地实践,介绍了一个前端混沌工程的小工具react-chaos,并且详细讲述了其内部实现原理,希望能够帮助提升前端系统健壮性。

什么是混沌工程

混沌工程 Chaos Engineering定义

在分布式系统上进行由经验指导的受控实验,观察系统行为并发现系统弱点,以建立对系统在规模增大时因意外条件引发混乱的能力和信心。

混沌工程发展简介

2008年8月,Netflix 主要数据库的故障导致了三天的停机, DVD租赁业务中断,多个国家的大量用户受此影响。

之后 Netflix工程师着手寻找替代架构,并在2011年起,逐步将系统迁移到 AWS上,运行基于微服务的新型分布式架构。

这种架构消除了单点故障,但也引入了新的复杂性类型,需要更加可靠和容错的系统。为此, Netflix 工程师创建了 Chaos Monkey,会随机终止在生产环境中运行的 EC2实例。工程师可以快速了解他们正在构建的服务是否健壮,有足够的弹性,可以容忍计划外的故障。

至此,混沌工程开始兴起。

上图中展示了混沌工程从2010年演进发展的时间线:

  • 2010年 Netflix 内部开发了 AWS 云上随机终止 EC2 实例的混沌实验工具: Chaos Monkey
  • 2011年 Netflix 释出了其猴子军团工具集: Simian Army
  • 2012年 Netflix 向社区开源由 Java 构建 Simian Army,其中包括 Chaos Monkey V1 版本
  • 2014年 Netflix 开始正式公开招聘 Chaos Engineer
  • 2014年 Netflix 提出了故障注入测试(FIT),利用微服务架构的特性,控制混沌实验的爆炸半径
  • 2015年 Netflix 释出 Chaos Kong ,模拟AWS区域(Region)中断的场景
  • 2015年 Netflix 和社区正式提出混沌工程的指导思想 – Principles of Chaos Engineering
  • 2016年 Kolton Andrus(前 Netflix 和 Amazon Chaos Engineer )创立了 Gremlin ,正式将混沌实验工具商用化
  • 2017年 Netflix 开源 Chaos Monkey 由 Golang 重构的 V2 版本,必须集成 CD 工具 Spinnaker 来使用
  • 2017年 Netflix 释出 ChAP (混沌实验自动平台),可视为应用故障注入测试(FIT)的加强版
  • 2017年 由Netflix 前混沌工程师撰写的新书“混沌工程”在网上出版
  • 2017年 Russell Miles 创立了 ChaosIQ 公司,并开源了 chaostoolkit 混沌实验框架

对混沌工程的理解

混沌工程师从之前大型应用容灾备份逐步演化而来的,最开始都是单机房单服务器很容易造成单点故障,然后有了云计算,支持集群能够实现机器的水平扩展能力,然后不断加强运维能力,加大监控能够第一时间发现线上问题,这样可以在问题扩大之前及时解决。

随着后端微服务化,部署的机器越来越多,节点数目增加,整体系统的不确定性也越来越大,各种网络故障、磁盘、内存等等问题导致故障率升高。

因此,混沌工程应运而生,通过在分布式系统中进行受控的实验,提升系统的韧性更好的抵御未知的风险。

总结一下混沌工程是:

  • 一种拥抱失败的技术文化
  • 一套抽象严谨的实践原则
  • 一种主动防御的稳定性手段
  • 一个高速发展的技术领域

混沌工程原则

为什么要混沌工程?

“如果你不提早发现和解决问题(引入混沌工程实验),最后问题会来(周末/半夜)解决你”。

混沌工程可以理解成一种主动防御的能力,能够提前找到未知的问题,提高系统韧性。

  • 由于实验是在最小化爆炸范围内进行的,可以减小业务损失,让重大风险在可控范围提前暴露
  • 提升系统弹性,持续验证系统对极端场景的容错能力
  • 增强团队信心,验证稳定性措施有效性,量化团队价值

如何实施混沌工程?

完整的混沌工程实验是一个持续性迭代的闭环体系,从初步的实验需求和实验对象出发,通过实验可行性评估,确定实验范围,设计合适的观测指标、实验场景和环境,选择合适的实验工具和平台框架;

建立实验计划,和实验对象的干系人充分沟通,进而联合执行实验过程,并搜集预先设计好的实验指标;

待实验完成后,清理和恢复实验环境,对实验结果进行分析,追踪根源并解决问题,并将以上实验场景自动化,并入流水线,定期执行;

之后,便可开始增加新的实验范围,持续迭代和有序改进。

在实施过程中需要注意的几个重要事情:

  • 结合技术架构,选择实验工具
  • 最小爆炸半径,控制实验风险
  • 建立面向失败设计的技术文化
  • 围绕战略制定目标,围绕目标设计组织
  • 复用成熟产品,提升效能

阿里云技术专家周洋在《云原生构架下的混沌工程实践》中提到在阿里新零售、云服务、云业务等领域如何落地混沌工程的。

最小化爆炸半径,实现常态化的实验

在各个系统中提供统一的接入层,能够很方便的进行混沌实验,同时最小化爆炸半径,控制实验风险。实现常态化的实验,多多暴露出问题。

记得有一年阿里双11的时候,在0点高峰过后半小时,高层技术团队主动切断某一个机房的网络,进行错误容灾演练,虽然需要冒着一定的风险,但是对于锻炼团队如何应对这种突发情况有很大帮助。

云服务的稳定性

在云服务上,对于稳定性不断提出要求,需要能够应对各种极端场景。可以看到阿里云在这方面有非常多的实践,从硬件、网络、系统到运行态都有很多的应对方案。

通过平台能力,标准化实验流程

基于平台化插件能力,可以标准化整个实验流程:计划、执行、观察、记录、还原和分析,通过不断标准化的演练可以不断提升系统的稳定性。

前端能否实施混沌工程?

可以看到业界关于混沌工程的实践主要还是在服务端,那么前端是否有必要实施混沌工程?

答案当然是Yes。

前端页面作为用户感受功能的入口,用户体验是第一位的。但是由于各种各样的问题,例如服务端数据返回异常、类型检查异常等等原因,会导致整体页面白屏,功能不可用。

对于前端系统,如何应对各种的异常数据,如何将错误范围控制在最小可控的范围,如何能够保证降级使用,对于前端工程师都提出了越来越高的要求。

前端混沌工程工具 - React Chaos

J.C Haitt宣布了一个可以用于前端混沌工程的工具-React Chaos (github.com/jchiatt/rea… ) ,他创造了一个工具可以通过抛异常错误从而随机破坏你的React组件。React Chaos是一个高阶组件,可以通过高阶组件的方式包装任何你想测试出现异常的组件。

React-Chaos提供了两个核心方法和组件:

  • withChaos:通过高阶组件包装其他组件,从而可以随机抛出异常错误
  • ErrorBoundary:处理异常错误

withChaos的使用与实现

const ComponentWithChaos = withChaos(
  ComponentWillHaveChaos,
  1,
  'a custom error message, level 1',
  true
);

通过withChaos方法封装了一个组件,我们可以看看withChaos是如何实现的,下面是withChaos的方法定义,四个参数:待包装的组件,混沌等级,错误消息和是否在生产环境执行

const withChaos = (
  WrappedComponent: React.ElementType,
  level: Level,
  errorMessage?: string,
  runInProduction?: boolean
)

在withChaos方法里面,会返回另一个组件,其中的实现就是通过高阶组件的方式,通过Chaos组件来包装WrappedComponent组件,从而可以在Chaos组件里面实现额外的能力。

  return class extends React.Component {
    render() {
      return (
        <Chaos
          level={level}
          errorMessage={errorMessage}
          runInProduction={runInProduction}
        >
          <WrappedComponent {...this.props} />
        </Chaos>
      );
    }
  };

我们再看看Chaos组件里面的实现逻辑,里面有一段根据level随机抛出异常的逻辑

const chaosLevel = level !== 5 ? convertChaosLevel(level) : 0.5;
const chaosOn = Math.random() >= chaosLevel;
if (chaosOn) {
  throw new Error(errorMessage);
}

至此我们看到了withChaos的实现原理,通过Chaos组件作为WrappedComponent的高阶组件,从而在Chaos组件中根据Level抛出异常,达到组件挂掉的效果。

ErrorBoundary的使用与实现

ErrorBoundary可以封装一个组件,从而捕获该组件出现的异常,并且可以在出现异常的情况下触发fallback。它的使用方式如下:

<ErrorBoundary fallback={<Fallback />}>
    <ComponentWithChaos />
</ErrorBoundary>

在ErrorBoundary组件中,它其实通过componentDidCatch的生命周期方法来检测是否出现异常,从而改变state进行异常情况下的渲染。

componentDidCatch(err: Error) {
    this.setState({
        hasError: true,
        error: err,
    });
}
...
render() {
    const { error } = this.state;
    const { children, fallback } = this.props;

    if (error) {
      return (
        fallback || (
          <pre>Error was caught but no fallback component was provided.</pre>
        )
      );
    }

    return children;
}

实践效果

最后,我们看一下例子的效果:

  • 初始的效果

  • 随机异常的效果

使用这个工具,通过withChaos方法使用少量的代码可以很方便模拟出组件异常的场景,使用ErrorBoundary可以很方便的控制捕获异常情况,防止整个页面白屏。

写在最后

混沌工程其实并不是一个全新的概念,对于服务降级、异地多活、异常处理的理念大家都有不少的实践。

随着Netflix在混沌工程上不断实践,业界对于这个概念的实践也越来越多,通过有目的性的,在生产环境中,在可控爆炸范围内,持续自动化进行实验,从而不断提升系统韧性,增强团队应对异常场景的信心。

混沌工程的理念对于前端系统有很多借鉴意义,前端系统要对依赖系统的不稳定性需要有预案,不断提升自身的健壮性。

好了,各位小伙伴们,准备好给你的前端系统添点乱了吗?


有兴趣同学可以关注微信公众号奶爸码农,不定期分享关于理财、技术、个人成长的信息: