Cascading是一个开源的Java库和应用程序编程接口(API),它为MapReduce提供了一个抽象层。它容许开发者构建出能在Hadoop集群上运行的复杂的、关键任务的数据处理应用。html
Cascading项目始于2007年夏天。它的第一个公开版本,即版本0.1,发布于2008年1月。版本1.0发布于2009年1月。从该项目的主页http://www.cascading.org/能够下载二进制版本,源代码以及一些加载项模块。前端
map和reduce操做提供了强大的原语操做。然而,在建立复杂的、能够被不一样开发者共享的合成性高的代码时,它们粒度级别彷佛不合适。再者,许多开发者发现当他们面对实际问题的时候,很难用MapReduce的模式来思考问题。java
为了解决第一个问题,Cascading用简单字段名和一个数据元组模型值来替代MapReduce使用的键和值,而该模型的元组是由值的列表构成的。对第二个问题,Cascading直接从Map和Reduce操做分离出来,引入了更高层次的抽象:Function,Filter,Aggregator和Buffer。数据库
其余一些可选择的方案在该项目初始版本公开发布的同时基本上也出现了,但Cascading的设计初衷是对它们进行补充和完善。主要是考虑到大部分可选的架构都是对系统强加一些前置和后置条件或有其余方面的要求而已。express
例如,在其余几种MapReduce工具里,运行应用程序以前,你必须对数据进行预格式化处理、过滤或把数据导入HDFS(Hadoop分布式文件系统)。数据准备步骤必须在系统的程序设计抽象以外完成。相反,Cascading提供方法实现把数据准备和管理做为系统程序设计抽象的组成部分。编程
该实例研究将首先介绍Cascading的主要概念,最后归纳介绍ShareThis如何在本身的基础框架上使用Cascading。数组
若是但愿进一步了解Cascading处理模型,请参见项目主页上的“Cascading用户手册”。浏览器
字段、元组和管道服务器
MapReduce模型使用键和值的形式把输入数据和Map函数,Map函数和Reduce函数以及Reduce函数和输出数据联系起来。网络
但据咱们所知,实际的Hadoop应用程序一般会将多个MapReduce做业链在一块儿。看一下用MapReduce模型实现的一个典型的字数统计例子。若是须要根据统计出来的数值进行降序排列,这是一个可能的要求,它将须要启动另外一个MapReduce做业来进行这项工做。
所以,理论上来讲,键和值的模式不只把Map和 Reduce绑定到一块儿,它也把Reduce和下一次的Map绑定了,这样一直进行下去(图16-11)。即键/值对源自输入文件,流过Map和Reduce操做造成的链,而且最后终止到一个输出文件。实现足够多这样连接的MapReduce应用程序,便能看出一系列定义良好的键/值操做,它们被一遍一遍地用来修改键/值数据流的内容。
图16-11. 基于MapReduce的计数和排序
Cascading系统经过使用具备相应字段名的元组(与关系型数据库中的表名和列名相似)来替代键/值模式的方法简化了这一处理流程。在处理过程当中,由这些字段和元组组成的流数据在它们经过用户定义的、由管道(pipe)连接在一块儿的操做时得以处理(图16-12)。
所以,MapReduce的键和值被简化成以下形式。
字段
字段是一个String(字符串)类型的名称集合(如“first_name”)、表示位置信息的数值(如2和-1分别是第三和最后一个位置)或是二者混合使用的集合,与列名很是像。所以字段用来声明元组里值的名称和经过名称在元组中选出对应的值。后者就像执行SQL的select语句。
图16-12. 由字段和元组连接的管道
元组
元组就是由java.lang.Comparable类对象组成的数组。元组与数据库中的行或记录相似。
Map和Reduce操做都被抽象隐藏到一个或多个管道实例以后(图16-13)。
Each
Each管道一次只处理一个单独的输入元组。它能够对输入元组执行一个Function或一个Filter操做(后文立刻要介绍)。
GroupBy
GroupBy管道在分组字段上对元组进行分组。该操做相似于SQL的group by语句。若是元组的字段名相同,它也能把多个输入元组数据流合并成一个元组数据流。
CoGroup
CoGroup管道既能够实现元组在相同的字段名上链接,也能够实现基于相同字段的分组。全部的标准链接类型(内链接—inner join,外链接—outer join等)以及自定义链接均可以用于两个或多个元组数据流。
图16-13. 管道类型
Every
Every管道每次只处理元组的一个单独分组的数据,分组数据能够由GroupBy或CoGroup管道产生。Every管道能够对分组数据应用Aggregator或Buffer操做。
SubAssembly
SubAssembly管道容许在一个单独的管道内部进行循环嵌套流水线处理,或反过来,一个管道也能够被嵌入更加复杂的流水线处理中。
全部这些管道被开发者连接在一块儿造成“管道流水线处理流程”,这里每一个流水线能够有不少输入元组流(源数据,source)和不少输出元组流(目标数据,sink)(见图16-14)。
图16-14. 简单的管道流水线
从表面上看来,这可能比传统的MapReduce模型更复杂。而且,不能否认,相较于Map,Reduce,Key和Value,这里涉及的概念更多。但实际上,咱们引入了更多的概念,它们必须都工做协助提供不一样的功能。
例如,若是一个开发者想对reducer的输出值提供“辅助排序”功能,她将须要实现Map、Reduce,一个“合成”Key(嵌套在父Key中的两个Key),值,partitioner、一个用于“输出值分组”的comparator和一个“输出键”的comparator,全部这些概念以各类方式结合协做使用,而且在后续的应用中几乎不可重用。
在Cascading里,这项工做只对应一行代码:new GroupBy(, , ),其中previous是数据源管道。
操做
如前所述,Cascading经过引入一些替换性操做脱离了MapReduce模式,这些操做或应用于单个元组,或应用于元组分组(图16-15)。
Function
Function做用于单个的输入元组,对每一个输入,它可能返回0或多个输出元组。Function操做供Each类型的管道使用。
图16-15. 操做类型
Filter
Filter是一种特殊的函数,它的返回值是boolean(布尔)值,用于指示是否把当前的元组从元组流中删除。虽然定义一个函数也能实现这一目的,可是Filter是为实现这一目的而优化过的操做,而且不少过滤器可以经过逻辑运算符(如And、Or、Xor和Not)分组,能够快速建立更复杂的过滤操做。
Aggregator
Aggregator对一组元组执行某种操做,这些分组元组是经过一组共同字段分组获得的。好比,字段“last-name”值相同的元组。常见的Aggregator方法是Sum(求和)、Count(计数),Average(均值)、Max(最大)和Min(最小)。
Buffer
Buffer和Aggregator操做相似,不一样的是,它被优化用来充当一个“滑动窗口”扫描一个惟一分组中全部的元组。当开发者须要有效地为一组排序的元组插入遗漏的值时,或计算动态均值的时候,这个操做很是有用。一般,处理元组分组数据的时候,Aggregator也是一个可选的操做,由于不少Aggregator可以有效地连接起来工做,但有时,Buffer才是处理这种做业的最佳工具。
管道流水线建立的时候,这些操做便绑定到各管道(图16-16)。
Each和Every类型的管道提供了一种简单的元组选择机制,它们能够选择一些或全部的输入元组,而后把这些选择的数据传送给它的子操做。而且咱们有一个简单的机制把这些操做的结果和原来的输入元组进行合并,而后产生输出元组。这里并不详细说明机制,它使得每一个操做只关心参数指定的元组值和字段,而不是当前输入元组的整个字段集。其次,操做在不一样应用程序之间重用,这点和Jave方法重用的方式相同。
图16-16. 操做流程
例如,在Java中,声明一个方法concatenate(String first, Stringsecond),比直接定义concatenate(Person person)更抽象。第二个方法的定义,concatenate()函数必须“了解”Person对象;而第一个方法的定义并不清楚数据来自哪里。Cascading操做展示了一样的抽象能力。
Tap类、Scheme对象和Flow对象
在前面的几个图中,咱们屡次提到源数据(source)和目标数据(sink)。在Cascading系统中,全部的数据都是读自或写入Tab类实例,可是它们是经过Scheme对象被转换成或取自元组实例对象。
Tap
Tap类负责如何访问数据以及从哪一个位置访问数据。例如,判断数据是存于HDFS仍是存于本地?在Amazon S3中,仍是跨HTTP协议进行访问?
Scheme
Scheme类负责读取原始数据并把它们转换成元组格式/或把元组数据写入原始数据格式文件,这里的原始数据能够是文本行、Hadoop二进制的顺序文件或是一些专用格式数据。
注意,Tap类对象不是管道处理流程的一部分,所以它们不是Pipe类型。
可是当Tap对象在集群上变得可执行的时候,它们就和管道组件关联到一块儿。当一个管道处理流程与必要的几个源和目标数据Tap实例关联一块儿后,咱们就获得一个Flow对象。Flow对象是在管道处理流程与指定数量的源及目标数据Tap关联时建立的,而Tap对象的功能是输出或获取管道流程指望的字段名。就是说,若是Tap对象输出一个具备字段名“line”的元组(经过读取HDFS上的文件数据),那么这个管道流程头部必须也但愿字段名是“line”。不然,链接管道处理流程和Tap的处理程序会马上失败并报错。
所以,管道处理流程实际上就是数据处理定义,而且它们自己不是“可执行”的。在它们能够在集群上运行以前,必须链接到源和目标Tap对象。这种把Tap和管道处理流程分开处理的特性使Cascading系统很是强大。
若是认为管道处理流程和Java类类似,那么Flow就像Java对象实例(图16-17)。也就是说,在同一个应用程序里面,一样的管道处理流程能够被实例化不少次从而造成新的Flow,不用担忧它们之间会有任何干扰。如此一来,管道处理流程就能够像标准Java库同样建立和共享。
图16-17. 流水线处理过程
Cascading实战
如今咱们知道Cascading是什么,清楚地了解它是如何工做的,可是用Cascading写的应用程序是什么样子呢?咱们来看看例16-2。
例16-2. 字数统计和排序
Scheme sourceScheme =
new TextLine(new Fields("line")); ?
Tap source =
new Hfs(sourceScheme, inputPath); ?
Scheme sinkScheme = new TextLine(); ?
Tap sink =
new Hfs(sinkScheme, outputPath, SinkMode.REPLACE); ?
Pipe assembly = new Pipe("wordcount"); ?
String regexString = "(?
Function regex = new RegexGenerator(new Fields("word"), regexString);
assembly =
new Each(assembly, new Fields("line"), regex); ?
assembly =
new GroupBy(assembly, new Fields("word")); ?
Aggregator count = new Count(new Fields("count"));
assembly = new Every(assembly, count); ?
assembly =
new GroupBy(assembly, new Fields("count"), new Fields("word")); ?
FlowConnector flowConnector = new FlowConnector();
Flow flow =
flowConnector.connect("word-count", source, sink, assembly);
flow.complete();
? 建立一个新的Scheme对象读取简单的文本文件,为每一行名为“line”字段(被Fields对象声明)输出一个新的Tuple对象。
? 建立一个新的Scheme对象用于写简单文本文件,而且它指望输出的是一个具备任意多个字段/值的Tuple对象。假若有多个值要输出,这些值在输出文件里将以制表符分隔。
? 建立源和目标Tap实例分别指向输入文件和
? 输出目录。目标Tap对象输出数据时将覆盖目录下现有的全部文件。
? 构建管道处理流程的头,并把它命名为“wordcount”。这个名称用于绑定源及目标数据到这个管道处理流程。多个头或尾要求必须有本身惟一的名称。
? 构建具备一个函数的Each类型管道,它将解析line字段里的每一个词,把解析结果放入一个新的Tuple对象。
? 构建GroupBy管道,它将建立一个新的Tuple组,实现基于word字段的分组。
? 构建一个具备Aggregator操做的Every类型管道,它将对基于不一样词的分组Tuple对象分别进行字数统计。统计结果存于count的字段里。
? 构建GroupBy类型管道,它将根据数值对count字段进行分组,造成新的Tuple分组,而后对word字段值进行辅助排序。结果是一组基于count字段值升序排列的count字段值和word字段的值列表。
用Flow对象把管道处理流程和数据源及目标联系起来,而后
在集群上执行这个Flow。
在这个例子里,咱们统计输入文件中的不一样单词的数量,并根据它们的天然序(升序)进行排序。假若有些词的统计值相同,这些词就根据它们的天然顺序(字母序)排序。
这个例子有一个明显的问题,即有些词可能会有大写字母;例如,“the”和“The”,当它出如今句首的时候就是“The”。所以咱们能够插入一个新的操做来强制全部单词都转换为小写形式,可是咱们意识到那些须要从文档中解析词语的全部未来的应用都必须作一样的操做,所以咱们决定建立一个可重用的管道SubAssembly,如同咱们在传统应用程序中建立一个子程序同样(参见例16-3)。
例16-3. 建立一个SubAssembly
public class ParseWordsAssembly extends SubAssembly ?
{
public ParseWordsAssembly(Pipe previous)
{
String regexString = "(?
Function regex = new RegexGenerator(new Fields("word"), regexString);
previous = new Each(previous, new Fields("line"), regex);
String exprString = "word.toLowerCase()";
Function expression =
new ExpressionFunction(new Fields("word"), exprString,String.class); ?
previous = new Each(previous, new Fields("word"), expression);
setTails(previous); ?
}
}
? 声明SubAssembly是子类,它自己是一种管道类型。
? 建立一个Java的表达式函数,它将调用toLowerCase()方法来处理“word”字段对应的字符串类型值。咱们要传入表达式函数指望的“word”字段的Java类型,这里是String类型。后台用Janino(http://www.janino.net/)来编译。
? 咱们必须告知SubAssembly的父类这个管道子组件在哪里结束。
首先,咱们新建一个SubAssembly类,它管理咱们的“解析词”管道组件。由于这是一个Java类,因此可用于其余任何应用程序,固然这要求它们处理的数据中有word字段(例16-4)。注意,也有办法可使这个函数更加通用,这些方法在“Cascading用户手册”中都有介绍。
例16-4. 用一个SubAssembly扩展单词计数和排序
Scheme sourceScheme = new TextLine(new Fields("line"));
Tap source = new Hfs(sourceScheme, inputPath);
Scheme sinkScheme = new TextLine(new Fields("word", "count"));
Tap sink = new Hfs(sinkScheme, outputPath, SinkMode.REPLACE);
Pipe assembly = new Pipe("wordcount");
assembly =
new ParseWordsAssembly(assembly); ?
assembly = new GroupBy(assembly, new Fields("word"));
Aggregator count = new Count(new Fields("count"));
assembly = new Every(assembly, count);
assembly = new GroupBy(assembly, new Fields("count"), new Fields("word"));
FlowConnector flowConnector = new FlowConnector();
Flow flow = flowConnector.connect("word-count", source, sink, assembly);
flow.complete();
? 咱们用ParseWordsAssembly管道组件替换了以前例子中的Each类型管道。最后,咱们只是用新的SubAssembly类型子管道替代了前面Every类型管道和单词解析函数。有必要的话,还能够继续进行更深刻的嵌套处理。
灵活性
后退一步,让咱们来看看这个新的模型给咱们带来了什么好处,或更妙的是,消除了哪些不足。
能够看出,咱们没必要再用MapReduce做业模式来考虑问题,或考虑Mapper和Reducer接口的实现问题,后续的MapReduce做业和前面的MapReduce做业如何绑定或连接。在运行的时候,Cascading“规划器”(planner)会算出最优的方法把管道处理流程切分红MapReduce做业,并管理做业之间的连接(图16-18)。
图16-18. 怎么把Flow翻译成链式MapReduce做业
所以,开发者能够以任何粒度来构造本身的应用程序。它们能够一开始就只是一个很小的作日志文件过滤处理的应用程序,可是后来能够根据须要不断增添新的功能。
Cascading是一个API而不是相似SQL的字符串句法,所以它更灵活。首先,开发者能用他们熟悉的语言建立特定领域语言(domain-specific language,DSL),像Groovy,JRuby,Jython,Scala等(示例参见项目网站)。其次,开发者能对Cascading不一样的部分进行扩展,像容许自定义Thrift或JSON对象使其能读写,而且容许它们以元组数据流的形式传送。
Hadoop和Cascading在ShareThis的应用
ShareThis是一个方便用户共享在线内容的共享网络。经过单击网页上或浏览器插件上的一个按钮,ShareThis容许用户无缝地访问他们的任何在线联系人及在线网络,而且容许他们经过电子邮件,IM,Facebook,Digg,手机SMS等方式共享它们的内容,而这一过程的执行甚至不要求他们离开当前的访问网页。发布者能配置他们的ShareThis按钮来标记服务的全球共享能力,如此推进网络流量,刺激传播活动,追踪在线内容的共享。经过减小网页不须要的内容及提供经过社会网络、隶属组和社区实时的内容发布功能,ShareThis还简化了社区媒体服务。
ShareThis用户经过在线窗口共享网页和信息时,一个连续的事件数据流就进入ShareThis网络。这些事件首先要过滤和处理,而后传送给各类后台系统,包括 AsterData,Hypertable和Katta。
这些事件的数据量能达到很大数量级,数据量太大以至于传统的系统没法处理。这种数据的“污染”(dirty)也很严重,主要归咎于流氓软件系统的“注入式攻击”、网页缺陷或错误窗口。所以,ShareThis选择为后台系统部署Hadoop做为预处理和处理协调管理(orchestration)前台。他们也选择使用Amazon Web服务(基于弹性云计算平台EC2)来托管其服务器,而且使用Amazon S3(简单服务存储服务)提供长期的存储功能,目的是利用其弹性的MapReduce模式(Elastic MapReduce,EMR)。
这里着重介绍“日志处理管道”(图16-19)。日志处理管道只是简单地从S3文件夹(bucket)里读取数据,进行处理(稍后介绍),而后把结果存入另外一个文件夹。简单消息队列服务(Simple Queue Service,SQS)用于协调各类事件的处理,用它来标记数据处理执行程序的开始和完成状态。下行数据流是一些其余的处理程序,它们用于拖动数据装载AsterData数据仓库,如从Hypertable系统获取URL列表做为网络爬取工具的下载源,或把下载的网页推入Katta系统来建立Lucene索引。注意,Hadoop系统是ShareThis整个架构的中心组件。它用于协调架构组件之间的数据处理和数据移动工做。
有了Hadoop系统做为前端处理系统,在全部事件日志文件被加载到AsterData集群或被其余组件使用以前,它会基于一系列规则基于一系列规则对数据进行解析、过滤、清理和组织。AsterData是一个集群化数据仓库系统,它能支持大数据存储,并容许使用标准的SQL语法发出复杂的即时查询请求。ShareThis选择Hadoop集群来进行数据清理和准备工做,而后它把数据加载到AsterData集群实现即时分析和报告处理。尽管使用AsterData也有可能达到咱们的目的,可是在处理流程的第一阶段使用Hadoop系统来抵消主数据仓库的负载具备重要意义。
为了简化开发过程,制定不一样架构组件间的数据协调规则以及为这些组件提供面向开发者的接口,Cascading被选为主要的数据处理API。这显示出它和“传统的”Hadoop用例的差异,它们主要是用“Hadoop”来实现对存储数据的查询处理。
图16-19. ShareThis日志处理管道
相反的,Cascading和Hadoop的结合使用为端到端的完整解决方案提供了一个更好、更简单的结构,所以对用户来讲更有价值。
对于开发者来讲,Cascading的学习过程很简单,它从一个简单的文本解析单元测试(经过建立cascading.ClusterTestCase类的子类)开始,而后把这个单元程序放入有更多规则要求的处理层,而且在整个过程当中,与系统维护相关的应用逻辑组织不变。Cascading用如下几种方法帮助保持这种逻辑组织的不变性。首先,独立的操做(Function,Filter等)均可以进行独立写和测试。其次,应用程序被分红不一样的处理阶段:一个阶段是解析,一个阶段是根据规则要求进行处理,最后一个阶段是封装/整理数据,全部这些处理都是经过前述的SubAssembly基础类实现的。
ShareThis的日志文件数据看起来很是像Apache日志文件,它们有日期/时间戳、共享URL、引用页URL和一些元数据。为了让分析下行数据流使用这些数据,这些URL必须先解压(解析查询字符串数据和域名等)。所以须要建立一个高层的SubAssembly对象来封装解析工做,而且,若是字段解析很复杂,SubAssembly子对象就可被嵌入来解析一些特定字段。
咱们使用一样的方式来应用处理规则。当每一个Tuple对象经过SubAssembly对象实例的时候,若是有任何规则被触发,该对象就会被标记上标签“坏”(bad)。具备“坏”字标签的Tuple对象,会被附上被标记的缘由用于后来的审查工做。
最后,建立一个切分SubAssembly对象来作两件事。第一,用于对元组数据流进行分流处理,一个数据流针对标记“好”(good)的数据,另外一个针对标记“坏”的数据。第二件是,切分器把数据切分红片,如以小时为单位。为了实现这一动做,只须要两个操做:第一个是根据已有数据流的timestamp(时间戳)建立区间段;第二个是使用interval(区间)和good/bad元数据来建立目录路径(例如,“05/good/”中“05”是早上5点,“good”是通过全部规则验证的数据)。这个路径而后被Cascading TemplateTap使用,这是一个特殊的Tap类型,它能够根据Tuple对象值把元组数据流动态输出到不一样的路径位置。
本例中,“path”值被TemplateTap用来建立最终输出路径。
开发者也建立了第四个SubAssembly类型对象——它用于在单元测试时应用Cascading Assertion(断言)类。这些断言用来复查规则组件和解析SubAssembly作的工做。
在例16-5的单元测试中,咱们看到partitioner没有被检测,可是它被放入另一个这里没有展现的集成测试中了。
例16-5. Flow单元测试
public void testLogParsing() throws IOException
{
Hfs source = new Hfs(new TextLine(new Fields("line")), sampleData);
Hfs sink =
new Hfs(new TextLine(), outputPath + "/parser", SinkMode.REPLACE);
Pipe pipe = new Pipe("parser");
// split "line" on tabs
pipe = new Each(pipe, new Fields("line"), new RegexSplitter("\t"));
pipe = new LogParser(pipe);
pipe = new LogRules(pipe);
// testing only assertions
pipe = new ParserAssertions(pipe);
Flow flow = new FlowConnector().connect(source, sink, pipe);
flow.complete(); // run the test flow
// verify there are 98 tuples, 2 fields, and matches the regex pattern
// for TextLine schemes the tuples are { "offset", "line }
validateLength(flow, 98, 2, Pattern.compile("^[0-9]+(\\t[^\\t]*){19}$"));
}
针对集成和部署,许多Cascading内置属性均可以使该系统和外部系统更容易集成,并进行更大规模的处理工做。
在生产环境中运行时,全部的SubAssembly对象都链接起来并规划到一个Flow对象里,可是除了有源和目标Tap对象以外,咱们也设计了trap(捕捉)Tap类型(图16-20)。一般,当远程的Mapper或Reducer任务的操做抛出一个异常的时候,Flow对象就会失败并杀死它管理的全部MapReduce做业。当一个Flow有trap的时候,全部的异常都会被捕捉而且形成异常的数据信息会被保存到当前这个捕捉程序对应的Tap对象里。而后能够在不终止当前Flow的状况下,继续处理下一个Tuple对象。有时你想让程序在出现错误的时候就中止,但在这里,ShareThis开发者知道在生产系统运行的时候,他们能同时回览并查看“失败”的数据,而后更新其单元测试。丢失几个小时的处理时间比丢失几个坏记录数据更糟糕。
使用Cascading的事件监听器,Amazon SQS可被集成进来。当一个Flow结束的时候,系统就发送一条消息来通知其余系统它们已经能够从 Amazon S3上获取准备好的数据了。当Flow处理失败的时候,会有不一样的消息发送,向其余的进程报警。
其他的位于不一样的独立集群的下行数据流进程将在中断的日志处理管道位置处开始处理。如今日志处理管道一天运行一次,所以没有必要让100个节点的集群闲着运转23个小时。所以咱们是每24小时执行一次终止和启用操做。
未来,在小型的集群上根据业务需求,增长运行间歇期到每6个小时一次或1小时一次都是很是简单的。其余的集群系统能够独立地根据各自负责的业务须要以不一样的间隔期启用或关闭。例如,网络数据爬取组件(使用Bixo,它是EMI和ShareThis开发的基于Cascading的网络数据爬取工具)能够在一个小型集群上与Hypertable集群协做连续运转。这种随需应变的模型在Hadoop上运行良好,每一个集群都能把工做负载调节到它指望处理的数量级。
图16-20. ShareThis日志处理Flow
总结
对于处理和协调跨不一样架构组件的数据的移动这个问题,Hadoop是一个很是强大的平台。它惟一的缺点是它的主要计算模型是MapReduce。
Cascading的目标是(不用MapRedue模式来考虑设计方案的状况下)帮助开发者经过使用一个逻辑定义良好的API来快速而简单地创建强大的应用程序,而同时又把提升数据分布、复制、分布式处理管理的性能和程序活性的工做都留给了Hadoop。