DDIA 读书笔记(1)可靠性,可扩展性,可维护性
现如今大部分应用程序都是 IO 密集型应用(与之相对的是 CPU 密集型)。这意味着对于大多数业务场景来说,性能瓶颈在于对数据的增删改查。
这些业务场景大致可以分为以下几类:
核心的数据增删改查(数据库)
存储某些代价高昂的操作结果,以提高下次读取的速度(缓存)
允许用任意关键词来查询或筛选结果(搜索索引)
通过消息队列异步处理任务(流处理)
定期处理大批量的数据(批处理)
这些功能都可以归类为数据系统(data system),即处理数据的系统。根据业务场景的不同,这些系统都会有各自的取舍,比如“数据库”会要求持久化,“缓存”重在快速读取但持久化相对不那么可靠。核心的要求则是数据正确性。
随着业务的发展,大部分应用程序都需要组合不同的系统来满足业务场景,随着架构的逐渐复杂,需要有一些评判系统健康程度的标准,即可靠性,可扩展性,可维护性三个方面
可靠性(Reliability)
可靠性十分重要,对于工业系统来说,线上应用不可用或是 bug 可能会导致公共秩序的混乱甚至发生危险事件(核电站,飞行系统等),对于纯线上应用来讲,几分钟的不可用也肯能导致上千万甚至上亿美元的损失。
系统可靠性可以从以下几点进行评判
应用程序的功能始终与设计人员期望的一致
妥善处理意外情况(用户犯错或者按照非预期的方式使用)
在可预期的数据量和负载下保持良好的性能
预防未鉴权的访问和滥用
我们可能会影响系统可靠性的错误称为故障(fault),持续的故障可能会导致失效(failure),而系统应对故障的特性则为容错(fault-tolerant)。故障和失效的区别在于故障通常是系统的一部分产生问题,无法对外提供正常的服务,而失效则是整个系统都挂掉了。
另外故障是多种多样的,防不胜防,因此比起预防错误,我们更加偏向于容忍错误(安全问题除外,预防为主)。在这种情况下,适当提高故障率有助于提高系统的容错能力,最经典的例子就是 Chaos Monkey 了
硬件故障
随着公司业务的发展,业务需要的机器数量越来越多,原本低概率的硬件故障也会变成家常便饭,今天这个机器磁盘挂了,明天那个机器 CPU 挂了,而且这些故障是无法避免的,硬件总会有寿命限制。通常应对这类故障的方案就是冗余,比如磁盘组 raid,机房需要备用电源等,一但发生故障,就需要迅速切换到备用硬件上。如果能在软件层面去做冗余控制是最好的,一个能容忍硬件故障的程序会大大减少运维压力。
软件故障
硬件故障是随机且独立的,因此危害性也是固定的,不会超出预期。相比之下软件故障则更加不稳定,有些只是很小的问题,有些则可能导致大范围故障甚至失效。比如
软件 bug 是可以反复触发的,一个不在预期内的输入可能会导致进程 crash,而这样的输入会不断地出现,导致很多进程都会不断的 crash。
通常一个机器上会跑好几个不同的进程,这些进程会共享硬件资源,如果某个进程突然占用了大量的 cpu,会影响到其他进程的运行。
某些接口突然响应变慢,导致请求堆积,进一步减慢系统的处理速度,恶性循环。
级联故障,一个组件的崩溃可能会导致其他关联组件的崩溃。
应对这些故障没有特别好的通用办法,只能靠平时多做积累,比如增加更细致的监控报警粒度,多考虑极限情况,处理任意依赖的组件超时或挂掉的情况等。
人为错误
人总是会犯错的,然而系统运维或多或少都需要一些人工参与,关于如何尽量减少这些人工错误,通常有以下几种方法
尽量减少人犯错的机会,比如设计合理的抽象、API 或管理后台来限制人工的自由性,但不能限制的太死,因为员工会嫌麻烦并想办法绕开它,平衡的艺术。
想办法把最容易犯错的地方与可能导致失效的地方隔离开。比如提供一个无限逼近真实生产环境的沙盒环境来模拟操作,提前发现问题。
不同环境,不同层面的测试,从测试环境到生产环境,单元测试到自动化测试到人工测试,压力测试,越完善越好。
降低错误带来的影响,比如快速回滚的机制,每次发布都要提前准备回滚策略。
详细且清晰的监控,以便发现问题。
良好的管理实践和培训(不在本书范围内)
可拓展性(Scalability)
一句话描述:可拓展性是系统在面对负载增长时保持合理性能的能力,这里涉及到两个关键词,”负载“ 和 ”性能“。
描述负载
描述系统的负载可以用一些特定的数字来表示,比如聊天室的活跃用户量,web 服务的 qps 等,我们称之为 “负载参数(load parameters)”。当负载参数增加时,系统的性能往往会随之变化。
描述性能
性能表示系统的处理能力,对于 web 服务器来说,性能指的是响应时间(response time)。因为每个请求的响应时间都是不同的,实际应用时通常会看几个统计指标,比如 平均值,p95,p99 等。
应对负载的方法
一般来讲,一个应对当前负载的架构不太可能应付超过 10 倍的负载,所以如果负载增长过快,基本上是要重构了。
系统拓展分为垂直拓展和水平拓展,垂直拓展基本就是冲着提高机器性能去了,对于提高性能是有瓶颈的,而且无限制提高配置成本通常也是难以接受的,所以一般都是优先考虑水平拓展,即加机器。
水平拓展非常容易,低配的机器多加几台十几台非常容易,但是这样的拓展方法对软件要求较高,要么是无状态的服务,要么是有状态且支持分布式的服务。后者在软件层面的成本也是高的可怕。
实际业务中还是需要综合考虑两种方法,以 ROI 为导向来考虑如何拓展会比较合适。另外值得一提的是,可拓展性是基于预测的,它需要人为假设将来的负载,一旦假设成空,那么基于假设的种种努力基本就是白费功夫,因此对于很多业务快速迭代的公司来说,支撑业务迭代往往比保持性能更重要。
可维护性(Maintainability)
后续维护的人力成本也占了软件生命周期的大头,诸如修 bug,偿还技术债,迁移新平台等,想要系统平稳运行,人力维护少不了。然而大家都不喜欢维护历史遗留的代码,这些项目总是各种各样让人不爽的点。
为了避免自己的项目变成历史遗留项目,设计之初就需要考虑好几个原则
可操作性:方便运维团队保持系统稳定运行
简单性:尽可能地消除复杂度,让新人能够快速理解整个系统
可演化性:更简单地对系统进行更改,适配新的需求。
可操作性(Operability)
一个运维友好的系统应该可以满足以下几点:
完善而细致的监控,可以让系统内部的状态直观可见
提供好用的自动化工具,并与系统集成
避免单点问题
详细的文档和操作流程
支持自由配置的默认值
系统可以进行简单的自我修复
行为可预测
简单性(Simplicity)
随着业务的发展和经手人员越来越多,代码就会变得越来越复杂,改动成本也会变的越来越高,遗留下来的技术债也会逐渐累积,并形成恶性循环。
因此让系统变的简单十分重要,简单的代码容易理解,也容易改动,不会产生额外的复杂性。消除复杂度的常用方法是抽象,合理的抽象可以屏蔽很多细节,而且变的通用,一处编写到处使用,可以有效降低复杂度,避免重复造轮子。
可演化性(Evolvability)
可演化性指的是系统拥抱变化的能力,因为需求在频繁变更,系统也需要随之变更。常用的方法有敏捷开发,TDD 等,不过这些面对当前国内互联网单月甚至双周迭代也有些心有余而力不足。
总结
三个属性是衡量系统的通用指标,在合理的成本内控制三者的平衡是每一个工程师都需要去做的事。
版权声明: 本文为 InfoQ 作者【莫黎】的原创文章。
原文链接:【http://xie.infoq.cn/article/bdedb3d8f72d7c85ca73b420d】。文章转载请联系作者。
评论