前言
你是否遇到过大半夜被电话叫醒,开始紧张地查验问题,处理故障以及恢复服务。也许就是因为睡前的一个很小的变更,因某种未预料到的场景,引起蝴蝶效应,导致大面积的系统混乱、故障和服务中断,对客户的业务造成影响。
特别是近几年,尽管有充分的监控告警和故障处理流程,这样的新闻在 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在混沌工程上不断实践,业界对于这个概念的实践也越来越多,通过有目的性的,在生产环境中,在可控爆炸范围内,持续自动化进行实验,从而不断提升系统韧性,增强团队应对异常场景的信心。
混沌工程的理念对于前端系统有很多借鉴意义,前端系统要对依赖系统的不稳定性需要有预案,不断提升自身的健壮性。
好了,各位小伙伴们,准备好给你的前端系统添点乱了吗?
有兴趣同学可以关注微信公众号奶爸码农,不定期分享关于理财、技术、个人成长的信息: