0%

可靠&可扩展&可维护的应用系统

可靠性

什么叫作系统的可靠性

原文:

可靠性大致意味着:即使发生了某种错误,系统仍可以继续正常工作

一般,用于测试目的,可以故意提高故障发生概率,例如通过随机杀死某个进程,来确保系统仍保持健壮。

硬件故障

例如:磁盘崩溃、内存故障、网络故障、机房停电等等。通常对于硬件故障,都会为硬件添加冗余来减少系统故障率。例如,对磁盘配置RAID,服务器配备双电源,热拔插CPU,数据中心添加备用电源、发电机等。当一个组件发生故障,冗余组件可以快速接管,之后再更换失效的组件。

软件错误

软件故障一般很难事先预料,因为导致软件故障的bug通常会长时间处于引而不发的状态,直到碰到特定的触发条件。软件问题有时没有快速解决办法,而只能仔细考虑很多细节,包括认真检查依赖的假设条件与系统之间交互,进行全面的测试,进行隔离,运行进程崩溃并自动重启。

人为错误

有一项针对大型互联网服务的调查发现,运维者的配置错误居然是系统下线的首要原因,而硬件问题(服务器或网络)仅在10%~25%的故障中有影响。对于人为故障,一般来说可以通过结合以下多种方法:

  • 想办法分离最容易出错的地方、容易引发故障的接口,对重要接口进行多重条件检查
  • 充分的测试:从单元测试到全系统的集成测试到手动测试
  • 提供快速的恢复机制,例如快速回滚配置改动
  • 设置详细而清晰的监控子系统,包括性能指标和错误率。
  • 推行管理流程并加以培训

我对可靠性的理解

系统的可靠性不是指让系统的任何一个部分在任何时刻都不发生故障,这是不可能的,随机事件的发生不可控的,系统的可靠性是指在系统的某个部分发生了故障时,能够尽快的从故障中恢复过来,同时尽量把由于故障导致的损失降到最小。并且在故障期间不会由于一个故障,像多米诺骨牌一样,引发一连串的其他故障。

可扩展性

对于可扩展性,一个重要讨论的内容是:如果系统以某种方式增长,我们应对增长的措施有哪些,我们该如何添加计算资源来处理额外的负载。

描述负载

首先,我们需要简洁地描述系统当前的负载,这样才能更好地讨论后续增长的问题。负载可以用称为负载参数的若干数字来描述,参数的最佳选择取决于系统的体系架构,它可能是web服务器的每秒请求处理次数,数据库中写入的比例,缓存命中率等。

描述性能

描述系统负载之后,接下来设想如果负载增加将会发生什么。一般有有两种考虑方式:

  • 负载增加,但系统资源(如CPU、内存、网络带宽等)保存不变,系统性能会发生什么变化?
  • 负载增加,如果要保持性能不变,需要增加多少资源?
下面对一个描述性能的重要指标进行额外讨论——响应时间

响应时间

一般来说,对服务进行请求,每次的响应时间都是不同的,而且响应时间可能变化很大。因此。最好不要将响应时间视为一个固定的数字,而应该视为一种数值分布。

平均响应时间与相关百分位数

我们经常考察的是服务请求的平均响应时间(算数平均值)。但是,如果想知道更典型的响应时间,平均值并不是合适的指标。因为它掩盖了一些信息,无法告诉有多少用户实际经历了多少延迟,因此最好使用百分位数</font>。中位数指标非常适合描述多少用户需要等待多长的时间,一半的用户请求的服务时间少于中位数响应时间,另一半则多用于中位数的时间。因此中位数也称为50百分位数,简写为p50。有些时候还有需要关注更大的百分位数,例如常见的p95、p99甚至p999(99.9百分位数)。采用较高的响应时间百分位数很重要,因为它们直接影响用户的总体服务体验(长尾效应)。

排队延迟往往在高百分数响应时间中影响很大,由于服务器并行处理的请求优先(例如,CPU内核数的限制),正在处理的少数请求可能会阻挡后续请求,这种情况有时被称为队头阻塞。即使后续请求可能处理很简单,但它阻塞在等待先前请求的完成,客户端将会观察到极慢的响应时间。因此,很重要的一点是要在客户端来测量响应时间。

因此,当测试系统的能支持的负载时,负载生成端要独立于响应时间来持续发送请求。如果客户端在发送请求之前总是等待先前请求的完成,就会在测试中人为地缩短了服务器端的累计排队深度,这就带来了测试偏差。

应对负载增加的方法

一般来说,针对特定级别负载而设计的架构不太可能应付超出目标10倍的实际负载。如果目标服务处于快速增长阶段,那么需要认真考虑每增加一个数量级的负载,架构应该如何设计。

对于未发生数量级变化的负载,更多的是在垂直拓展(升级到更强大的机器)和水平拓展(增加更多的机器实例)进行选择。通常,在单台机器上运行的系统通常更简单,然而高端机器可能非常昂贵,且扩展水平有限,最终往往还是无法避免需要水平扩展。另外,把无状态服务分布然后扩展至多台机器相对比较容易,而有状态服务从单个节点扩展到分布式多机环境的复杂性会大大增加。

对于特定应用来说,扩展能力好的架构通常会做出某些假设,然后有针对性地优化设计,如哪些操作是最频繁的,那些负载是少数情况。如果这些假设最终发现是错误的,那么在可扩展性上做的努力就白费了,甚至会出现与设计预期完全相反的情况。对于早期的初创公司或者尚未定型的产品,快速迭代产品功能往往比投入精力来应对不可知的扩展性更为重要。

可维护性

软件的大部分成本并不在最初的开发阶段,而在于整个生命周期内持续的投入,包括缺陷修复、增加新功能、适配新需求等等。但是,许多开发人员根本不喜欢维护这些遗留系统,例如修复他人埋下的错误,或者使用过时的开发平台。所以,换个角度,我们应该从软件设计时开始考虑,尽可能减少维护期间的麻烦,一般我们可以考虑以下几点:

  • 简单性:简化系统复杂性,使新工程师能够轻松理解系统
  • 可演化性:后续工程师能够轻松地对系统进行改进,并根据需求变化将其适配其他场景