【Hadoop】19-MapReduce的工作流

至此,你已经知道MapReduce应用开发的机制了。我们目前还未考虑如何将数据处理问题转化成MapReduce模型。前面的数据处理都用来解决十分简单的问题(如在指定年份找到最高气温值的记录)。如果处理过程更复杂,这种复杂度一般是因为有更多的MapReduce作业,而不是更复杂的map和reduce函数。换而言之,通常是增加更多的作业,而不是增加作业的复杂度。

对于更复杂的问题,可考虑使用比MapReduce更高级的语言,如Pig、hive、cascading、crunch或Spark。一个直接的好处是:有了它之后,就用不着处理到MapReduce作业的转换,而是集中精力分析正在执行的任务。

1.将问题分解成MapReduce作业

让我们看一个更复杂的问题,我们想把它转换成MapReduce工作流。
假设我们想找到每个气象台每年每天的最高气温记录的均值。例如,要计算029070~99999气象台的1月1日的每日最高气温的均值,我们将从这个气象台的1901年1月1日,1902年1月1日,直到2000年的1月1日的气温中找出每日最高气温的均值。

我们如何使用MapReduce来计算它呢?计算自然分解为下面两个阶段。

  1. 计算每对station-date的每日最高气温。本例中的MapReduce程序是最高气温程序的一个变种,不同之处在于本例中的键是一个综合的station-date对,而不只是年份。
  2. 计算每个station-day-month键的每日最高气温的均值。mapper从上一个作业得到输出记录(station·date,最高气温值),丢掉年份部分,将其值投影到记录(station-day-month,最高气温值)。然后reducer为每个station-day-month键计算最高气温值的均值。
第一阶段的输出看上去就是我们想要的气象台的信息。范例中的mean_max_daily_temp.sh脚本提供了Hadoop Streaming的一个实现:
029070-99999 19010101 0
029070-99999 19020101 -94
...
前两个字段形成键,最后一列是指定气象台和日期所有记录中的最高气温。第二阶段计算这些年份中每日最高气温的平均值:
029070-99999 0101 -68

以上是气象台029070-99999的在整个世己中1月1日的日均最高气温为一6.8℃。只用一个MapReduce过程就能完成这个计算,但是它可能会让部分程序员花更多精力。

一个作业可以包含多个(简单的)MapReduce步骤,这样整个作业由多个可分解的、可维护的mapper和reducer组成。相对于我们已经做的,mapper和reducer完全可以进一步分解。mapper一般执行输人格式解析、投影(选择相关的字段)和过滤(去掉无关记录)。在前面的mapper中,我们在一个mapper中实现了所有这些函数。然而,还可以将这些函数分割到不同的mapper,然后使用Hadoop自带的ChainMapper类库将它们连接成一个mapper。结合使用ChainReducer,你可以在一个MapReduce作业中运行一系列的mapper,再运行一个reducer和另一个mapper链。

2.关于JobControl

当MapReduce工作流中的作业不止一个时,问题随之而来:如何管理这些作业按顺序执行?有几种方法,其中主要考虑是否有一个线性的作业链或一个更复杂的作业有向无环图(DAG,directed acyclic graph)。

对于线性链表,最简单的方法是一个接一个地运行作业,等前一个作业运行结束后再运行下一个:
JobClient.runJob(conf1);
JobClient.runJob(conf2);
如果一个作业失败,run.job()方法就抛出一个I10Exception,这样一来,管道中后面的作业就无法执行。根据具体的应用程序,你可能想捕获异常,并清除前一个作业输出的中间数据。

这种方法类似新的MapReduce API,除了需要Job上的waitForCompletion()方法的布尔返回值:true表示作业成功,而false表示失败。

对于比线性链表更复杂的结构,有相关的类库可以帮助你合理安排工作流。它们也适用于线性链表或一次性作业。最简单的是org.apache.hadoop.mapreduce.jobcontrol包中的JobControl类。在org.apache.hadoop.mapred.jobcontrol包中也有一个等价的类。JobCcontrol的实例表示一个作业的运行图,你可以加人作业配置,然后告知JobControl实例作业之间的依赖关系。在一个线程中运行JobControl时,它将按照依赖顺序来执行这些作业。也可以查看进程,在作业结束后,可以查询作业的所有状态和每个失败相关的错误信息。如果一个作业失败,JobControl将不执行与之有依赖关系的后续作业。

3.关于Apache Oozie

Apache Oozie是一个运行工作流的系统,该工作流由相互依赖的作业组成。Oozie由两部分组成:一个工作流引擎,负责存储和运行由不同类型的Hadoop作业(MapReduce,Pig,Hive等)组成的工作流;一个coordinator引擎,负责基于预定义的调度策略及数据可用性运行工作流作业。Oozie的设计考虑到了可扩展性,能够管理Hadoop集群中数千工作流的及时运行,每个工作流的组成作业都可能有好几十个。

由于在运行工作流中成功的那一部分时不会浪费任何时间,因此在Oozie中更易处理失败工作流的重运行。任何一个管理过复杂批处理系统的人都知道,由于宕机或故障而丢失作业后再跟上处理节奏是多么困难,因此他们都会欣赏Oozie的这个特性。(更进一步,coordinator应用代表了单个的数据管线可以被打包在一起,然后作为一个单元一起运行。)

不同于在客户端运行并提交作业的JobControl,Ooize作为服务器运行,客户端提交一个立即或稍后执行的工作流定义到服务器。在Ooize中,工作流是一个由动作(action)节点和控制流节点组成的DAG(有向无环图)。动作节点执行工作流任务,例如在HDFS中移动文件,运行MapReduce、streaming、Pig或Hive作业,执行Sqoop导人,又或者是运行shell脚本或Java程序。控制流节点通过构建条件逻辑(不同执行分支的执行依赖于前一个动作节点的输出结果)或并行执行来管理活动之间的工作流执行情况。当工作流结束时,Oozie通过发送一个HTTP的回调向客户端通知工作流的状态。还可以在每次进人工作流或退出一个动作节点时接收到回调。

3.1、定义Oozie工作流

工作流定义是使用Hadoop Process Difinition Language以XML格式来书写,这个规范可在Oozie网站(http://oozie.apache.org/)找到。

范例展示了一个运行单个MapReduce作业的简单OoZie工作流定义。

范例,用来运行求最高温度的MapReduce作业的Oozie工作流定义:

<workflow-app xmlns="uri:00zie:workflow:0.1" name="max-temp-workflow">
    <start to="max-temp-mr"/>
    <action name="max-temp-mr">
        <map-reduce>
            <job-tracker>${resourceManager}</job-tracker>
            <name-node>${nameNode}</name-node>
            <prepare>
                <delete path="${nameNode}/user/${wf:user()}/output"/>
            </prepare>
            <configuration>
                <property>
                    <name>mapred.mapper.new-api</name>
                    <value>true</value>
                </property>
                <property>
                    <name>mapred.reducer.new-api</name>
                    <value>true</value>
                </property>
                <property>
                    <name>mapreduce.job.map.class</name>
                    <value>MaxTemperatureMapper</value>
                </property>
                <property>
                    <name>mapreduce.job.combine.class</name>
                    <vaIue>MaxTemperatureReducer</value>
                </propertY>
                <property>
                    <name>mapreduce.job.reduce.class</name>
                    <value>MaxTemperatureReducer</value>
                </property>
                <property>
                    <name>mapreduce.job.output.key.class</name>
                    <value>org.apache.hadoop.io.Text</value>
                </property>
                <property>
                    <name>mapreduce.job.output.value.class</name>
                    <value>org.apache.hadoop.io.IntWritable</value>
                </property>
                <property>
                    <name>mapreduce.input.fileinputformat.inputdir</name>
                    <value>/user/${wf:user()}/input/ncdc/micro</value>
                </property>
                <property>
                    <name>mapreduce.output.fileoutputformat.outputdir</name>
                    <value>/user/${wf:user()}/output</value>
                </property>
            </configuration>
        </map-reduce>
        <ok to="end"/>
        <error to="fail"/>
    </action>
    <kill name="fail">
        <message>MapReduce failed,error message[${wf:errorMessage(wf:IastErrorNode())}]</message>
    </kill>
    <end name="end"/>
</workflow-app>
这个工作流有三个控制流节点和一个动作节点:一个start控制节点、一个map-reduce动作节点、一个kill控制节点和一个end控制节点。节点及其之间的转换如下图所示。
每个工作流都必须有一个start节点和一个end节点。当工作流作业开始时,它转移到有start节点指定的节点上体例中max-temp-mr动作)。当一个工作流作业转移到end节点时就意味着它成功完成了。然而,如果一个工作流作业转移到了kill节点,那么就被认为失败了并且报告在工作流定义中的message元素指定的错误消息。

这个工作流定义文件的大部分都是指定map-reduce动作。前两个元素(job-tracker和name.node)用于指定提交作业的YARN资源管理器(或Hadoop1中的jobtracker)和输人输出数据的namenode(实际上是一个Hadoop文件系统的URI)。两者都被参数化,使得工作流定义不受限于特定的集群,更有利于测试。这些参数在提交时指定为工作流属性,我们稍后会看到。


注意:与其名称无关,job-tracker元素是用来指定YARN资源管理器地址和端口的。

可选项prepare元素在MapReduce作业之前运行并用于目录删除(在需要的时候也可以创建目录,但这里没有指明)0通过确保输出目录在运行作业之前处于一致的状态,如果作业失败的话,Oozie也可以安全地重新执行。运行MapReduce作业是在configuration元素中设定的,通过为Hadoop配置的名值对来设置嵌套的元素。可以把MapReduce配置部分看作本书中其他地方运行MapReduce程序使用的驱动类的一个替代。我们在工作流的定义中的几个地方利用了JSP Expression Language(EL)语法。Oozie提供了一组与工作流交互的函数。例如,${wf:user()}返回开始当前工作流作业的用户名,我们用它来指定正确的文件系统路径。Oozie规范中列出所有Oozie支持的EL函数。

3.2打包和配置Oozie工作流应用
工作流应用由工作流定义和所有运行它所需的资源(例如MapReduce JAR文件、Pig脚本等)构成。应用必须遵循一个简单的目录结构,并在HDFS上配置,这样它们才能被Oozie访问。对于这个工作流应用,我们将所有的文件放到基础目录max-temp-workflow中,如下所示:


工作流定义文件workflow.xml必须在该目录的顶层出现。包含应用的MapReduce类的JAR文件放在lib目录中。

遵循这种布局的工作流应用能通过很多合适的工具创建,例如Ant或Maven;可以在本书附带的代码中找到样例。一旦创建应用,就使用正规的Hadoop工具将它复制到HDFS。命令如下:

hadoop fs -put hadoop-examples/target/max-temp-workflow max-temp-workflow

3.3运行Oozie工作流作业

接下来,我们看看如何为刚刚上载的应用运行一个工作流作业。为此,使用oozie命令行工具,它是用于和Oozie服务器通信客户端程序。方便起见,我们输出OOZIE_URL环境变量来告诉oozie命令使用哪个Oozie服务器(这里我们使用本地运行的服务器):

export OOZIE_URL="http://localhost:11000/oozie"
oozie工具有很多子命令(输人oozie help可得到这些子命令的列表),但我们将调用带有-run选项的job子命令来运行工作流作业:
oozie job -config ch06-mr-dev/src/main/resources/max-temp-workflow.properties -run
job:0000001-140911933236814-oozie-oozi-W
-config选项设定本地Java属性文件,它包含工作流XML文件里参数的定义(这里是nameNode和resourceManager)与oozie.wf.application.path,后者告知Oozi HDFS中工作流应用的位置。属性文件的内容如下:
nameNode=hdfs://localhost:8029
resourceManager=localhost:8032
oozie.wf.application.path=${nameNode}/user/${user.name}/max-temp-workflow

为了得到工作流作业的状态信息,要使用-info选项,指定由前面运行的命令打印的作业ID(输人oozie job将得到所有作业的列表):

oozie job -info 0000001-140911933236814-oozie-oozi-W

输出显示如下状态:RUNNING、KILLED或者SUCCEDED。通过Oozie的网页UI即http://localhost:11000/oozie,你也可以找到这些信息。

作业成功后,可以通过以下常用方法检查结果:

hadoop fs -cat output/part-*
1949 111
1950 22
这个例子只是简单展示了如何写Oozie工作流。Oozie网站上的文档介绍了如何创建更复杂的工作流以及如何写和运行coordinator作业。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值