构建可靠系统的原则与实践

简介: 随着阿里技术的发展,我们的技术系统越来越成为社会的基础设施,对于这些系统的可靠性要求也就越来越高。但是实际上很多的基础的产品和系统确仍然会出现一些稳定性问题,那么如何才能构建可靠的系统呢?是不是制定非常严格而细致的规则就可以做出可靠的系统呢? # 航空业的教训 在回答这个问题之前,我们先来看看对于系统可靠性要求非常高的航空业是怎么做的?美国的FAA是在航空安全领域事实上的权威,为了保证航空

随着阿里技术的发展,我们的技术系统越来越成为社会的基础设施,对于这些系统的可靠性要求也就越来越高。但是实际上很多的基础的产品和系统确仍然会出现一些稳定性问题,那么如何才能构建可靠的系统呢?是不是制定非常严格而细致的规则就可以做出可靠的系统呢?

航空业的教训

在回答这个问题之前,我们先来看看对于系统可靠性要求非常高的航空业是怎么做的?美国的FAA是在航空安全领域事实上的权威,为了保证航空器的安全,FAA制订了非常详细而复杂的航空器认证规则,而这些规则是否就保证了航空器的安全了呢?

让我们来了解一下最近的两起空难:
埃塞俄比亚航空ET302航班
2019年3月10日,埃塞俄比亚航空ET302航班在起飞六分钟后坠毁,飞机上载有149名乘客和8名机组人员。
印尼狮航JT610航班
2018年10月29日,印尼狮航JT610航班在起飞后约十分钟坠毁,飞机上载有181名乘客,和8名机组人员。

几百条鲜活生命的消逝,这是多么严重的后果啊!究竟是什么原因导致了这样的灾难呢?

这两起空难的共同点是都是波音的737MAX机型,并且都是在起飞后不久发生的空难。那么这个背后的原因是什么呢?虽然官方的调查还没有结束,但是民间的分析指向了同一个原因,那就是这款机型的设计问题。
CFM LEAP engine
上图展示了Boeing 737 MAX的CFM LEAP引擎,值得注意的是和一般民航飞机不同的是,引擎的上沿和机翼平面几乎齐平。为什么会这么设计呢?这是因为波音737系列最早是上世纪60年代设计的,当时的引擎的直径小很多,外形更加细长,而机翼的高度是和引擎直径相匹配的。但是随着技术的发展,更新更省油的引擎直径变得越来越大,这时候原来的机翼高度无法满足更大直径引擎的安装空间,要想调整机翼的高度则需要改变起落架的设计,改变起落架的设计则需要改变起落架收起时相关机体位置的设计,而机体设计的变化会带来更多的变化从而会被FAA认为是一款全新型号的飞机,而全新型号的飞机则需要经历完整的FAA认证流程,会带来巨大的时间和经济成本。为了避免这样的成本波音选择了将引擎前移并且提升高度,但是这样带来了另外一个问题,由于空气动力学方面的原因,飞机会变得静不稳定,特别是在起飞阶段,引擎的推力会导致飞机迎角过高进入危险的失速状态。为了回避这个问题,波音引入了一个自动控制程序MCAS,通过读取迎角传感器的数据判断飞机是否迎角过高,如果过高的话自动控制飞机降低迎角,从而保证飞机的安全。

那么这么一套保证飞行安全的系统和空难有什么关系呢?事实上MCAS系统工作得非常好,根据波音自己的统计,Boeing 737 MAX系列已经完成了数十万次的安全起降。但是问题在于当传感器工作不正常时,MCAS有可能会根据错误的迎角数据做出错误的判断和动作,也就是在不应该降低迎角的时候降低迎角,导致飞机直冲地面。

一起后果扩大的故障

回到我们的工作中,前不久我们碰到了一起系统故障,其过程有一定典型的意义,为了描述方面,这里隐去一些具体细节,简单说一下故障的过程。
开始的时候,由于某些原因导致缓存命中率有所下降,而缓存命中率下降导致了数据库load升高,而数据库load升高以及可能的慢SQL导致了部分请求在获取DB connection的时候超时,从而引发了exception。当exception发生的时候,为了保证系统的可用性,系统逻辑进入了一段兜底逻辑,而这段兜底逻辑在特定的条件下产生了错误的返回,从而导致线上脏数据,而这些脏数据带来了业务资损和大量的人工订正数据的成本。

这个故障处理的过程并不是重点所以不再赘述,我们要问的是为什么一个简单的exception会导致这么严重的后果呢?

两个事例的共同点

如果我们仔细去观察上述两个事例,我们会发现其中有如下几个共同点:

  • 为了好的目的而引入了非常简单的备用逻辑直接保证“效果”
  • 这些备用逻辑在绝大部分情况下都能正常工作
  • 但是在极端情况下这样的逻辑失效了,并且产生了严重的后果

换句话说,系统设计者在尝试用非常简单的逻辑去解决一个实际上复杂的问题,虽然实际上并没有完全解决问题,但是因为这样的逻辑能够通过大量的测试(或者合规检查),所以系统设计者“假定”问题得到了解决,从而放心地应用到了生产环境。

那么一个非常复杂的问题是否真的能够通过一个简单巧妙的办法解决吗?

没有银弹

在系统设计领域,我们通常会把问题的复杂性分为两类,分别是偶得复杂性,实质复杂性。

偶得复杂性 Accidental Complexity

所谓偶得复杂性是指由开发者自己在尝试解决问题时引入的复杂性挑战,一般而言是由解决问题的方法和手段带来的,对于特定的问题,不同的方法会带来不同的偶得复杂性。

实质复杂性 Essential Complexity

所谓实质复杂性是由事务本身所决定的,和解决方法无关。

对于偶得复杂性,通过变换解决办法是有可能用简单的办法来解决的,但是对于实质复杂性,我们是无法通过改变手段来解决的,而必须采用相应复杂的方法来解决问题。换句话说对于实质复杂的问题,不要指望有银弹

上述事例中,实际的环境和问题是存在比较大的实质复杂性的,然而我们却试图通过一些非常简单的逻辑去解决问题,从而带来了严重的后果。

快速失败 Fail Fast

那么要想防止这类问题,设计高可靠的系统要怎么做呢?
这里我想介绍一条反直觉的软件设计原则,快速失败(Fail Fast):

In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure.

这是一条反直觉的原则,大部分人听说这条原则的第一反应是这样不是让系统变得更加脆弱了吗?

实际上并不是,原因在于我们不能停留在某一次的失败(failure),而是需要观察完整的过程,如下图所示:
How it works?
当问题发生时,系统立即停止工作,由人工介入找到并以合理的方式解决根本的问题,然后系统恢复运作。通过这样的选择,我们就能够更早更容易地暴露问题,每当系统发生问题之后,真正的根因会更快得以解决,所以最后我们就能得到一个更加可靠的系统。

需要说明的是,快速失败不是说系统设计不处理任何问题到处失败,而只是在面对essential complexity的时候,不要尝试用一个简单粗暴的方案去解决,要么就用一套合理的机制设计去解决它,要么就fail fast把控制权交给系统上层决策,通常来说最终可能会回归到人,由人来分析和处理问题。

根据实际的工作经验,我还发现一个有意思的现象,很多系统的设计者往往高估一些能轻易想到的问题的严重性,而低估那些想不到的问题的严重性。上述的软件系统故障的例子中,如果异常往外抛出,问题的后果可能仅仅是某些操作人员的部分操作失败,用户可能会重试,也可能会开工单把问题反馈上来。只要我们处理工单的同学及时响应并且解决根本问题,这个问题就不会演变成一个比较严重的问题。但是不抛出异常通过备用逻辑来兜底一旦失败,会导致比较严重的后果。如果系统的设计者能够认真衡量和计算这些后果的差别,就会做出更加合理的选择。

当然还有一点就是快速失败会把更多数量的问题暴露在用户面前,让用户在心理层面有不好的影响,但是需要注意的是不暴露问题不等于解决问题,暴露问题只是让大家看到了问题而已。为了更好更早地暴露问题,一方面我们需要引入更完备的测试防止问题进入生产环境,另外一方面也需要引导系统使用者以更加客观实际的心态来接受系统中的问题。

不要重复自己 Don't Repeat Yourself

和构建稳定系统相关的另外一条原则是软件工程中常说的DRY原则,也就是:

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

这一条和稳定系统的关系在于,通过合理的复用设计,能够大幅度提高系统的可测性,降低调试问题的难度,提高系统的可维护性,背后的逻辑还是比较简单的,这里不再赘述。

如何实践上述原则

那么要想实践上述原则构建可靠的系统需要注意哪些方面呢?
结合自己的工作经验,我认为主要是这么几个方面:

所有的原则都是有代价的

世界上没有免费的午餐,借用之前Choice课程老师的一句话,坚持价值观都是有代价的,我们也可以说
坚持原则都是要付出代价的。
这里的代价包括工作量,短期结果,解决问题的难度,带来的项目风险等等,系统的设计者需要做出合理的权衡,付出一定的代价才可能应用上述的原则。

刨根问底,5 whys找到根因

当问题发生时,最重要的事情在于找到问题的根因,只有我们解决了根本的问题,系统才会真正变得健壮起来,否则都只是假象。我们可以用 5 whys 的办法来找到问题的根本原因。

回归测试保障

个人认为自动化回归测试相当于汽车的安全带,我们需要构建覆盖度高的自动化回归测试保障体系,从而更早更好地发现问题,减少对于最终用户的冲击,把问题扼杀在萌芽状态。

让团队养成好的习惯

不管是一个开发者,还是一个开发团队,坚持原则并不是临时起意,而需要成为习惯。只有把原则变成习惯的个人或者团队,才能够真正贯彻这些原则。所以平常工作中某些看起来没有必要的坚持原则,实际上有助于习惯的养成,而当原则成为了团队的习惯,这些原则才能在需要的时候得以实践,获得回报。

不要把fail fast曲解为快速试错

可能有人会认为fail fast就是快速试错,也就是不断尝试,碰到正确的为止。需要强调的是快速失败需要很好的设计和机制保证。

后记

以上是我对于构建可靠系统的思考与实践总结,最近做了一次分享但是感觉没有讲好所以写下来,欢迎讨论和拍砖。

参考文档

  1. Fail Fast
  2. Software Won't Fix Boeing's 'Faulty' Airframe
  3. Gregory Travis's article about Boeing 737 MAX
目录
相关文章
|
2月前
|
缓存 监控 安全
构建高效后端系统的最佳实践
本文将深入探讨如何构建一个高效的后端系统,从设计原则、架构选择到性能优化等方面详细阐述。我们将结合实际案例和理论分析,帮助读者了解在构建后端系统时需要注意的关键点,并提供一些实用的建议和技巧。
42 2
|
1天前
|
存储 负载均衡 监控
如何利用Go语言的高效性、并发支持、简洁性和跨平台性等优势,通过合理设计架构、实现负载均衡、构建容错机制、建立监控体系、优化数据存储及实施服务治理等步骤,打造稳定可靠的服务架构。
在数字化时代,构建高可靠性服务架构至关重要。本文探讨了如何利用Go语言的高效性、并发支持、简洁性和跨平台性等优势,通过合理设计架构、实现负载均衡、构建容错机制、建立监控体系、优化数据存储及实施服务治理等步骤,打造稳定可靠的服务架构。
9 1
|
2月前
|
缓存 NoSQL 安全
构建高效后端系统的关键步骤
本文将探讨如何设计和实现一个高效的后端系统。我们将从系统架构、数据库设计、缓存策略、安全性以及性能优化等多个方面进行详细讲解。通过遵循这些指导原则,你可以构建出一个既灵活又高性能的后端系统,满足现代应用程序的需求。
|
3月前
|
设计模式 关系型数据库 持续交付
构建高效可靠的微服务架构:策略与实践
【7月更文挑战第60天】在现代软件开发领域,微服务架构已经成为一种流行的设计模式,它允许开发者将应用程序拆分成一组小型、松散耦合的服务。本文将深入探讨如何构建一个高效且可靠的微服务系统,涵盖关键设计原则、技术选型以及实践中的注意事项。我们将通过分析具体案例来揭示微服务的优势与挑战,并提供实用的解决方案和最佳实践。
|
2月前
|
运维 监控 数据可视化
高效运维的秘密武器:自动化工具链的构建与实践在当今数字化时代,IT系统的复杂性和规模不断增加,使得传统的手动运维方式难以应对日益增长的业务需求。因此,构建一套高效的自动化工具链成为现代运维的重要任务。本文将深入探讨如何通过自动化工具链提升IT运维效率,确保系统稳定运行,并实现快速响应和故障恢复。
随着企业IT架构的不断扩展和复杂化,传统的手动运维已无法满足业务需求。自动化工具链的构建成为解决这一问题的关键。本文介绍了自动化工具链的核心概念、常用工具及其选择依据,并通过实际案例展示了自动化工具链在提升运维效率、减少人为错误、优化资源配置等方面的显著效果。从监控系统到自动化运维平台,再到持续集成/持续部署(CI/CD)的流程,我们将一步步揭示如何成功实施自动化工具链,助力企业实现高效、稳定、可靠的IT运维管理。
|
3月前
|
Devops 持续交付 开发者
|
4月前
|
存储 设计模式 前端开发
软件架构设计的原则与模式:构建高质量系统的基石
【7月更文挑战第26天】软件架构设计是构建高质量软件系统的关键。遵循高内聚、低耦合、单一职责等设计原则,并灵活运用分层架构、微服务架构、客户端-服务器架构等设计模式,可以帮助我们设计出更加灵活、可扩展、可维护的软件系统。作为开发者,我们应该不断学习和实践这些原则与模式,以提升自己的架构设计能力,为团队和用户提供更加优秀的软件产品。
|
4月前
|
监控 测试技术 持续交付
代码质量评估与改进策略:打造高效、可维护的软件基石
【7月更文挑战第26天】代码质量是软件开发中不可忽视的重要环节。通过有效的评估方法和改进策略,我们可以不断提升代码质量,打造高效、可维护的软件系统。作为开发者,我们应该始终关注代码质量,将其视为自己专业能力的体现和团队成功的保障。只有这样,我们才能在激烈的市场竞争中立于不败之地,为用户提供更加优质的产品和服务。
|
4月前
|
监控 前端开发 UED
软件交付问题之架构让代码组织更有序,如何解决
软件交付问题之架构让代码组织更有序,如何解决
|
4月前
|
存储 Cloud Native 智能网卡
共识协议的技术变迁问题之应用程序开发者应如何利用现有服务降低系统复杂性
共识协议的技术变迁问题之应用程序开发者应如何利用现有服务降低系统复杂性