Hadoop Streaming:Chaining Jobs

时间:2012-10-13 11:18:03

标签: hadoop streaming chaining

问题是如何使用Hadoop Streaming(仅限)在Hadoop中链接作业。

1 个答案:

答案 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。运行每个单独作业的方法是 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

使用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不会尝试在并行的依赖作业中运行。