《Designing Data-Intensive Application》01数据系统的基石-可靠性/可扩展性/可维护性

序言

如果近几年从业于软件工程,特别是服务器端和后端系统开发,那么您很有可能已经被大量关于数据存储和处理的时髦词汇轰炸过了: NoSQL! 大数据! Web-Scale! 分片! 最终一致性! ACID! CAP定理! 云服务! MapReduce! 实时!

数据密集型应用(data-intensive applications)正在通过使用这些技术进步来推动可能性的边界。一个应用被称为数据密集型的,数据是其主要挑战(数据量,数据复杂度或数据变化速度)——与之相对的是计算密集型,即处理器速度是其瓶颈。

帮助数据密集型应用存储和处理数据的工具与技术,正迅速地适应这些变化。新型数据库系统(NoSQL)已经备受关注,而消息队列,缓存,搜索索引,批处理和流处理框架以及相关技术也非常重要。很多应用组合使用这些工具与技术。

本书的目标是帮助您在飞速变化的数据处理和数据存储技术大观园中找到方向。本书并不是某个特定工具的教程,也不是一本充满枯燥理论的教科书。相反,我们将看到一些成功数据系统的样例:许多流行应用每天都要在生产中会满足可扩展性、性能、以及可靠性的要求,而这些技术构成了这些应用的基础。

我们将深入这些系统的内部,理清它们的关键算法,讨论背后的原则和它们必须做出的权衡。在这个过程中,我们将尝试寻找思考数据系统的有效方式――不仅关于它们如何工作,还包括它们为什么以这种方式工作,以及哪些问题是我们需要问的。

阅读本书后,你能很好地决定哪种技术适合哪种用途,并了解如何将工具组合起来,为一个良好应用架构奠定基础。本书并不足以使你从头开始构建自己的数据库存储引擎,不过幸运的是这基本上很少有必要。你将获得对系统底层发生事情的敏锐直觉,这样你就有能力推理它们的行为,做出优秀的设计决策,并追踪任何可能出现的问题。

本书的目标读者

如果以下任意一条对您为真,你会发现这本书很有价值:

  • 您想了解如何使数据系统可扩展,例如,支持拥有数百万用户的Web或移动应用。
  • 您需要提高应用程序的可用性(最大限度地减少停机时间),保持稳定运行。
  • 您正在寻找使系统在长期运行过程易于维护的方法,即使系统规模增长,需求与技术也发生变化。
  • 您对事物的运作方式有着天然的好奇心,并且希望知道一些主流网站和在线服务背后发生的事情。

本书涉及的领域

我们主要关注的是数据系统的架构(architecture),以及它们被集成到数据密集型应用中的方式。本书没有足够的空间覆盖部署,运维,安全,管理等领域――这些都是复杂而重要的主题,仅仅在本书中用粗略的注解讨论这些对它们很不公平。每个领域都值得用单独的书去讲。

本书中描述的许多技术都被涵盖在大数据(Big Data)这个时髦词的范畴中。然而"大数据"这个术语被滥用,缺乏明确定义,以至于在严肃的工程讨论中没有用处。这本书使用歧义更小的术语,如"单节点"之于"分布式系统",或"在线/交互式系统“之于"离线/批处理系统”。

本书纲要

本书分为三部分:
1.在第一部分中,我们会讨论设计数据密集型应用所赖的基本思想。
我们从第1章开始,讨论我们实际要达到的目标:可靠性,可扩展性和可维护性,我们该如何思考这些概念以及如何实现它们。
在第2章中,我们比较了几种不同的数据模型和查询语言,看看它们如何适用于不同的场景。
在第3章中将讨论存储引擎:数据库如何在磁盘上摆放数据,以便能高效地再次找到它。
第4章转向数据编码(序列化),以及随时间演化的模式。

2.在第二部分中,我们从讨论存储在一台机器上的数据转向讨论分布在多台机器上的数据。这对于可扩展性通常是必需的,但带来了各种独特的挑战。我们首先讨论复制〈第5章),分区/分片(第6章)和事务(第7章)。然后我们将探索关于分布式系统问题的更多细节(第8章),以及在分布式系统中实现一致性与共识意味着什么(第9章)。

3.在第三部分中,我们讨论那些从其他数据集衍生出一些数据集的系统。衍生数据经常出现在异构系统中:当没有单个数据库可以把所有事情都做的很好时,应用需要集成几种不同的数据库,缓存,索引等。在第10章中我们将从一种衍生数据的批处理方法开始,然后在此基础上建立在第11章中讨论的流处理。最后,在第12章中,我们将所有内容汇总,讨论在将来构建可靠,可伸缩和可维护的应用程序的方法。

参考文献与延伸阅读

本书中讨论的大部分内容已经在其它地方以某种形式出现过了- -会议演示文稿,研究论文,博客文章,代码,BUG跟踪器,邮件列表,以及工程习惯中。本书总结了不同来源资料中最重要的想法,并在文本中包含了指向原始文献的链接。如果你想更深入地探索一个领域,那么每章末尾的参考文献都是很好的资源,其中大部分可以免费在线获取。

致谢

本书融合了学术研究和工业实践的经验,融合并系统化了大量其他人的想法与知识。在计算领域,我们往往会被各种新鲜花样所吸引,但我认为前人完成的工作中,有太多值得我们学习的地方了。本书有800多处引用︰文章,博客,讲座,文档等,对我来说这些都是宝贵的学习资源。我非常感谢这些材料的作者分享他们的知识。

第一部分 数据系统的基石

第一章:可靠性,可扩展性,可维护性

现今很多应用程序都是数据密集型(data-intensive)的,而非计算密集型(compute-intensive)的。因此CPU很少成为这类应用的瓶颈,更大的问题通常来自数据量、数据复杂性、以及数据的变更速度。

本书将是一趟关于数据系统原理、实践与应用的旅程,并讲述了设计数据密集型应用的方法。我们将探索不同工具之间的共性与特性,以及各自的实现原理。

本章将从我们所要实现的基础目标开始:可靠、可扩展、可维护的数据系统。我们将澄清这些词语的含义,概述考量这些目标的方法。并回顾一些后续章节所需的基础知识。在接下来的章节中我们将抽丝剥茧,研究设计数据密集型应用时可能遇到的设计决策。

关于数据系统的思考

我们通常认为,数据库、消息队列、缓存等工具分属于几个差异显著的类别。虽然数据库和消息队列表面上有一些相似性- -它们都会存储一段时间的数据- -但它们有迥然不同的访问模式,这意味着迥异的性能特征和实现手段。

那我们为什么要把这些东西放在数据系统(data system)的总称之下混为一谈呢?
近些年来,出现了许多新的数据存储工具与数据处理工具。它们针对不同应用场景进行优化,因此不再适合生硬地归入传统类别[1]。类别之间的界限变得越来越模糊,例如︰数据存储可以被当成消息队列用(Redis),消息队列则带有类似数据库的持久保证(Apache Kafka)。

其次,越来越多的应用程序有着各种严格而广泛的要求,单个工具不足以满足所有的数据处理和存储需求。取而代之的是,总体工作被拆分成一系列能被单个工具高效完成的任务,并通过应用代码将它们缝合起来。

例如,如果将缓存(应用管理的缓存层,Memcached或同类产品)和全文搜索(全文搜索服务器,例如Elasticsearch或Solr)功能从主数据库剥离出来,那么使缓存/索引与主数据库保持同步通常是应用代码的责任。图1-1给出了这种架构可能的样子(细节将在后面的章节中详细介绍)。
在这里插入图片描述
当你将多个工具组合在一起提供服务时,服务的接口或应用程序编程接口(API,Application Programming Interface)通常向客户端隐藏这些实现细节。现在,你基本上已经使用较小的通用组件创建了一个全新的、专用的数据系统。这个新的复合数据系统可能会提供特定的保证,例如︰缓存在写入时会作废或更新,以便外部客户端获取一致的结果。现在你不仅是应用程序开发人员,还是数据系统设计人员了。

设计数据系统或服务时可能会遇到很多棘手的问题,例如︰当系统出问题时,如何确保数据的正确性和完整性?当部分系统退化降级时,如何为客户提供始终如一的良好性能?当负载增加时,如何扩容应对?什么样的API才是好的API?

影响数据系统设计的因素很多,包括参与人员的技能和经验、历史遗留问题、系统路径依赖、交付时限、公司的风险容忍度、监管约束等,这些因素都需要具体问题具体分析。
本书着重讨论三个在大多数软件系统中都很重要的问题:
可靠性(Reliability)
可扩展性(Scalability)
可维护性(Maintainability)

可靠性

人们对于一个东西是否可靠,都有一个直观的想法。人们对可靠软件的典型期望包括:

  • 应用程序表现出用户所期望的功能。
  • 允许用户犯错,允许用户以出乎意料的方式使用软件。
  • 在预期的负载和数据量下,性能满足要求。
  • 系统能防止未经授权的访问和滥用。

如果所有这些在一起意味着"正确工作",那么可以把可靠性粗略理解为"即使出现问题,也能继续正确工作”。

造成错误的原因叫做故障(fault),能预料并应对故障的系统特性可称为容错(fault-tolerant)或韧性(resilient)。

注意故障(fault)不同于失效(failure)[2]。故障通常定义为系统的一部分状态偏离其标准,而失效则是系统作为一个整体停止向用户提供服务。故障的概率不可能降到零,因此最好设计容错机制以防因故障而导致失效。本书中我们将介绍几种用不可靠的部件构建可靠系统的技术。

反直觉的是,在这类容错系统中,通过故意触发来提高故障率是有意义的,例如:在没有警告的情况下随机地杀死单个进程。许多高危漏洞实际上是由糟糕的错误处理导致的【3】,因此我们可以通过故意引发故障来确保容错机制不断运行并接受考验,从而提高故障自然发生时系统能正确处理的信心。Netflix公司的Chaos Monkey 【4】就是这种方法的一个例子。

尽管比起阻止错误(prevent error),我们通常更倾向于容忍错误。但也有预防胜于治疗的情况(比如不存在治疗方法时)。安全问题就属于这种情况。例如,如果攻击者破坏了系统,并获取了敏感数据,这种事是撤销不了的。但本书主要讨论的是可以恢复的故障种类,正如下面几节所述。

硬件故障

当想到系统失效的原因时,硬件故障(hardware faults)总会第一个进入脑海。硬盘崩溃、内存出错、机房断电、有人拔错网线……任何与大型数据中心打过交道的人都会告诉你︰一旦你拥有很多机器,这些事情总会发生!

据报道称,硬盘的平均无故障时间(MTTF, mean time to failure)约为10到50年 【5】 【6】。因此从数学期望上讲,在拥有10000个磁盘的存储集群上,平均每天会有1个磁盘出故障。

为了减少系统的故障率,第一反应通常都是增加单个硬件的冗余度,例如:磁盘可以组建RAID,服务器可能有双路电源和热插拔CPU,数据中心可能有电池和柴油发电机作为后备电源,某个组件挂掉时冗余组件可以立刻接管。这种方法虽然不能完全防止由硬件问题导致的系统失效,但它简单易懂,通常也足以让机器不间断运行很多年。

直到最近,硬件冗余对于大多数应用来说已经足够了,它使单台机器完全失效变得相当罕见。只要你能快速地把备份恢复到新机器上,故障停机时间对大多数应用而言都算不上灾难性的。只有少量高可用性至关重要的应用才会要求有多套硬件冗余。

但是随着数据量和应用计算需求的增加,越来越多的应用开始大量使用机器,这会相应地增加硬件故障率。此外在一些云平台(如亚马逊网络服务(AWS, Amazon Web Services) )中,虚拟机实例不可用却没有任何警告也是很常见的【7】,因为云平台的设计就是优先考虑灵活性(flexibility)和弹性(elasticity) ,而不是单机可靠性。

如果在硬件冗余的基础上进一步引入软件容错机制,那么系统在容忍整个(单台)机器故障的道路上就更进一步了。这样的系统也有运维上的便利,例如:如果需要重启机器(例如应用操作系统安全补丁),单服务器系统就需要计划停机。而允许机器失效的系统则可以一次修复一个节点,无需整个系统停机。

软件错误

另一类错误是内部的系统性错误(systematic error)【7】。这类错误难以预料,而且因为是跨节点相关的,所以比起不相关的硬件故障往往可能造成更多的系统失效【5】。例子包括:

  • 接受特定的错误输入,便导致所有应用服务器实例崩溃的BUG。例如2012年6月30日的闰秒,由于Linux内核中的一个错误,许多应用同时挂掉了。
  • 失控进程会占用一些共享资源,包括CPU时间、内存、磁盘空间或网络带宽。
  • 系统依赖的服务变慢,没有响应,或者开始返回错误的响应。
  • 级联故障,一个组件中的小故障触发另一个组件中的故障,进而触发更多的故障【10】。

虽然软件中的系统性故障没有速效药,但我们还是有很多小办法,例如︰仔细考虑系统中的假设和交互;彻底的测试;进程隔离;允许进程崩溃并重启;测量、监控并分析生产环境中的系统行为。如果系统能够提供一些保证(例如在一个消息队列中,进入与发出的消息数量相等),那么系统就可以在运行时不断自检,并在出现差异(discrepancy)时报警【12】。

人为错误

设计并构建了软件系统的工程师是人类,维持系统运行的运维也是人类。即使他们怀有最大的善意,人类也是不可靠的。举个例子,一项关于大型互联网服务的研究发现,运维配置错误是导致服务中断的首要原因,而硬件故障(服务器或网络)仅导致了10-25%的服务中断【13】。

尽管人类不可靠,但怎么做才能让系统变得可靠?最好的系统会组合使用以下几种办法:

  • 以最小化犯错机会的方式设计系统。例如,精心设计的抽象、API和管理后台使做对事情更容易,搞砸事情更困难。但如果接口限制太多,人们就会忽略它们的好处而想办法绕开。很难正确把握这种微妙的平衡。
  • 将人们最容易犯错的地方与可能导致失效的地方解耦(decouple)。特别是提供一个功能齐全的非生产环境沙箱(sandbox),使人们可以在不影响真实用户的情况下,使用真实数据安全地探索和实验。
  • 在各个层次进行彻底的测试【3】,从单元测试、全系统集成测试到手动测试。自动化测试易于理解,已经被广泛使用,特别适合用来覆盖正常情况中少见的边缘场景(corner case)。
  • 允许从人为错误中简单快速地恢复,以最大限度地减少失效情况带来的影响。例如,快速回滚配置变更,分批发布新代码〈以便任何意外错误只影响一小部分用户〉,并提供数据重算工具(以备旧的计算出错)。
  • 配置详细和明确的监控,比如性能指标和错误率。在其他工程学科中这指的是遥测
    (telemetry)。(一旦火箭离开了地面,遥测技术对于跟踪发生的事情和理解失败是至关重要的。)监控可以向我们发出预警信号,并允许我们检查是否有任何地方违反了假设和约束。当出现问题时,指标数据对于问题诊断是非常宝贵的。
  • 良好的管理实践与充分的培训—―一个复杂而重要的方面,但超出了本书的范围。
可靠性有多重要?

可靠性不仅仅是针对核电站和空中交通管制软件而言,我们也期望更多平凡的应用能可靠地运行。商务应用中的错误会导致生产力损失(也许数据报告不完整还会有法律风险),而电商网站的中断则可能会导致收入和声誉的巨大损失。

即使在“非关键"应用中,我们也对用户负有责任。试想一位家长把所有的照片和孩子的视频储存在你的照片应用里【15】。如果数据库突然损坏,他们会感觉如何?他们可能会知道如何从备份恢复吗?

在某些情况下,我们可能会选择牺牲可靠性来降低开发成本(例如为未经证实的市场开发产品原型)或运营成本(例如利润率极低的服务),但我们偷工减料时,应该清楚意识到自己在做什么。

可扩展性

可扩展性(Scalability)是用来描述系统应对负载增长能力的术语。但是请注意,这不是贴在系统上的一维标签︰说"X可扩展"或"Y不可扩展"是没有任何意义的。相反,讨论可扩展性意味着考虑诸如“如果系统以特定方式增长,有什么选项可以应对增长?“和"如何增加计算资源来处理额外的负载?“等问题。

描述负载

在讨论增长问题〈如果负载加倍会发生什么?)前,首先要能简要描述系统的当前负载。负载可以用一些称为负载参数(load parameters)的数字来描述。参数的最佳选择取决于系统架构,它可能是每秒向Web服务器发出的请求、数据库中的读写比率、聊天室中同时活跃的用户数量、缓存命中率或其他东西。除此之外,也许平均情况对你很重要,也许你的瓶颈是少数极端场景。

为了使这个概念更加具体,我们以推特在2012年11月发布的数据【16】为例。推特的两个主要业务是:
发布推文
用户可以向其粉丝发布新消息(平均4.6k请求/秒,峰值超过12k请求/秒)。
主页时间线
用户可以查阅他们关注的人发布的推文(300k请求/秒)。

处理每秒12,000次写入(发推文的速率峰值)还是很简单的。然而推特的扩展性挑战并不是主要来自推特量,而是来自扇出(fan-out)——每个用户关注了很多人,也被很多人关注。

大体上讲,这一对操作有两种实现方式。
1.发布推文时,只需将新推文插入全局推文集合即可。当一个用户请求自己的主页时间线时,首先查找他关注的所有人,查询这些被关注用户发布的推文并按时间顺序合并。在如图1-2所示的关系型数据库中,可以编写这样的查询:

SELECT 	tweets.*, users.*
 FROM 	tweets
 JOIN 	users ON tweets.sender_id = users.id
 JOIN 	follows ON follows.followee_id = users.id
 WHERE 	follows.follower_id = current_user

在这里插入图片描述
2.为每个用户的主页时间线维护一个缓存,就像每个用户的推文收件箱(图1-3)。当一个用户发布推文时,查找所有关注该用户的人,并将新的推文插入到每个主页时间线缓存中。因此读取主页时间线的请求开销很小,因为结果已经提前计算好了。
在这里插入图片描述
推特的第一个版本使用了方法1,但系统很难跟上主页时间线查询的负载。所以公司转向了方法2,方法2的效果更好,因为发推频率比查询主页时间线的频率几乎低了两个数量级,所以在这种情况下,最好在写入时做更多的工作,而在读取时做更少的工作。

然而方法2的缺点是,发推现在需要大量的额外工作。平均来说,一条推文会发往约75个关注者,所以每秒4.6k的发推写入,变成了对主页时间线缓存每秒345k的写入。但这个平均值隐藏了用户粉丝数差异巨大这一现实,一些用户有超过3000万的粉丝,这意味着一条推文就可能会导致主页时间线缓存的3000万次写入!及时完成这种操作是一个巨大的挑战――推特尝试在5秒内向粉丝发送推文。

在推特的例子中,每个用户粉丝数的分布(可能按这些用户的发推频率来加权)是探讨可扩展性的一个关键负载参数,因为它决定了扇出负载。你的应用程序可能具有非常不同的特征,但可以采用相似的原则来考虑它的负载。

推特轶事的最终转折∶现在已经稳健地实现了方法2,推特逐步转向了两种方法的混合。大多数用户发的推文会被扇出写入其粉丝主页时间线缓存中。但是少数拥有海量粉丝的用户(即名流)会被排除在外。当用户读取主页时间线时,分别地获取出该用户所关注的每位名流的推文,再与用户的主页时间线缓存合并,如方法1所示。这种混合方法能始终如一地提供良好性能。在第12章中我们将重新讨论这个例子,这在覆盖更多技术层面之后。

描述性能

对于Hadoop这样的批处理系统,通常关心的是吞吐量(throughput),即每秒可以处理的记录数量,或者在特定规模数据集上运行作业的总时间。对于在线系统,通常更重要的是服务的响应时间(response time) ,即客户端发送请求到接收响应之间的时间。

延迟和响应时间
延迟(latency)和响应时间(response time)经常⽤作同义词,但实际上它们并不⼀样。响应
时间是客户所看到的,除了实际处理请求的时间(服务时间(service time))之外,还包括⽹
络延迟和排队延迟。延迟是某个请求等待处理的持续时⻓,在此期间它处于休眠(latent)状 态,并等待服务【17】。

即使不断重复发送同样的请求,每次得到的响应时间也都会略有不同。现实世界的系统会处理各式各样的请求,响应时间可能会有很⼤差异。因此我们需要将响应时间视为⼀个可以测量的数值分布(distribution),而不是单个数值。

在图1-4中,每个灰条表代表⼀次对服务的请求,其⾼度表示请求花费了多⻓时间。⼤多数请求是相当快的,但偶尔会出现需要更⻓的时间的异常值。这也许是因为缓慢的请求实质上开销更⼤,例如它们可能会处理更多的数据。但即使(你认为)所有请求都花费相同时间的情况下,随机的附加延迟也会导致结果变化,例如:上下⽂切换到后台进程,⽹络数据包丢失与TCP重传,垃圾收集暂停,强制从磁盘读取的⻚⾯错误,服务器机架中的震动【18】,还有很多其他原因。
在这里插入图片描述
如果想知道典型场景下⽤户需要等待多⻓时间,那么中位数是⼀个好的度量标准:⼀半⽤户请求的响应时间少于响应时间的中位数,另⼀半服务时间⽐中位数⻓。中位数也被称为第50百分位点,有时缩写为p50。

为了弄清异常值有多糟糕,可以看看更高的百分位点,例如第95、99和99.9百分位点(缩写为p95,p99和p999)。它们意味着95%,99%或99.9%的请求响应时间要比该阈值快,例如:如果第95百分位点响应时间是1.5秒,则意味着100个请求中的95个响应时间快于1.5秒,而100个请求中的5个响应时间超过1.5秒。如图1-4所示。

响应时间的高百分位点(也称为尾部延迟(tail latencies))非常重要,因为它们直接影响用户的服务体验。例如亚马逊在描述内部服务的响应时间要求时以99.9百分位点为准,即使它只影响一千个请求中的一个。亚⻢逊观察到:响应时间增加100毫秒,销售量就减少1%【20】;而另⼀些报告说:慢 1 秒钟会让客户满意度指标减少16%【21,22】。

另一方面,优化第99.99百分位点(一万个请求中最慢的一个)被认为太昂贵了,不能为亚马逊的目标带来足够好处。减小高百分位点处的响应时间相当困难,因为它很容易受到随机事件的影响,这超出了控制范围,而且效益也很小。

百分位点通常用于服务级别⽬标(SLO, service level objectives)和服务级别协议(SLA, service level agreements),即定义服务预期性能和可⽤性的合同。

排队延迟(queueing delay)通常占了高百分位点处响应时间的很大一部分。由于服务器只能并行处理少量的事务(如受其CPU核数的限制)﹐所以只要有少量缓慢的请求就能阻碍后续请求的处理,这种效应有时被称为头部阻塞(head-of-line blocking)。即使后续请求在服务器上处理的非常迅速,由于需要等待先前请求完成,客户端最终看到的是缓慢的总体响应时间。因为存在这种效应,测量客户端的响应时间非常重要。

为测试系统的可扩展性而人为产生负载时,产生负载的客户端要独立于响应时间不断发送请求。如果客户端在发送下一个请求之前等待先前的请求完成,这种行为会产生人为排队的效果,使得测试时的队列比现实情况更短,使测量结果产生偏差【23】。

实践中的百分位点
在多重调用的后端服务里,高百分位数变得特别重要。即使并行调用,最终用户请求仍然需要等待最慢的并行调用完成。如图1-5所示,只需要一个缓慢的调用就可以使整个最终用户请求变慢。即使只有一小部分后端调用速度较慢,如果最终用户请求需要多个后端调用,则获得较慢调用的机会也会增加,因此较高比例的最终用户请求速度会变慢(效果称为尾部延迟放大【24】)。如果您想将响应时间百分点添加到您的服务的监视仪表板,则需要持续有效地计算它们。例如,您可能希望在最近10分钟内保持请求响应时间的滚动窗口。每一分钟,您都会计算出该窗口中的中值和各种百分数,并将这些度量值绘制在图上。
简单的实现是在时间窗口内保存所有请求的响应时间列表,并且每分钟对列表进行排序。如果对你来说效率太低,那么有一些算法能够以最小的CPU和内存成本(如前向衰减【25】, t- digest 【26】或HdrHistogram【27】)来计算百分位数的近似值。请注意,平均百分比(例如,减少时间分辨率或合并来自多台机器的数据)在数学上没有意义-聚合响应时间数据的正确方法是添加直方图【28】。

在这里插入图片描述

应对负载的方法

适应某个级别负载的架构不太可能应付10倍于此的负载。如果你正在开发一个快速增长的服务,那么每次负载发生数量级的增长时,你可能都需要重新考虑架构。

人们经常讨论纵向扩展(scaling up)(垂直扩展(vertical scaling),转向更强大的机器)和横向扩展(scaling out)(水平扩展(horizontal scaling),将负载分布到多台小机器上)之间的对立。跨多台机器分配负载也称为“无共享(shared-nothing)“架构。可以在单台机器上运行的系统通常更简单,但高端机器可能非常贵,所以非常密集的负载通常无法避免地需要横向扩展。现实世界中的优秀架构需要将这两种方法务实地结合,因为使用几台足够强大的机器可能比使用大量的小型虚拟机更简单也更便宜。

有些系统是弹性(elastic)的,这意味着可以在检测到负载增加时自动增加计算资源,而其他系统则是手动扩展(人工分析容量并决定向系统添加更多的机器)。如果负载极难预测(highly unpredictable),则弹性系统可能很有用,但手动扩展系统更简单,并且意外操作可能会更少。

跨多台机器部署无状态服务(stateless services)非常简单,但将带状态的数据系统从单节点变为分布式配置则可能引入许多额外复杂度。出于这个原因,常识告诉我们应该将数据库放在单个节点上(纵向扩展),直到扩展成本或可用性需求迫使其改为分布式。

随着分布式系统的工具和抽象越来越好,至少对于某些类型的应用而言,这种常识可能会改变。可以预见分布式数据系统将成为未来的默认设置,即使对不处理大量数据或流量的场景也如此。本书的其余部分将介绍多种分布式数据系统,不仅讨论它们在可扩展性方面的表现,还包括易用性和可维护性。

大规模的系统架构通常是应用特定的一―没有一招鲜吃遍天的通用可扩展架构(不正式的叫法∶万金油(magic scaling sauce))。应用的问题可能是读取量、写入量、要存储的数据量、数据的复杂度、响应时间要求、访问模式或者所有问题的大杂烩。

举个例子,用于处理每秒十万个请求(每个大小为1kB)的系统与用于处理每分钟3个请求(每个大小为2GB)的系统看上去会非常不一样,尽管两个系统有同样的数据吞吐量。

一个良好适配应用的可扩展架构,是围绕着假设(assumption)建立的:哪些操作是常见的?哪些操作是罕见的?这就是所谓负载参数。如果假设最终是错误的,那么为扩展所做的工程投入就白费了,最糟糕的是适得其反。在早期创业公司或非正式产品中,通常支持产品快速迭代的能力,要比可扩展至未来的假想负载要重要的多。

尽管这些架构是应用程序特定的,但可扩展的架构通常也是从通用的积木块搭建而成的,并以常见的模式排列。在本书中,我们将讨论这些构件和模式。

可维护性

众所周知,软件的大部分开销并不在最初的开发阶段,而是在持续的维护阶段,包括修复漏洞、保持系统正常运行、调查失效、适配新的平台、为新的场景进行修改、偿还技术债、添加新的功能等等。

不幸的是,许多从事软件系统行业的人不喜欢维护所谓的遗留(legacy)系统。

但是我们可以,也应该以这样一种方式来设计软件︰在设计之初就尽量考虑尽可能减少维护期间的痛苦,从而避免自己的软件系统变成遗留系统。为此,我们将特别关注软件系统的三个设计原则:

  • 可操作性(Operability)
  • 简单性(Simplicity)
  • 可演化性(evolability)
    也称为可扩展性(extensibility) ,可修改性(modifiability)或可塑性(plasticity)。

和之前提到的可靠性、可扩展性一样,实现这些目标也没有简单的解决方案。不过我们会试着想象具有可操作性,简单性和可演化性的系统会是什么样子。

可操作性:人生苦短,关爱运维

有人认为,“良好的运维经常可以绕开垃圾(或不完整)软件的局限性,而再好的软件摊上垃圾运维也没法可靠运行"。尽管运维的某些方面可以,而且应该是自动化的,但在最初建立正确运作的自动化机制仍然取决于人。

运维团队对于保持软件系统顺利运行至关重要。一个优秀运维团队的典型职责如下(或者更多)【29】 :

  • 监控系统的运行状况,并在服务状态不佳时快速恢复服务跟踪问题的原因,例如系统故障或性能下降
  • 及时更新软件和平台,比如安全补丁
  • 了解系统间的相互作用,以便在异常变更造成损失前进行规避。
  • 预测未来的问题,并在问题出现之前加以解决(例如,容量规划)
  • 建立部署,配置、管理方面的良好实践,编写相应工具
  • 执行复杂的维护任务,例如将应用程序从一个平台迁移到另一个平台
  • 当配置变更时,维持系统的安全性
  • 定义工作流程,使运维操作可预测,并保持生产环境稳定。
  • 铁打的营盘流水的兵,维持组织对系统的了解。

良好的可操作性意味着更轻松的日常工作,进而运维团队能专注于高价值的事情。数据系统可以通过各种方式使日常任务更轻松:

  • 通过良好的监控,提供对系统内部状态和运行时行为的可见性(visibility)
  • 为自动化提供良好支持,将系统与标准化工具相集成
  • 避免依赖单台机器(在整个系统继续不间断运行的情况下允许机器停机维护)
  • 提供良好的文档和易于理解的操作模型(“如果做x,会发生Y")
  • 提供良好的默认行为,但需要时也允许管理员自由覆盖默认值
  • 有条件时进行自我修复,但需要时也允许管理员手动控制系统状态
  • 行为可预测,最大限度减少意外
简单性:管理复杂度

复杂度(complexity)有各种可能的症状,例如︰状态空间激增、模块间紧密耦合、纠结的依赖关系、不一致的命名和术语、解决性能问题的Hack、需要绕开的特例等等,现在已经有很多关于这个话题的讨论【31,32,33】。

因为复杂度导致维护困难时,预算和时间安排通常会超支。在复杂的软件中进行变更,引入错误的风险也更大︰当开发人员难以理解系统时,隐藏的假设、无意的后果和意外的交互就更容易被忽略。相反,降低复杂度能极大地提高软件的可维护性,因此简单性应该是构建系统的一个关键目标。

简化系统并不一定意味着减少功能;它也可以意味着消除额外的(accidental)的复杂度。Moseley和Marks 【32】把额外复杂度定义为︰由具体实现中涌现,而非(从用户视角看,系统所解决的)问题本身固有的复杂度。

用于消除额外复杂度的最好工具之一是抽象(abstraction)。一个好的抽象可以将大量实现细节隐藏在一个干净,简单易懂的外观下面。一个好的抽象也可以广泛用于各类不同应用。比起重复造很多轮子,重用抽象不仅更有效率,而且有助于开发高质量的软件。抽象组件的质量改进将使所有使用它的应用受益。

例如,高级编程语言是一种抽象,隐藏了机器码、CPU寄存器和系统调用。SQL也是一种抽象,隐藏了复杂的磁盘/内存数据结构、来自其他客户端的并发请求、崩溃后的不一致性。当然在用高级语言编程时,我们仍然用到了机器码;只不过没有**直接(directly)**使用罢了,正是因为编程语言的抽象,我们才不必去考虑这些实现细节。

抽象可以帮助我们将系统的复杂度控制在可管理的水平,不过,找到好的抽象是非常困难的。在分布式系统领域虽然有许多好的算法,但我们并不清楚它们应该打包成什么样抽象。

本书将紧盯那些允许我们将大型系统的部分提取为定义明确的、可重用的组件的优秀抽象。

可演化性:拥抱变化

系统的需求永远不变,基本是不可能的。更可能的情况是,它们处于常态的变化中,例如︰你了解了新的事实、出现意想不到的应用场景、业务优先级发生变化、用户要求新功能、新平台取代旧平台、法律或监管要求发生变化、系统增长迫使架构变化等。

在组织流程方面,敏捷(agile)工作模式为适应变化提供了一个框架。敏捷社区还开发了对在频繁变化的环境中开发软件很有帮助的技术工具和模式,如测试驱动开发(TDD, test-driven development)和重构(refactoring) 。

这些敏捷技术的大部分讨论都集中在相当小的规模(同一个应用中的几个代码文件)。本书将探索在更大数据系统层面上提高敏捷性的方法,可能由几个不同的应用或服务组成。例如,为了将装配主页时间线的方法从方法1变为方法2,你会如何“重构"推特的架构?

修改数据系统并使其适应不断变化需求的容易程度,是与简单性和抽象性密切相关的︰简单易懂的系统通常比复杂系统更容易修改。但由于这是一个非常重要的概念,我们将用一个不同的词来指代数据系统层面的敏捷性:可演化性(evolvability)【34】。

本章小结

本章探讨了一些关于数据密集型应用的基本思考方式。这些原则将指导我们阅读本书的其余部分,那里将会深入技术细节。

一个应用必须满足各种需求才称得上有用。有一些功能需求((functional requirements)(它应该做什么,比如允许以各种方式存储,检索,搜索和处理数据)以及一些非功能性需求(nonfunctional )(通用属性,例如安全性,可靠性,合规性,可扩展性,兼容性和可维护性)。在本章详细讨论了可靠性,可扩展性和可维护性。

不幸的是,使应用可靠、可扩展或可维护并不容易。但是某些模式和技术会不断重新出现在不同的应用中。在接下来的几章中,我们将看到一些数据系统的例子,并分析它们如何实现这些目标。

在本书后面的第三部分中,我们将看到一种模式︰几个组件协同工作以构成一个完整的系统(如图1-1中的例子)

参考文献

  1. Michael Stonebraker and ugur Cetintemel: "“One Size Fits All’: An ldea Whose Time HasCome and Gone,” at 21st International Conference on Data Engineering (ICDE),April 2005.
  2. Walter L.Heimerdinger and Charles B. Weinstock:“A Conceptual Framework for SystemFault Tolerance,” Technical Report CMU/SEl-92-TR-033, Software Engineering Institute,Carnegie Mellon University, October 1992.
  3. Ding Yuan, Yu Luo, Xin Zhuang, et al.: “Simple Testing Can Prevent Most Critical Failures: AnAnalysis of Production Failures in Distributed Data-Intensive Systems,” at 11th USENIXSymposium on Operating Systems Design and lmplementation (OSDI), October 2014.
  4. Yury lzrailevsky and Ariel Tseitlin: “The Netflix Simian Army,” techblog.netflix.com,July 19,2011.
  5. Daniel Ford, Francois Labelle,Florentina l.Popovici, et al.: “Availability in Globally DistributedStorage Systems,” at 9th USENIX Symposium on Operating Systems Design and lmplementation(OSDI), October 2010.
  6. Brian Beach:“Hard Drive Reliability Update - Sep 2014,” backblaze.com, September 23,2014.
  7. Laurie Voss:“AWS:The Good, the Bad and the ugly,” blog.awe.sm, December 18,2012.
  8. Haryadi S. Gunawi, Mingzhe Hao, Tanakorn Leesatapornwongsa, et al.: “What Bugs Live inthe Cloud?,” at 5th ACM Symposium on Cloud Computing(SoCC),November 2014.doi:10.1145/2670979.2670986
  9. Nelson Minar: “Leap Second Crashes Half the Internet,” somebits.com, July 3,2012.
  10. Amazon Web Services: “Summary of the Amazon EC2 and Amazon RDS Service Disruption in the US East Region,” aws.amazon.com,April 29, 2011.
  11. Richard I. Cook: “How Complex Systems Fail,” Cognitive Technologies Laboratory, April 2000.
  12. Jay Kreps: “Getting Real About Distributed System Reliability,”blog.empathybox.com, March 19, 2012.
  13. David Oppenheimer, Archana Ganapathi, and David A. Patterson: “Why Do Internet ServicesFail, and What Can Be Done About It?,” at 4th USENIX Symposium on Internet Technologies andSystems (USITS), March 2003.
  14. Nathan Marz: “Principles of Software Engineering, Part 1,” nathanmarz.com, April 2, 2013.
  15. Michael Jurewitz:“The Human Impact of Bugs,” jury.me, March 15, 2013.
  16. Raffi Krikorian: “Timelines at Scale,” at QCon San Francisco, November 2012.
  17. Martin Fowler: Patterns of Enterprise Application Architecture. Addison Wesley, 2002. ISBN:978-0-321-12742-6
  18. Kelly Sommers: “After all that run around, what caused 500ms disk latency even when wereplaced physical server?” twitter.com, November 13, 2014.
  19. Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, et al.: “Dynamo: Amazon’s HighlyAvailable Key-Value Store,” at 21st ACM Symposium on Operating Systems Principles (SOSP),October 2007.
  20. Greg Linden: “Make Data Useful,” slides from presentation at Stanford University DataMining class (CS345), December 2006.
  21. Tammy Everts: “The Real Cost of Slow Time vs Downtime,”webperformancetoday.com,November 12, 2014.
  22. Jake Brutlag:“Speed Matters for Google Web Search,”googleresearch.blogspot.co.uk, June 22,2009.
  23. Tyler Treat: “Everything You Know About Latency Is Wrong,” bravenewgeek.com, December 12,2015.
  24. Jeffrey Dean and Luiz André Barroso: “The Tail at Scale,” Communications of the ACM, volume 56, number 2, pages 74–80,February2013.doi:10.1145/2408776.2408794
  25. Graham Cormode, Vladislav Shkapenyuk, Divesh Srivastava, and Bojian Xu: “Forward Decay:A Practical Time Decay Model for Streaming Systems,” at 25th IEEE International Conference onData Engineering (ICDE), March 2009.
  26. Ted Dunning and Otmar Ertl: “Computing Extremely Accurate Quantiles Using t-Digests,”github.com, March 2014.
  27. Gil Tene: “HdrHistogram,” hdrhistogram.org.
  28. Baron Schwartz: “Why Percentiles Don’t Work the Way You Think,” vividcortex.com, December7, 2015.
  29. James Hamilton: “On Designing and Deploying Internet-Scale Services,” at 21st LargeInstallation System Administration Conference (LISA), November 2007.
  30. Brian Foote and Joseph Yoder: “Big Ball of Mud,” at 4th Conference on Pattern Languages ofPrograms (PLoP), September 1997.
  31. Frederick P Brooks: “No Silver Bullet – Essence and Accident in Software Engineering,” in TheMythical Man-Month, Anniversary edition, Addison-Wesley, 1995. ISBN: 978-0-201-83595-3
  32. Ben Moseley and Peter Marks: “Out of the Tar Pit,” at BCS Software Practice Advancement (SPA), 2006.
  33. Rich Hickey: “Simple Made Easy,” at Strange Loop, September 2011.
  34. Hongyu Pei Breivold, Ivica Crnkovic, and Peter J. Eriksson: “Analyzing Software Evolvability,” at 32nd Annual IEEE International Computer Software and Applications Conference (COMPSAC), July 2008. doi:10.1109/COMPSAC.2008.50
### 回答1: 《Designing Data-Intensive Applications》这本书是一本关于设计和构建大型数据应用程序的指南。它重点关注数据存储、处理和传输方面的现代技术。这本书由三部分组成:第一部分围绕着数据存储系统展开,包括传统的关系型数据库和现代的NoSQL数据库等;第二部分则聚焦于数据处理,涵盖流处理、批处理和交互式查询等领域;第三部分则探讨了如何构建可靠、可扩展且具有良好性能的分布式系统。无论是新手还是老手,都可以从本书中获得一些有价值的见解。这本书概述了不同技术在数据处理方面的优缺点,并介绍了一些构建大型、高效数据应用程序的最佳实践。另外,由于该书旨在让读者获得对现代架构和技术的深刻理解,因此它也介绍了一些分布式系统的核心理论,例如CAP定理和BASE原则等。这本书的读者包括数据科学家、软件工程师、数据工程师和系统管理员。总之,如果你正在构建一个数据密集型应用程序,想要了解最新的技术和最佳实践,那么《Designing Data-Intensive Applications》绝对值得一读。 ### 回答2: 《设计数据密集型应用程序》(Designing Data-Intensive Applications)是一本由 Martin Kleppmann 所著的计算机科学类图书,这本书讨论了当今主要的数据系统和应用程序所面临的问题和挑战,并提供了一些解决方案。这本书内容涵盖了分布式系统、数据存储和查询、数据一致性与容错性、流处理、数据流水线、实时分析等领域,不仅提供了丰富的理论知识,还有很多实践案例和相关技术细节。 对于软件工程师、架构师、数据工程师等人来说,这本书是一本非常重要的访问。如果你正在开发一个数据密集型的应用或系统,这本书提供了很多有用的指导和建议。例如,这本书会告诉你如何选择和使用不同的数据存储技术,如何设计高效的数据处理流水线,如何保证系统的可扩展性和容错性等等。 《设计数据密集型应用程序》还使用了很多实用的案例和场景,比如 Twitter 的分布式消息队列 Kafka,Google 的基于 Paxos 算法的分布式一致性协议 Chubby,Facebook 的实时数据处理系统 Apache Samza 等等。通过这些案例,读者可以更好地了解如何应用书中的理论知识到实际工作中。 总之,《设计数据密集型应用程序》是一本值得阅读的计算机科学类图书,无论你是软件工程师、数据工程师、系统架构师等,都有望从中获得很多启发。 ### 回答3: Designing Data-Intensive Applications是一本讲述大数据应用程序设计的书籍。这本书主要涵盖了数据密集型应用程序设计的方方面面,包括数据存储和查询、数据处理和流处理、分布式系统和高可用性、性能和可扩展性等。它通过介绍各种不同的数据管理工具和技术,帮助读者了解如何在大数据领域中设计出高效和可靠的应用程序。 在设计数据密集型应用程序时,需要考虑很多因素。从数据存储和查询的角度来看,我们需要考虑使用哪种数据库或数据存储方案,并且了解其适用的场景和可扩展性。同时还需要思考如何使用数据查询工具来优化查询性能。 在数据处理和流处理方面,我们需要选择正确的数据处理框架和工具,以处理大数据集合。我们也需要了解如何设计分布式系统,并且使其具有高可用性和容错性。性能和可扩展性也是设计数据密集型应用程序时的至关重要因素,因此我们需要考虑如何优化系统吞吐量和处理能力,并在需要时进行水平扩展。 总之,Designing Data-Intensive Applications是一本非常有价值的书籍,可以帮助读者了解如何在大数据领域中进行应用程序设计。它提供了丰富的知识和有实际应用的案例,让读者在实践中掌握数据密集型应用程序设计的关键技能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值