0%

现代系统通过使控制流发生突变来对这些情况做出反应。一般而言,我们把这些突变称为异常控制流(Exceptional Control Flow,ECF)。

Read more »

链接(Link) 其实就是一个“打包”的过程,它将所有二进制形式的目标文件和系统组件组合成一个可执行文件,这个过程的完成需要链接器(Linker)。随着项目代码越来越多,最终需要将它们分散到多个源文件中,编译器每次只能编译一个源文件,生成一个目标文件,这个时候,链接器除了将目标文件和系统组件组合起来,还需要将编译器生成的多个目标文件组合起来。

Read more »

存储层次是在计算机体系结构下存储系统层次结构的排列顺序。每一层于下一层相比都拥有较高的速度和较低延迟性,以及较小的容量。大部分现今的中央处理器的速度都非常的快。大部分程序工作量需要存储器访问。由于高速缓存的效率和存储器传输位于层次结构中的不同档次,所以实际上会限制处理的速度,导致中央处理器花费大量的时间等待存储器I/O完成工作。

Read more »

计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。虽然系统的具体实现方式随着时间不断变化,但是系统内在的概念却没有改变。所有计算机系统都有相似的硬件和软件组件,它们又执行着相似的功能。

Read more »

到目前为止,本书主要描述的是现状。在这最后一章中,我们将放眼未来,讨论应该是怎么样的:我将提出一些想法与方法,我相信它们能从根本上改进我们设计与构建应用的方式。

对未来的看法与推测当然具有很大的主观性。所以在撰写本章时,当提及我个人的观点时会使用第一人称。您完全可以不同意这些观点并提出自己的看法,但我希望本章中的概念,至少能成为富有成效的讨论出发点,并澄清一些经常被混淆的概念。

第一章概述了本书的目标:探索如何创建可靠可伸缩可维护的应用与系统。这一主题贯穿了所有的章节:例如,我们讨论了许多有助于提高可靠性的容错算法,有助于提高可伸缩性的分区,以及有助于提高可维护性的演化与抽象机制。在本章中,我们将把所有这些想法结合在一起,并在它们的基础上展望未来。我们的目标是,发现如何设计出比现有应用更好的应用 —— 健壮,正确,可演化,且最终对人类有益。

Read more »

第十章中,我们讨论了批处理技术,它读取一组文件作为输入,并生成一组新的文件作为输出。输出是衍生数据(derived data) 的一种形式;也就是说,如果需要,可以通过再次运行批处理过程来重新创建数据集。我们看到了如何使用这个简单而强大的想法来建立搜索索引、推荐系统、做分析等等。

然而,在第十章中仍然有一个很大的假设:即输入是有界的,即已知和有限的大小,所以批处理知道它何时完成输入的读取。例如,MapReduce核心的排序操作必须读取其全部输入,然后才能开始生成输出:可能发生这种情况:最后一条输入记录具有最小的键,因此需要第一个被输出,所以提早开始输出是不可行的。

实际上,很多数据是无界限的,因为它随着时间的推移而逐渐到达:你的用户在昨天和今天产生了数据,明天他们将继续产生更多的数据。除非你停业,否则这个过程永远都不会结束,所以数据集从来就不会以任何有意义的方式“完成”【1】。因此,批处理程序必须将数据人为地分成固定时间段的数据块,例如,在每天结束时处理一天的数据,或者在每小时结束时处理一小时的数据。

日常批处理中的问题是,输入的变更只会在一天之后的输出中反映出来,这对于许多急躁的用户来说太慢了。为了减少延迟,我们可以更频繁地运行处理 —— 比如说,在每秒钟的末尾 —— 或者甚至更连续一些,完全抛开固定的时间切片,当事件发生时就立即进行处理,这就是流处理(stream processing) 背后的想法。

一般来说,“流”是指随着时间的推移逐渐可用的数据。这个概念出现在很多地方:Unix的stdin和stdout,编程语言(惰性列表)【2】,文件系统API(如Java的FileInputStream),TCP连接,通过互联网传送音频和视频等等。

在本章中,我们将把事件流(event stream) 视为一种数据管理机制:无界限,增量处理,与上一章中的批量数据相对应。我们将首先讨论怎样表示、存储、通过网络传输流。在“数据库与流”中,我们将研究流和数据库之间的关系。最后在“流处理”中,我们将研究连续处理这些流的方法和工具,以及它们用于应用构建的方式。

Read more »

现实世界中存在三种不同类型的系统:

服务(在线系统)

服务等待客户的请求或指令到达。每收到一个,服务会试图尽快处理它,并发回一个响应。响应时间通常是服务性能的主要衡量指标,可用性通常非常重要(如果客户端无法访问服务,用户可能会收到错误消息)。

批处理系统(离线系统)

一个批处理系统有大量的输入数据,跑一个作业(job) 来处理它,并生成一些输出数据,这往往需要一段时间(从几分钟到几天),所以通常不会有用户等待作业完成。相反,批量作业通常会定期运行(例如,每天一次)。批处理作业的主要性能衡量标准通常是吞吐量(处理特定大小的输入所需的时间)。

流处理系统(准实时系统)

流处理介于在线和离线(批处理)之间,所以有时候被称为准实时(near-real-time)准在线(nearline) 处理。像批处理系统一样,流处理消费输入并产生输出(并不需要响应请求)。但是,流式作业在事件发生后不久就会对事件进行操作,而批处理作业则需等待固定的一组输入数据。这种差异使流处理系统比起批处理系统具有更低的延迟。

批处理是构建可靠、可伸缩和可维护应用程序的重要组成部分。例如,2004年发布的批处理算法Map-Reduce(可能被过分热情地)被称为“造就Google大规模可伸缩性的算法”【2】。随后在各种开源数据系统中得到应用,包括Hadoop,CouchDB和MongoDB。

与多年前为数据仓库开发的并行处理系统【3,4】相比,MapReduce是一个相当低级别的编程模型,但它使得在商用硬件上能进行的处理规模迈上一个新的台阶。虽然MapReduce的重要性正在下降【5】,但它仍然值得去理解,因为它描绘了一幅关于批处理为什么有用,以及如何做到有用的清晰图景。

实际上,批处理是一种非常古老的计算方式。早在可编程数字计算机诞生之前,打孔卡制表机(例如1890年美国人口普查【6】中使用的霍尔里斯机)实现了半机械化的批处理形式,从大量输入中汇总计算。 Map-Reduce与1940年代和1950年代广泛用于商业数据处理的机电IBM卡片分类机器有着惊人的相似之处【7】。正如我们所说,历史总是在不断重复自己。

Read more »

正如第八章所讨论的,分布式系统中的许多事情可能会出错。处理这种故障的最简单方法是简单地让整个服务失效,并向用户显示错误消息。如果无法接受这个解决方案,我们就需要找到容错的方法—— 即使某些内部组件出现故障,服务也能正常运行。

在本章中,我们将讨论构建容错分布式系统的算法和协议的一些例子。我们将假设第八章的所有问题都可能发生:网络中的数据包可能会丢失、重新排序、重复递送或任意延迟;时钟只是尽其所能地近似;且节点可以暂停(例如,由于垃圾收集)或随时崩溃。

构建容错系统的最好方法,是找到一些带有实用保证的通用抽象,实现一次,然后让应用依赖这些保证。这与第七章中的事务处理方法相同:通过使用事务,应用可以假装没有崩溃(原子性),没有其他人同时访问数据库(隔离),存储设备是完全可靠的(持久性)。即使发生崩溃,竞态条件和磁盘故障,事务抽象隐藏了这些问题,因此应用不必担心它们。

现在我们将继续沿着同样的路线前进,寻求可以让应用忽略分布式系统部分问题的抽象概念。例如,分布式系统最重要的抽象之一就是共识(consensus)就是让所有的节点对某件事达成一致。正如我们在本章中将会看到的那样,要可靠地达成共识,且不被网络故障和进程故障所影响,是一个令人惊讶的棘手问题。

一旦达成共识,应用可以将其用于各种目的。例如,假设你有一个单主复制的数据库。如果主库挂掉,并且需要故障切换到另一个节点,剩余的数据库节点可以使用共识来选举新的领导者。正如在“处理节点宕机”中所讨论的那样,重要的是只有一个领导者,且所有的节点都认同其领导。如果两个节点都认为自己是领导者,这种情况被称为脑裂(split brain),它经常会导致数据丢失。正确实现共识有助于避免这种问题。

在本章后面的“分布式事务与共识”中,我们将研究解决共识和相关问题的算法。但首先,我们首先需要探索可以在分布式系统中提供的保证和抽象的范围。

我们需要了解可以做什么和不可以做什么的范围:在某些情况下,系统可以容忍故障并继续工作;在其他情况下,这是不可能的。我们将深入研究什么可能而什么不可能的限制,既通过理论证明,也通过实际实现。我们将在本章中概述这些基本限制。

分布式系统领域的研究人员几十年来一直在研究这些主题,所以有很多资料—— 我们只能介绍一些皮毛。在本书中,我们没有空间去详细介绍形式模型和证明的细节,所以我们将坚持非正式的直觉。如果你有兴趣,参考文献可以提供更多的深度。

Read more »

最近几章中反复出现的主题是,系统如何处理错误的事情。例如,我们讨论了副本故障切换(“处理节点中断”),复制延迟(“复制延迟问题”)和事务控制(“弱隔离级别”)。当我们了解可能在实际系统中出现的各种边缘情况时,我们会更好地处理它们。

但是,尽管我们已经谈了很多错误,但之前几章仍然过于乐观。现实更加黑暗。我们现在将悲观主义最大化,假设任何可能出错的东西都会出错i。(经验丰富的系统运维会告诉你,这是一个合理的假设。如果你问得好,他们可能会一边治疗心理创伤一边告诉你一些可怕的故事)

i. 除了一个例外:我们将假定故障是非拜占庭式的(请参阅“拜占庭故障”)。

使用分布式系统与在一台计算机上编写软件有着根本的区别,主要的区别在于,有许多新颖和刺激的方法可以使事情出错【1,2】。在这一章中,我们将了解实践中出现的问题,理解我们能够依赖,和不可以依赖的东西。

最后,作为工程师,我们的任务是构建能够完成工作的系统(即满足用户期望的保证),尽管一切都出错了。 在第九章中,我们将看看一些可以在分布式系统中提供这种保证的算法的例子。 但首先,在本章中,我们必须了解我们面临的挑战。

本章对分布式系统中可能出现的问题进行彻底的悲观和沮丧的总结。 我们将研究网络的问题(“不可靠的网络”); 时钟和时序问题(“不可靠的时钟”); 我们将讨论他们可以避免的程度。 所有这些问题的后果都是困惑的,所以我们将探索如何思考一个分布式系统的状态,以及如何推理发生的事情(“知识、真相与谎言”)。

Read more »

在数据系统的残酷现实中,很多事情都可能出错:

  • 数据库软件、硬件可能在任意时刻发生故障(包括写操作进行到一半时)。
  • 应用程序可能在任意时刻崩溃(包括一系列操作的中间)。
  • 网络中断可能会意外切断数据库与应用的连接,或数据库之间的连接。
  • 多个客户端可能会同时写入数据库,覆盖彼此的更改。
  • 客户端可能读取到无意义的数据,因为数据只更新了一部分。
  • 客户之间的竞争条件可能导致令人惊讶的错误。

为了实现可靠性,系统必须处理这些故障,确保它们不会导致整个系统的灾难性故障。但是实现容错机制工作量巨大。需要仔细考虑所有可能出错的事情,并进行大量的测试,以确保解决方案真正管用。

数十年来,事务(transaction) 一直是简化这些问题的首选机制。事务是应用程序将多个读写操作组合成一个逻辑单元的一种方式。从概念上讲,事务中的所有读写操作被视作单个操作来执行:整个事务要么成功(提交(commit))要么失败(中止(abort)回滚(rollback))。如果失败,应用程序可以安全地重试。对于事务来说,应用程序的错误处理变得简单多了,因为它不用再担心部分失败的情况了,即某些操作成功,某些失败(无论出于何种原因)。

和事务打交道时间长了,你可能会觉得它显而易见。但我们不应将其视为理所当然。事务不是天然存在的;它们是为了简化应用编程模型而创建的。通过使用事务,应用程序可以自由地忽略某些潜在的错误情况和并发问题,因为数据库会替应用处理好这些。(我们称之为安全保证(safety guarantees))。

并不是所有的应用都需要事务,有时候弱化事务保证、或完全放弃事务也是有好处的(例如,为了获得更高性能或更高可用性)。一些安全属性也可以在没有事务的情况下实现。

Read more »