问题是如何使用Hadoop Streaming(仅限)在Hadoop中链接作业。
答案 0 :(得分:1)
这个答案就是提问者实际提出的问题。我通常会引用它,但会因为它太大而弃权。
这是一个关于如何使用Hadoop Streaming(目前为1.0.3)链接两个或多个流媒体作业的文档。
为了理解将进行链接并能够进行链接的最终代码 写任何其他连锁工作一些初步但实用的理论是必需的。
首先,Hadoop的工作是什么? Hadoop的工作是
hadoopJob = Configuration + Execution
其中,
配置:使执行成为可能的所有设置。
执行:完成所需任务的可执行文件或脚本文件集。换句话说,我们的任务的地图和减少步骤。
Configuration = hadoopEnvironment + userEnvironment
其中,
hadoopEnvironment :是Hadoop一般环境的设置。此常规环境是从资源(即位于$ HADOOP_HOME / conf目录中的xml文件)定义的。例如,一些资源是core-site.xml,mapred-site.xml和hadoop-site.xml,它们分别定义了hdfs临时目录,作业跟踪器和集群节点数。
userEnvrironment :是运行作业时用户指定的参数。在Hadoop中,这些参数称为选项。
userEnvironment = genericOptions + streamingOptions
其中,
genericOptions :它们是通用的,因为它们可以独立于作业而吸引每个流媒体作业。它们由GenericsOptionsParser处理。
streamingOptions :它们是特定于工作的,因为它们会吸引某项工作。例如,每个作业都有自己的输入和输出目录或文件。它们由StreamJob处理。
示意性地,
hadoopJob
/\
/ \
/ \
/ \
/ \
Configuration Execution
/\ |
/ \ |
/ \ executable or script files
/ \
/ \
/ \
hadoopEnvironment userEnvironment
| /\
| / \
| / \
$HADOOP_HOME/conf / \
/ \
genericOptions streamingOptions
| |
| |
GenericOptionsParser StreamJob
正如任何人都可以看到的,以上所有的一系列配置。其中一部分是 对于集群管理员(hadoopEnvironment)而另一部分是 对于群集的用户(userEnvironment)。总之,一份工作主要是一份工作 如果我们暂时忘记执行,那么在抽象级别配置 一部分。
我们的代码应该照顾以上所有内容。现在我们准备编写代码。
首先,代码级别的Hadoop作业是什么?这是一个jar文件。每当我们 提交作业我们提交一个带有一些命令行参数的jar文件。例如 当我们运行单个流作业时,我们执行命令
$HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/hadoop/contrib/streaming/hadoop-streaming-1.0.3.jar -D mapred.reduce.tasks=1 -mapper m.py -reducer r.py -input /in.txt -output /out/
其中,
我们的工作是hadoop-streaming-1.0.3.jar
使用命令行参数-D mapred.reduce.tasks = 1 -mapper m.py -reducer r.py -input /in.txt -output / out /
在这个罐子里面,我们的课程可以妥善处理所有事情。
因此,我们打开一个新的java文件,比如TestChain.java,
// import everything needed
public class TestChain
{
//code here
public static void main( String[] args) throws Exception
{
//code here
}//end main
}//end TestChain
要处理hadoopEnvironment,我们的类应该继承类Configured。 Class Configured使我们能够访问Hadoop的环境和参数,即前面提到的资源。资源是包含名称/值对形式的数据的xml文件。
展望未来,每个界面或多或少都是外部世界与世界想要完成的任务之间的媒介。也就是说,界面是我们用来完成任务的工具。因此,我们的班级是一个工具。为此,我们的类必须实现Tool接口,该接口声明方法run()。当接口实现时,此方法定义了我们的工具行为。最后,为了使用我们的工具,我们使用了类ToolRunner。 ToolRunner通过类GenericOptionsParser帮助处理来自userEnvironment的genericOptions。
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.ToolRunner;
import org.apache.hadoop.util.Tool;
// import everything else needed
public class TestChain extends Configured implements Tool
{
public int run( String[] args) throws Exception
{
//code here
return 0;
}//end run
public static void main( String[] args) throws Exception
{
// ToolRunner handles generic command line options
int res = ToolRunner.run( new Configuration(), new TestChain(), args);
System.exit( res);
}//end main
}//end TestChain
要完成图片,方法run()也称为驱动程序,设置作业,包括作业的初始化和配置。请注意,我们通过第一个参数' new Configuration'来委托ToolRunner处理hadoopEnnvironment。方法ToolRunner.run()。
到目前为止我们做了什么?我们只设置了工具运行的环境。 现在我们必须定义我们的工具,即进行链接。
就每个连锁工作都是一个流媒体工作而言,我们就是这样创建的。我们使用类StreamJob的StreamJob.createJob(String [] args)方法来完成此操作。字符串的args矩阵包含"命令行"每个工作的论据。这些命令行参数引用userEnvironment的streamingOptions(特定于作业)。此外,这些参数采用参数/值对的形式。例如,如果我们的作业有in.txt文件作为输入,/ out /作为输出目录,m.py作为mapper,r.py作为reducer,那么,
String[] example = new String[]
{
"-mapper" , "m.py"
"-reducer" , "r.py"
"-input" , "in.txt"
"-output" , "/out/"
}
你必须小心两件事。首先," - "有必要的。这是将参数与值区分开来的小事。这里,mapper是一个参数,m.py是它的值。差异可从" - "中理解。其次,如果你在左边和#34;之间添加一个空格。和" - "参数,然后忽略此参数。如果我们有" -mapper"然后" -mapper"不被视为参数。当StreamJob解析args矩阵时,查找参数/值对。最后一点,回想一下,工作大致是一种配置。我们期待这样,StreamJob.creatJob()应该返回一个配置或类似的配置。实际上StreamJob.createJob()返回一个JobConf对象。简而言之,JobConf对象是Hadoop理解并可以执行的特定mapreduce作业的描述。
假设我们有三个工作要链,
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.ToolRunner;
import org.apache.hadoop.streaming.StreamJob;
// import everything else needed
public class TestChain extends Configured implements Tool
{
public int run( String[] args) throws Exception
{
String[] job1 = new String[]
{
"-mapper" , "m1.py"
"-reducer" , "r1.py"
"-input" , "in1.txt"
"-output" , "/out1/"
}
JobConf job1Conf = new StreamJob.createJob( job1);
//code here
String[] job2 = new String[]
{
"-mapper" , "m2.py"
"-reducer" , "r2.py"
"-input" , "in2.txt"
"-output" , "/out2/"
}
JobConf job2Conf = new StreamJob.createJob( job2);
//code here
String[] job3 = new String[]
{
"-mapper" , "m3.py"
"-reducer" , "r3.py"
"-input" , "in3.txt"
"-output" , "/out3/"
}
JobConf job3Conf = new StreamJob.createJob( job3);
//code here
return 0;
}//end run
public static void main( String[] args) throws Exception
{
// ToolRunner handles generic command line options
int res = ToolRunner.run( new Configuration(), new TestChain(), args);
System.exit( res);
}//end main
}//end TestChain
此时我们设置了工具运行的环境 我们定义了它的行为。但是,我们还没有采取行动。 ToolRunner不是 足够。 ToolRunner,作为一个整体运行我们的工具。它没有经营个人 连锁工作。我们必须这样做。
有两种方法可以做到这一点。第一种方法是使用JobClient,第二种方法是使用JobControl。
使用JobClient,我们将链式作业作为一个序列运行,一个作业依次运行 为每个作业调用JobClient。运行每个单独作业的方法是 JobClient.runJob(jobtorun),其中jobtorun是JobConf对象。
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.ToolRunner;
import org.apache.hadoop.streaming.StreamJob;
public class TestChain extends Configured implements Tool
{
public int run( String[] args) throws Exception
{
String[] job1 = new String[]
{
"-mapper" , "m1.py"
"-reducer" , "r1.py"
"-input" , "in1.txt"
"-output" , "/out1/"
}
JobConf job1Conf = new StreamJob.createJob( job1);
JobClient.runJob( job1Conf);
String[] job2 = new String[]
{
"-mapper" , "m2.py"
"-reducer" , "r2.py"
"-input" , "in2.txt"
"-output" , "/out2/"
}
JobConf job2Conf = new StreamJob.createJob( job2);
JobClient.runJob( job2Conf);
String[] job3 = new String[]
{
"-mapper" , "m3.py"
"-reducer" , "r3.py"
"-input" , "in3.txt"
"-output" , "/out3/"
}
JobConf job3Conf = new StreamJob.createJob( job3);
JobClient.runJob( job3Conf);
return 0;
}//end run
public static void main( String[] args) throws Exception
{
// ToolRunner handles generic command line options
int res = ToolRunner.run( new Configuration(), new TestChain(), args);
System.exit( res);
}//end main
}//end TestChain
使用JobClient的这种方式的一个优点是作业进度打印在标准输出上。
JobClient的一个缺点是它无法处理作业之间的依赖关系。
使用JobControl,所有连锁工作都是一组工作的一部分。在这里,每个作业都在该组的框架中执行。这意味着必须首先在组中添加每个链作业,然后该组是运行的组。该组是FIFO或组中每个作业的执行遵循FCFS(先到先服务)模式。使用方法JobControl.addJob(jobtoadd)将每个作业添加到组中。
JobControl可以通过方法x.addDependingJob(y)处理依赖关系 工作x取决于工作y。这意味着,作业x无法运行直到作业y完成。 如果作业x依赖于作业y和z,并且z独立于y,那么 使用x.addDependingJob(y)和x.addDependingJob(z)我们可以表达这些 的依赖关系。
JobControl与JobClient相矛盾,"工作"使用Job个对象。当我们打电话 例如x.addDependingJob(y)方法,x,y是Job对象。同样的 对于JobControl.addJob(jobtoadd),jobtoadd是一个Job对象。每个Job对象都是 从JobConf对象创建。回到我们的代码,
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.jobcontrol.Job;
import org.apache.hadoop.mapred.jobcontrol.JobControl;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.ToolRunner;
import org.apache.hadoop.streaming.StreamJob;
public class TestChain extends Configured implements Tool
{
public int run( String[] args) throws Exception
{
//TestChain below is an arbitrary name for the group
JobControl jobc = new JobControl( "TestChain");
String[] job1 = new String[]
{
"-mapper" , "m1.py"
"-reducer" , "r1.py"
"-input" , "in1.txt"
"-output" , "/out1/"
}
JobConf job1Conf = new StreamJob.createJob( job1);
Job job1 = new Job( job1conf);
jobc.addJob( job1);
String[] job2 = new String[]
{
"-mapper" , "m2.py"
"-reducer" , "r2.py"
"-input" , "in2.txt"
"-output" , "/out2/"
}
JobConf job2Conf = new StreamJob.createJob( job2);
Job job2 = new Job( job2conf);
jobc.addJob( job2);
String[] job3 = new String[]
{
"-mapper" , "m3.py"
"-reducer" , "r3.py"
"-input" , "/out2/par*"
"-output" , "/out3/"
}
JobConf job3Conf = new StreamJob.createJob( job3);
Job job3 = new Job( job3conf);
job3.addDependingJob( job2);
jobc.addJob( job3);
//code here
return 0;
}//end run
public static void main( String[] args) throws Exception
{
// ToolRunner handles generic command line options
int res = ToolRunner.run( new Configuration(), new TestChain(), args);
System.exit( res);
}//end main
}//end TestChain
在上面的代码中,请注意job3依赖于job2。正如您可以看到job3的输入 是job2的输出。这个事实是一种依赖。 job3等到job2完成。
到目前为止,我们刚刚在集团中添加了连锁工作并描述了他们的依赖性。 我们最后需要做一组工作。
蛮力说只需调用方法JobControl.run()。虽然,这个 方法有效,这是有问题的。当连锁工作完成后,整个 工作仍然永远存在。一种正常工作的方法是定义一个新方法 我们的工作线程已经存在的线程(当作业运行时)。 然后我们可以等到连锁工作完成然后退出。与此同时 连锁工作'例如,我们可以要求执行作业执行信息 如果工作处于无效状态,我们可以找到多少工作 就是这个。
这种使用JobControl的方法的一个优点是可以处理很多 作业之间可能存在的依赖关系。
JobControl的一个缺点是作业进度没有印在 标准输出,它没有直接呈现。工作是否失败或 成功,打印没有任何用处。您必须通过Hadoop的Web UI进行检查 或者在下面的while循环中添加一些代码来跟踪作业的状态或者其他什么 需要。最后,
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.jobcontrol.Job;
import org.apache.hadoop.mapred.jobcontrol.JobControl;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.ToolRunner;
import org.apache.hadoop.streaming.StreamJob;
public class TestChain extends Configured implements Tool
{
public int run( String[] args) throws Exception
{
//TestChain below is an arbitrary name for the group
JobControl jobc = new JobControl( "TestChain");
String[] job1 = new String[]
{
"-mapper" , "m1.py"
"-reducer" , "r1.py"
"-input" , "in1.txt"
"-output" , "/out1/"
}
JobConf job1Conf = new StreamJob.createJob( job1);
Job job1 = new Job( job1conf);
jobc.addJob( job1);
String[] job2 = new String[]
{
"-mapper" , "m2.py"
"-reducer" , "r2.py"
"-input" , "in2.txt"
"-output" , "/out2/"
}
JobConf job2Conf = new StreamJob.createJob( job2);
Job job2 = new Job( job2conf);
jobc.addJob( job2);
String[] job3 = new String[]
{
"-mapper" , "m3.py"
"-reducer" , "r3.py"
"-input" , "/out2/par*"
"-output" , "/out3/"
}
JobConf job3Conf = new StreamJob.createJob( job3);
Job job3 = new Job( job3conf);
job3.addDependingJob( job2);
jobc.addJob( job3);
Thread runjobc = new Thread( jobc);
runjobc.start();
while( !jobc.allFinished())
{
//do whatever you want; just wait or ask for job information
}
return 0;
}//end run
public static void main( String[] args) throws Exception
{
// ToolRunner handles generic command line options
int res = ToolRunner.run( new Configuration(), new TestChain(), args);
System.exit( res);
}//end main
}//end TestChain
本节讨论可能发生的一些错误。在下面的错误消息中有一个类OptimizingJoins。这个类只是为了演示各种错误而与本讨论无关。
尝试编译时不存在包。
这是类路径的问题。编译就好(例如添加hadoop-streaming-1.0.3.jar包),
javac -classpath /usr/local/hadoop/contrib/streaming/hadoop-streaming-1.0.3.jar:/usr/local/hadoop/hadoop-core-1.0.3.jar TestChain.java
并添加任何缺失的包。
java.lang.NoClassDefFoundError:org / apache / hadoop / streaming / StreamJob
总错误是
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/hadoop/streaming/StreamJob
at OptimizingJoins.run(OptimizingJoins.java:135)
at org.apache.hadoop.util.ToolRunner.run(ToolRunner.java:65)
at OptimizingJoins.main(OptimizingJoins.java:248)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.apache.hadoop.util.RunJar.main(RunJar.java:156)
Caused by: java.lang.ClassNotFoundException: org.apache.hadoop.streaming.StreamJob
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
... 8 more
这是我们的jar文件的清单文件的问题。当我们编译我们的工作时 上面的方式,一切都很好。 Java编译器可以找到它需要的东西但 当我们通过命令在Hadoop中运行我们的工作时
$HADOOP_HOME/bin/hadoop jar /home/hduser/TestChain.jar TestChain
然后运行我们jar的JVM找不到StreamJob。解决这个问题,当我们 创建jar文件,我们在jar中放入一个包含类的清单文件 - StreamJob的路径。实际上,
MANIFEST.MF
Manifest-Version: 1.0
Class-Path: /usr/local/hadoop/contrib/streaming/hadoop-streaming-1.0.3.jar
Created-By: 1.7.0_07 (Oracle Corporation)
请注意,MANIFEST.MF文件始终以空行结束。我们的MANIFEST.MF 文件有4行,而不是3.然后我们创建jar文件,比如,
jar cmf META-INF/MANIFEST.MF TestChain.jar TestChain.class
ERROR streaming.StreamJob:无法识别的选项:-D
发生此错误是因为StreamJob无法解析-D选项。 StreamJob可以解析 只有流媒体,特定于工作的选项,-D是一个通用选项。
这个问题有两个解决方案。第一种解决方案是使用-jobconf 选项而不是-D。第二种解决方案是通过a解析-D选项 GenericOptionsParser对象。在第二个解决方案当然你必须删除 StreamJob.createJob()args。中的-D选项。
举一个例子,"清洁"第二种解决方案的代码实现是,
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.ToolRunner;
import org.apache.hadoop.streaming.StreamJob;
public class TestChain
{
public class Job1 extends Configured implements Tool
{
public int run( String[] args) throws Exception
{
String[] job1 = new String[]
{
"-mapper" , "m1.py"
"-reducer" , "r1.py"
"-input" , "in1.txt"
"-output" , "/out1/"
}
JobConf job1Conf = new StreamJob.createJob( job1);
JobClient.runJob( job1Conf);
return 0;
}//end run
}
public class Job2 extends Configured implements Tool
{
public int run( String[] args) throws Exception
{
String[] job2 = new String[]
{
"-mapper" , "m2.py"
"-reducer" , "r2.py"
"-input" , "in2.txt"
"-output" , "/out2/"
}
JobConf job2Conf = new StreamJob.createJob( job2);
JobClient.runJob( job2Conf);
return 0;
}//end run
}
public class Job3 extends Configured implements Tool
{
public int run( String[] args) throws Exception
{
String[] job3 = new String[]
{
"-mapper" , "m3.py"
"-reducer" , "r3.py"
"-input" , "in3.txt"
"-output" , "/out3/"
}
JobConf job3Conf = new StreamJob.createJob( job3);
JobClient.runJob( job3Conf);
return 0;
}//end run
}
public static void main( String[] args) throws Exception
{
TestChain tc = new TestChain();
//Domination
String[] j1args = new String[]
{
"-D", "mapred.output.key.comparator.class=org.apache.hadoop.mapred.lib.KeyFieldBasedComparator",
"-D", "mapred.text.key.comparator.options=-k1,1" ,
"-D", "mapred.reduce.tasks=1"
};
// Let ToolRunner handle generic command-line options
int j1res = ToolRunner.run( new Configuration(), tc.new Job1(), j1args);
//Cost evaluation
String[] j2rgs = new String[]
{
"-D", "mapred.reduce.tasks=12 " ,
"-D", "mapred.text.key,partitioner.options=-k1,1"
};
// Let ToolRunner handle generic command-line options
int j2res = ToolRunner.run( new Configuration(), tc.new Job2(), j2args);
//Minimum Cost
String[] j3args = new String[]
{
"-D", "mapred.reduce.tasks=1"
};
// Let ToolRunner handle generic command-line options
int j3res = ToolRunner.run( new Configuration(), tc.new Job1(), j3args);
System.exit( mres);
}
}//end TestChain
在上面的代码中,我们定义了一个封装了的全局类TestChain 连锁工作。然后我们定义每个单独的链作业,即我们定义其运行方法。 每个链作业都是一个继承了Configured和implements Tool的类。最后, 从TestChain的主要方法,我们按顺序运行每个作业。请注意,在运行之前 我们定义其通用选项的任何连锁工作。
编译
javac -classpath /usr/local/hadoop/contrib/streaming/hadoop-streaming-1.0.3.jar:/usr/local/hadoop/hadoop-core-1.0.3.jar TestChain.java
罐
jar cmf META-INF/MANIFEST.MF TestChain.jar TestChain.class TestChain\$Dom.class TestChain\$Cost.class TestChain\$Min.class
ERROR security.UserGroupInformation:PriviledgedActionException as:hduser cause:org.apache.hadoop.mapred.InvalidInputException:输入模式hdfs:// localhost:54310 / user / hduser / whateverFile匹配0个文件 < / p>
当我们使用JobControl时会发生此错误。例如,如果作业输入上一个作业的输出,那么如果此输入 - 输出文件不存在,则会发生此错误。 JobControl在&#34; parallel&#34;中运行所有独立的作业,而不是像JobClient那样逐个运行。因此,Jobcontrol尝试运行其输入文件不存在的作业,因此失败。
为了避免这种情况,我们声明使用x.addDependingJob(y)在这两个作业之间存在依赖关系,作业x取决于作业y。现在,JobControl不会尝试在并行的依赖作业中运行。