本节书摘来自华章出版社《大数据系统构建:可扩展实时数据系统构建原理与最佳实践》一书中的第1章,第1.7节,南森·马茨(Nathan Marz) [美] 詹姆斯·沃伦(JamesWarren) 著 马延辉 向 磊 魏东琦 译,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1.7 Lambda架构
实时计算任意数据集上的任意函数,是一个令人望而却步的问题。没有单独的工具可以提供完整的解决方案,相反,你必须使用各种工具和技术构建一个完整的大数据系统。
Lambda架构的主要思想是将大数据系统建立为一系列的层,如图1-6所示。每一层满足属性的一个子集,且通过该层的下一层所提供的功能来构建。虽然你通过整本书来学习如何设计、实现和部署每一层,但整个系统是如何有机结合在一起的高层思想是很容易理解的。
一切从query = function(all data)等式开始。理想情况下,你可以不断运行这个函数来获取结果。不幸的是,即使这是可能的,也需要占用大量的资源,并且相当昂贵。想象一下,每次你想要响应某人当前位置的查询时,都必须要读取1PB的数据集。
最显而易见的替代方法是预先计算查询函数。我们将预先计算的查询函数称为批处理视图(Batch View)。这不是动态计算查询,而是从预先计算好的视图中读取结果。预先计算的视图是有索引的,因此可以用随机读取的方式进行访问。该系统看起来像是这样的:
在该系统中,你在所有数据上运行一个函数来获取批处理视图。然后,当你想知道一个查询的值时,只需针对批处理视图运行一个函数即可。批处理视图可以使你很快地从中获得所需要的值,而无须扫描所有数据。
这个讨论有点抽象,下面举例说明。假设你正在构建一个网络分析的应用程序(再次构建),并且想要查询任一范围天数内某个URL的页面浏览量。如果你在所有数据上运行该查询函数,最好扫描数据集中相应时间范围内那个URL的页面浏览量,并返回结果的计数。
批处理视图方法替代在所有页面浏览上运行一个函数的方式,来预先计算[url,day]键的索引,以获得某天某个URL页面浏览的计数。然后为了解决该查询,从视图中检索相应时间范围内的所有天的值,并将所有计数相加,以得到最终的结果。这种方法如图1-7所示。
应该明确的是,到目前为止所描述的这种方法似乎缺了点什么。创建批处理视图显然会是一个高延迟操作,因为它是在你的所有数据上运行一个函数。在批处理视图结束时,很多新的数据将被收集但是没有展示在批处理视图中,并且这个查询将过期许多小时。但是我们暂时忽略这个问题,因为它是可以解决的。假设过期几个小时的查询是可以的,那么我们继续探索通过在完整数据集上运行函数来预先计算批处理视图的这个想法。
1.7.1 批处理层
Lambda架构中实现batch view = function(alldata)等式的这部分被称为批处理层。批处理层存储数据集的主副本,并在主数据集上预先计算批处理视图(见图1-8)。
主数据集可以被视作一个非常大的记录列表。
批处理层需要能够做两件事:存储不可变的、不断增长的主数据集;在该数据集上运行任意函数。最好使用批处理系统完成这种类型的处理。Hadoop是批处理系统的一个典型例子,我们将在本书中使用Hadoop来阐述批处理层的概念。
批处理层最简单的形式可以用如下的伪代码表示:
批处理层在while(true)中循环运行,不断从头开始重新计算批处理视图。实际上,批处理层的功能并不仅限于此,相关内容会在本书后续章节予以介绍。这里只探讨批处理层的最好方式。
批处理层的优点是,使用起来很简单。批处理计算可以编写成例如单线程的程序,并且你可以毫不费力地获取并行性。很容易在批处理层编写鲁棒性好的、高度可扩展的计算。批处理层可通过添加新机器进行扩展。
下面是批处理层计算的一个例子。不要担心对代码的理解问题—重点是展示一个天生具备并行性的程序是什么样子的:
这段代码把给定的原始页面浏览的数据集作为输入,为每个URL计算页面浏览量。这段代码的有趣之处在于—调度工作的所有并行性挑战和结果的合并都已经为你做好了。由于算法是用这种方式写的,因此它可以任意地分布在MapReduce集群中,扩展到可用的不管多少数量的节点上。在计算结束时,输出目录将包含一些结果文件。本书第7章将介绍如何编写这样的程序。
1.7.2 服务层
批处理层的功能是生成批处理视图。下一步是在某个地方加载视图,以便它们可以被查询到—这个地方就是服务层。服务层(Serving Layer)是一个专用的分布式数据库,用于加载批处理视图,并可以对它进行随机读取(见图1-9)。当新的批处理视图可用时,服务层会自动替换那些视图,这样更多的最新结果就是可用的了。
服务层数据库支持批处理更新和随机读取。最值得注意的是,它不需要支持随机写操作—这是非常重要的一点,因为随机写会在数据库中导致绝大多数的复杂性—因为不支持随机写,所以这些数据库非常简单。简单性使得它们是可预测的、鲁棒性好的,易于配置,且操作简单。ElephantDB是你将在本书中学习使用的服务层数据库,它只有几千行代码。
1.7.3 批处理层和服务层满足几乎所有属性
批处理层和服务层支持对任意数据集上的任意查询,当然是以查询将过期几个小时为代价的。一个新的数据片段从批处理层传播到可以被查询到的服务层,需要花费几个小时。需要注意的是,除了低延迟更新,批处理层和服务层满足了大数据系统所需的每个属性(参见1.5节)。让我们依次检查一遍:
鲁棒性和容错性—当机器发生故障时,Hadoop处理故障转移。服务层在内部使用副本机制,确保服务器宕机时的可用性。批处理层和服务层也是可以容忍人为错误的,因为一旦出错,你可以修复算法或删除坏数据,并从头开始重新计算视图。
可扩展性—批处理层和服务层均易于扩展。它们都是完全的分布式系统,扩展它们就像添加新机器一样容易。
通用性—这里描述的架构非常通用。你可以计算和更新任意数据集的任意视图。
延展性—添加一个新的视图就像对主数据集添加一个新函数一样容易。由于主数据集可以包含任意数据,因此新类型的数据可以很容易地被添加进来。如果想微调一个视图,你不必担心支持应用程序中多个版本的视图。你可以简单地从头开始重新计算整个视图。
即席查询—批处理层支持即席查询。在某个位置,所有数据都是方便可用的。
最少维护—维护这个系统的主要组件是Hadoop。Hadoop需要一些管理知识,但操作起来相当简单。正如前面所解释的那样,服务层数据库是很简单的,因为它们不进行随机写操作。因为服务层数据库有很少的活动部件,出错的可能性不大。因此,服务层数据库不太可能出错,从而更容易维护。
可调试性—在批处理层运行时,你永远都有计算的输入和输出。在传统数据库中,一个输出可以替换原来的输入—比如递增一个值时。在批处理层和服务层,输入是主数据集,输出是视图。同样,所有中间步骤都有输入和输出。当出错时,输入和输出可以给出调试所需要的所有信息。
批处理层和服务层之美在于它们用一个简单且易于理解的方法满足了几乎所有你想要的属性—没有并发问题要处理,扩展非常简单。唯一没有满足的属性是低延迟更新。在最后一层,速度层,会解决这个问题。
1.7.4 速度层
一旦批处理层完成预先计算批处理视图,服务层即进行更新。这意味着唯一没有在批处理视图中展示的数据是运行预先计算期间新来的数据。为了实现一个完全的实时数据系统,剩下的任务要完成—也就是说,为了实时在任意数据上执行任意函数计算—弥补最后几个小时的那些数据。这是速度层的目的。顾名思义,它的目标是确保新的数据按照应用程序的需求尽快展示在查询函数中(见图1-10)。
你可以认为速度层是类似于批处理层的,它基于接收到的数据生成视图。两者之间一个很大的区别是,速度层只查看最近的数据,而批处理层要立即查看所有数据。另一个很大的区别是,为达到最小延迟的可能,速度层不会立即查看所有新数据。相反,每当接收到新的数据,它就更新实时视图,而不是像批处理层从头开始重新计算视图。速度层做增量计算,而不是像批处理层那样进行重新计算。
我们可以将速度层上的数据流格式化成以下等式:
realtime view = function ( realtime view, new data)
实时视图基于新数据和现有的实时视图进行更新。
Lambda架构总体被总结为以下三个等式:
batch view = function (all data)
realtime view = function (realtime view, new data)
query = function (batch view. realtime view)
这些想法的图解如图1-11所示。也就是说,不是只需要对批处理视图运行一个函数就可以得到查询,而是需要通过查看批处理视图和实时视图并将结果合并在一起,才能得到查询。
速度层使用支持随机读取和随机写入的数据库。因为这些数据库支持随机写,所以它们比在服务层使用的数据库要高出几个数量级的复杂性,无论是实现方面还是操作方面。
Lambda架构之美在于,一旦数据通过批处理层到服务层,实时视图中相应的结果就不再需要了。这意味着你可以丢弃不再需要的实时视图。这是一个很好的结果,因为速度层远比批处理层和服务层更复杂。Lambda架构的这个属性被称为复杂性隔离,这意味着复杂性被推入一个只存储暂时结果的层中。如果有什么差错,你可以丢弃整个速度层的状态,并且在几小时内使一切恢复正常。
下面继续构建网络分析应用程序的例子—它支持一些天当中页面浏览量的查询。请回顾一下批处理层从[url,day]到页面浏览量所生成的批处理视图。
速度层保存自己独立的[url,day]到页面浏览量的视图。而批处理层通过逐次计算页面浏览,重新计算批处理视图。每当接收到新数据,速度层就通过增加在视图中的计数值来更新自己的视图。为解决一个查询过程,你需要查询所需的批处理视图和实时视图,来满足指定的日期范围,并将结果相加以得到最终计数。还有一项需要去做的工作,就是恰当地同步结果(相应内容将在本书后续的章节中介绍)。
一些算法很难增量地计算。批处理/速度层的分离,为你在批处理层上使用精确算法和速度层上使用近似算法提供了足够的灵活性。批处理层多次重写速度层,所以近似值得到修正,并且系统也显示了最终准确性的属性。例如计算独立计数,如果独立值的集合很大,这可能是很有挑战性的。很容易在批处理层完成独立值计数,因为你能立即看到所有数据,但在速度层你可以将HyperLogLog集合作为一个近似值使用。
最终,性能和鲁棒性兼得。因为批处理层纠正在速度层中的计算,所以在批处理层做准确计算且在速度层做近似计算的系统,呈现了最终的准确性。你得到的仍然是低延迟更新,但由于速度层是暂时的,实现该属性的复杂性并不会影响结果的鲁棒性。当涉及性能折中方案时,速度层的暂时特性给你提供了极大的灵活性。当然,因为用增量方式完成的计算是准确的,所以该系统是完全准确的。