在当今技术发展的情景下,很多应用是数据密集型,而非计算密集型。CPU已经几乎不再是应用程序的瓶颈,数据的量级、复杂度以及变化的速度逐渐成为最大的问题。
一个数据密集型应用通常是由一些其他标准组件构成的,例如许多应用都需要:
- 存储数据以便后续使用(数据库)
- 记录一些代价昂贵的操作结果,以便加速数据读取(缓存)
- 允许用户根据不同方式搜索(搜索引擎)
- 将消息发送到另一进程以便异步处理(流处理)
- 周期性的处理一些收集的数据(批处理) 他们听起来如此简单是由于我们通常并不需要思考很多,而是直接使用就好了,就像当我们构建一个应用程序时,多数的工程师并不需要设计一个数据存储引擎,数据库就可以很好地实现这一点。
然而现实并非如此,由于很多应用的需求场景不同,有很多不同特性的数据库、搜索引擎等等。当构建应用时,我们仍然需要找出哪些工具和方法是合适的,有时为了完成一个需求将这些工具综合起来使用也是比较难的。
本书主要讲的就是这些数据系统使用的原则和实践,以及如何使用他们构造一个数据密集型应用。
本章我们将从一切的基础:可靠性、可扩展性、可维护性说起,讲解他们的含义,接下来将一层一层的说明当处理数据密集型应用的时候,选择不同设计决策应该考虑些什么。
关于数据系统的思考
我们通常把数据库、队列、缓存等认为是不同类型的工具,即使数据库和消息队列某些情景下可以做同样的事情,然而他们有不同的访问模式,这意味着他们的特性不同,并且底层有不同的实现。
那么为什么我们把他们统称为数据系统呢?
很多数据存储的新工具今年有融合的趋势,他们都在许多不同场景下进行了相应的优化,不再只适用于某一个场景。例如基于内存的redis可以用来做消息队列,而有持久性保证的kafka也可以做消息队列,两类工具的边界正在模糊。
其次,很多应用现在都有不同需求,单个工具已经无法再满足其数据处理和存储的需求,通常是把这些复杂的需求拆成单个的步骤,对每个步骤使用最适合它的工具来处理。
比如在一个应用系统中,你可以有一个数据库比如mysql,然后可以在它之上构建一个memcached缓存层加速读取,搜索方面可以用ElasticSearch来专门支持,然后通过代码来把这些工具糅合到一起。
当你把这些工具合在一起对外提供及服务的时候,服务接口API通常会以一个client封装隐藏这些内部的实现,而你需要保证这套复杂的系统中各部分的一致性等等,你不仅是一个程序开发者,还是一个数据系统的设计者。
当你进行设计时,会遇到很多问题。比如内部步骤出错时如何确保数据完整、正确,当系统部分模块失败降级时如何确保整体服务性能依然是最优的,以及随着规模的增长,你的系统是否是可扩展的?
本书中我们聚焦于多数软件系统中所考虑的三个最重要的问题:
- 可靠性。即使部分系统故障,整个系统也应该正确的工作
- 可扩展性。随着系统数据量级、复杂度增加,应该有合理的方式进行扩容
- 可维护性。随着时间推移,会有不同的人来维护这套系统,他们应该能快速上手系统
这三个特征看起来有些模糊,但是接下来的部分将会怼他们进行详细的阐述,我们会探讨如何使用不同的技术、架构和算法实现这些目标。
可靠性
每个人对可靠性都有一个心里的初步想法,比如对一个软件来说,它应该包括:
- 程序产生用户期望的输出
- 可以容忍用户意外的错误输入
- 在正常的负载下性能足够好
- 避免未鉴权的滥用
综上,我们可以简单地定义为:即使出了什么错,系统也可以正常的运行。
系统能够预见并且对错误进行处理我们称之为容错性或弹性。但前一个词有点误导,我们很难把系统所有的错误都能预知并处理。所以我们只能尽量让系统的容错性更好一些。
要注意故障fault和失败failure是不同的,故障通常是系统某个组件异常,而失败是整个服务都无法对用户提供服务。我们不可能把故障降到0,但是我们可以通过一些机制使其尽量降低,以避免失败的发生。
我们可以通过制造一些错误或者混乱,来观察系统的运行是否正常,并且想办法在这种异常情况下减少系统错误,以此来提高系统的可靠性。(这个感觉和我们日常的故障演练、混沌工程有点像)
通常,我们是增加对错误的容忍度,而不是去预防他,但是在某些情况下预防优于容忍,比如安全相关的问题,我们应该优先预防,否则安全问题一旦发生,再抢救就很难了。
硬件故障
当我们说起系统故障来,首先想到的就是硬件故障,比如磁盘损坏、内存故障等等,凡是在数据中心工作过的都很清楚在海量集群中这些故障一直都有发生。假设一个存储集群有10000个磁盘,平均每天会坏一个。
那么我们想到的第一个解决这个问题的方法就是通过对单个机器或者存储的冗余来减少系统的故障率,比如通过RAID技术组织磁盘,给服务器增加备用电源、可热插拔的CPU等等,当一个组件故障时,冗余的组件可以快速替换。这种方式虽然不能完全的避免硬件问题,但是却可以较好的保持一个机器持续不间断的运行数年。
直到最近,硬件的冗余才满足多数应用的需求,因为它使单机故障的几率大大降低。只要你可以快速的在新机器上恢复一个备份,对多数应用来说所造成的故障都不是灾难性的。因此,多机冗余只是少数绝对需要高可靠的程序所需要的。
然而数据量级和应用对计算的需求增加,更多应用开始使用大量的机器集群,这增加了硬件故障的几率。而且在一些诸如AMS的云平台上,一个虚拟机节点没有任何预警就挂了是件很正常的事情,因为这些平台主要关注的点是灵活性和弹性,而不是单机可靠性。
因此,一个通过软件的容错技术或者硬件冗余技术来实现的容错系统正在逐步发展。这种系统也有一个操作上的优势,那就是单机系统如果要停机维护服务肯定要停止,而一个可容错的系统可以单个节点单个节点的处理,无需停止对外服务。
软件错误
我们通常认为,硬件的故障是随机的,并且相对独立,比如一个机器的磁盘故障并不意味着其他的机器也要故障了。或许那里是有那么一点微弱的联系,但是大量的硬件问题同时发生还是一件几乎不可能出现的事情。
另一类故障则是系统内的系统故障。这种故障很难预测,因为他们通常是有节点相关性的,因此,与不相关的硬件故障相比,他们往往导致更多的系统故障。例如:
- 一个软件的bug导致当给一个错误的输入时,一定会导致应用挂掉
- 一个过载的进程用尽了共享资源,比如CPU时钟、内存、磁盘空间或者网络带宽
- 一个系统所依赖的服务超时,变的无响应,或者返回崩溃的响应
- 级联失败,一个小的错误触发了另一个错误,最终导致了一堆错误 的发生
这种软件bug通常在很长时间以后在某个特定的场景才能发现。在这些场景下,我们发现该软件正在对其环境做出某种假设,尽管该假设通常是正确的,但由于某些原因最终会停止正确。
对软件中出现的系统故障并没有快速解决的方法。然而有很多小的事情可以帮助你减少这些问题:仔细思考边界条件以及系统的交互;完备的测试;进程隔离;设计时就允许进程崩溃和重启;在生产环境中测试、监控、分析系统的行为。如果一个系统想要提供一些保证,(比如在消息队列中入和出的消息数应该是相同的),那么可以在运行时不断自检,并且当发现差异时进行告警。
人的错误
待续...