分布式系统(一)(译)
计算机最基本两个任务
- 存储
- 计算
分布式编程与单机编程一样,旨在解决这些问题。只不过分布式是通过协调多台计算机来解决问题。而采用分布式编程则是由于问题规模庞大到不适合用单机来解决。
分布式系统并不是必须的。只要投入足够的钱和足够研发时间,总能设计出一台完美的计算机。它足够快,可用性高,足以应付任意规模的业务。
然而,预算是有限的。因此,人们不得不做出妥协。在业务规模较小时,升级硬件往往效果更为显著。然而,当业务规模增长到一定程度是,硬件升级就会达到一个临界点。此时,要么单台计算机难以承受业务计算或存储,要么升级需要付出的代价难以承受。这个时候,就需要为业务设计一套分布式系统。
目前,最好的低维护成本方案是采用中等的硬件设备,再通过软件来维持容错性。
计算主要受益于高端硬件,某种程度上它们可以用内存访问来代替缓慢的网络访问。但高端设备的性能依然受限于节点之间大量的通信。
理想上,增加一台新的机器将线程增加分布式系统的性能和容量。但这当然不可能。因为一些额外的负载会随着每一台机器的加入而增加。数据需要复制,计算任务需要协调等等。这是为什么需要学习分布式算法的原因。它对一些特定问题提供了切实有效的解决方案和一些指南,如什么是可能实现的,什么是不可能的以及一个正确实现的最低成本。
本文的重点是普适且具有商业价值的环境中的分布式编程和系统:数据中心。例如,文中不会讨论因奇特的网络环境配置或共享内存设置引起的特殊问题。此外,文章中心在探索系统的设计空间,而不是任何具体细节上的优化-后者是更专业的文章的主题。
可扩展性与其他
大部分问题,在业务规模较小时,都是较易解决的。但同样的问题,在业务增长到一定规模时,都会开始棘手。因此,分布式第一个需要处理的问题就是系统的可扩展性。对可扩展性,非正式定义是当业务规模从小到大时,问题处理不会变得更棘手。正式定义如下
Scalability is the ability of a system, network, or process, to handle a growing amount of work in a capable manner or its ability to be enlarged to accommodate that growth.
对于可扩展性,分布式主要关注以下三方面:
-
数量可扩展性
添加更多的节点,系统运行速度能够随之线性增长;随着数据集的增长,延迟不会增加
-
地理可扩展性
为了缩短对用户查询的相应速度,可取方案是设置多个数据中心(在不同的地方)。同时,也需要采用明智的方式来减少跨数据中心之间的延迟。
-
行政可扩展性
增加更多的节点不会增加系统的行政成本(如,主从节点的比例)。
当然,一个系统的发展都是多方面同时进行的。每个指标仅反应增长的部分方面。
一个可扩展性的系统应该是一个在业务规模增长的期间也能持续满足用户需求的系统。而这体现在两方面:性能和可用性。我们可以通过多种方法来测试这方面。
性能(或延迟)
性能的正式定义如下
Performance is characterized by the amount of useful work accomplished by a computer system compared to the time and resources used.
根据上下文,性能方面的考量主要设计以下三方面
- 低延迟,响应时间短
- 高吞吐(处理工作的速率)
- 资源利用低(译者:这里不是指利用率)(???)
优化这些方面的时候也需要权衡。例如,为了实现高吞吐,一个系统批量处理任务,从而减少操作消耗。这里的权衡的结果就是为了高吞吐而导致延长了单个工作响应时间。
低延迟往往是人们对于一个性能表现最感兴趣的方面。因为这方面会受到物理层面的限制而不是财务限制。相对其他方面,投入更多的资源预算在降低延迟难以起到好的效果。
Latency: The state of being latent; delay, a period between the initiation of something and the occurrence.
Latent: From Latin latens, latentis, present participle of lateo ("lie hidden"). Existing or present but concealed or inactive.
定义强调了延迟就是从事情发生到事件发挥影响或事件可见的这一段时间。
举个例子,假设有这么一种丧尸病毒。延迟就是从一个人感染到他变为僵尸这段时间。即,延迟就是事件发生后但影响还在暗处无法被被观察到的这段时间。
假设一个分布式系统,有这么一个业务:收到客户端的查询请求后,遍历系统内的所有数据并计算一个结果返回。也就说,这是一个数据存储系统,存储着任意数量的数据,并能够基于目前存储数据计算一个确定结果。
result = query(all data in the system)
在这个系统中,延迟并不是旧数据的规模庞大导致的查询响应速度,而是新数据写入后到“可见”的这段时间。
基于这个定义,换句话说,如果上面系统的数据集没有发生变化,那么就不存在“数据潜伏期”。一个系统如果数据没有发生变化,就不会(或不应该)有延迟问题的出现。
如果数据发生变化,分布式系统就有无法克服的数据延迟。这是基于信息传播的限制(理论最快光速)和硬件组件操作成本(RAM和CPU)。
分布式系统最低延迟对查询影响则取决于查询的性质和信息传输的距离。
可用性(和容错)
第二个方面可用性的定义如下
Availability the proportion of time a system is in a functioning condition. If a user cannot access the system, it is said to be unavailable.
分布式允许我们获得某些理想中的特性,而这些在单机系统中是难以实现的。例如,一个单机系统无法容忍错误(如,宕机),因此它只有发生了错误或者没有两个状态。
分布式系统就是在一堆不可靠的组件上,建立一个可靠的系统。
没有冗务能力的组件只能作为系统的底层组件。拥有冗余能力的系统才能容忍部分组件发生错误,更加具有可用性。值得注意的是,冗余有不同场景下指代不同的事务,如组件,服务器,数据中心等等。
按照惯例,可用性公式如下Availability = uptime / (uptime + downtime)
从技术角度上说,可用性可以看做是系统的容错能力。因为一个系统的错误发生概率会随着组件增多而增加。因此,系统应该在这方面做出补偿,避免因为组件的增多导致系统的可靠性降低。
举例
可用性 % 每年允许宕机的时间
90% ("one nine") 可以超过一个月
99% ("two nines") 不能超过4天
99.9% ("three nines") 不能超过9小时
99.99% ("four nines") 少于一小时
99.999% ("five nines") 大概5分钟
99.9999% ("six nines") 大概31秒
系统的可用性在某种意义上,概念比系统自身正常运行更加宽泛。因为一个服务的可用性可能收到,包括网络中断或者公司业务停滞(这个因素无关容错能力,但会影响系统的可用性)的影响。但不可能预知所有影响因素下,在提高可用性方面力所能及做到最好的就是去设计更好的高容错能力。
容错能力意味着什么?
Fault tolerance ability of a system to behave in a well-defined manner once faults occur
容错能力归结为:归纳你能设想到的错误,并设计一套系统或算法去容忍(译者:捕捉并处理)这些错误。你永远无法去容忍你无法预知到的错误。
实现上述优秀特性的阻碍
分布式系统受限于两个物理因素
- 节点的数量
- 节点间的距离
在这些限制的影响下
- 每增加一个节点,系统发生错误的可能性就会增加(减少可用性,增加了行政成本)
- 节点的增加还会增大节点之间交流的成本(性能随规模增大而降低)
- 随着节点间地理上的距离增加,节点间的理论最低延迟也会增加(因为具体的操作降低了性能)
除了上述因为物理延迟导致的现象,其他都是分布式系统设计的可选项。
性能和可用性都是系统做的外部保证。从高的层面来看,可以把这种保证当做系统层面的协议:如果写入新的数据,多快可以在其他任何节点访问新数据?数据写入后,如何确保数据持久?如果将数据拿去计算,多快可以拿到结果?当组件失效,对系统会造成什么影响?
还有一个隐形的标准就是易于理解。如何使保证更加易于理解?当然,没有一个简单的指标是易于理解的。
我很想将“可理解性”置于物理限制之下。 毕竟,对于人们来说,硬件上的局限性在于,我们很难理解比手指更动的事物。 这就是错误和异常之间的区别-错误是错误的行为,而异常是意外的行为。 如果您比较聪明,则可以预期会出现异常情况。
抽象和模型
抽象和模型的作用分别如下。抽象能够移除现实世界中无关解决问题的方面,使事物更加便于管理。模型能够以一种精确的方式去描述事物的关键属性。下一章节将讨论多种模型,如
- 系统模型(同步或者异步)
- 失败模型(崩溃-失败,分区,拜占庭)
- 一致性模型(强一致性,最终一致性)
一个好的抽象能够捕获与多个特定目标相关的因素,使系统易于理解。
人们希望能够对外一个单机系统一样处理一个由多个节点组成的分布式系统,但这种祈愿与现实之间存在一种张力(译者:张力可以理解成现实与愿望之间的矛盾冲突,且更偏向违背人们所想)。通常,分布式系统与单机系统愈加相似,付出的成本就昂贵(如,一种共享内存的分布式抽象)。
一个弱保证的系统有着更多的操作空间,因此可能有着更好的性能,但也可能难以推理。人们更善于推论一个类似于单机系统的分布式,而不是一个多个节点组成的集合。
一个分布式系统通常可以通过暴露更多的内部细节来获取性能。例如,一个列式存储系统,用户(在某种程度上)可以通过推断键值对在系统中的位置来获取查询上的性能优化。隐藏内部细节的系统则易于理解。
几种类型的故障错误使得编写一个像单机系统一样的分布式系统变得困难。网络延迟和网络分区(如,某些节点之间的网络中断)意味着一个系统有时候需要做出困难的抉择。是选择失去一些无法强制执行的关键保证来保证可用性还是保证安全性并在故障发生时拒绝客户端。
CAP理论-下个章节会讨论-涵盖了上述的一部分张力。最后,一个理想的系统应该既可以满足开发者需要(清晰的语义)又可以满足业务需要(可用性/一致性/延迟)
设计技术:分区和复制
在多个节点之间分配数据集的方式非常重要。为了进行任何计算,开发者需要定位数据并进行操作。
这里有两种应用在数据集上的基础技术:分区和复制。分区指可以将数据集拆分到多个节点上,以进行更多的并行计算。复制将数据集(可以是部分数据)复制或缓存在不同的节点上,以缩短客户端和服务器之间的距离并提高容错能力。
下图说明分区和复制之间的不同:数据分区(下面的A和B)是将数据集分成数个不同的集合,而数据复制(下图的C)则是把同份数据复制到多个位置。
分区和复制是分布式处理任何问题时的“万灵药”。
当然,这里的技巧是为你正确的实现选取一种合适的技术;存在多种实现了复制和服务的算法,每一种都有自己的优势和限制。这需要根据你的设计目标来进行选取。
分区
数据分区是分割总数据集为数个互不相交的子数据集;这是为了减少总数据集增长而带来的影响,因为每个切片是总数据量的子集。
- 数据分区通过限制需要操作的数据量和将相关的数据集中在同个切片来提升性能
- 通过允许切片单独失效,数据分区可以在不用牺牲可用性的前提下,允许更多的节点同时失效,提升了可用性。
数据分区的方案定制需要因地制宜。它很难在不知道业务的情况下制定方案。这也是为什么相关内容接下来还会多次关注的原因。
数据分区的内容大部分是根据开发者设想的数据主要访问模式来定义分片和处理因为独立分片带来的限制(例如,)。
复制
数据复制就是同一份数据复制多份到不同的机器上:这能让更多的服务器参与到计算中。
复制既是所有问题的根源,也是解决方案
数据复制-包括复制和重新生成相同数据-是解决延迟的主要方法
- 提升性能方面: 复制可以添加额外的适于新复制数据的计算资源和带宽
- 提升可用性方面:复制通过生成数据的副本,从而在不牺牲可用性前提下,增加了准许范围内的节点失效个数。
数据复制是关于提供额外的缓存,并在需要的地方进行缓存。它也涉及根据一些一致性模型,以某些方式来维持一致性。
复制允许我们同时实现可用性,性能和错误容错。害怕丢失可用性或性能降低?复制数据避免瓶颈或单点失效。计算速度下降?复制数据到多个系统上。I/O缓慢?复制数据到本地缓存来降低延迟或者复制数据到多台机器上来增加吞吐量。
复制数据也是造成许多问题的原因,因为不同机器上的同分数据的独立备份需要维持同步。这意味着需要确保数据复制符合一个一致性模型。
对于一致性模型的选择是十分重要的:一个好的一致性模型提供一个整洁的编码方式(换句话说,关于一致性的保证的相关属性易于推断)和满足业务/设计目标,如高可用性或强一致性。
如果底层数据没有被复制,那么一致性模型只有一个选择:强一致性模型。其他一致性模型会暴露数据复制的内部细节给开发者。然而,弱一致性模型可以提供低延迟和高可用性。而且它并不会难以理解,只是相对复杂。