[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录


原文地址:
http://note.youdao.com/share/?id=c0742ed1005880a85075a615abddf474&type=note
http://www.xuebuyuan.com/1875732.html
本文主要讲解三个问题:
      1 使用Java编写MapReduce程序时,如何向map、reduce函数传递参数。
      2 使用Streaming编写MapReduce程序(C/C++, Shell, Python)时,如何向map、reduce脚本传递参数
      3 使用Streaming编写MapReduce程序(C/C++, Shell, Python)时,如何向map、reduce脚本传递文件或文件夹
         (1) streaming 加载本地单个文件
          (2) streaming 加载本地多个文件
         (3) streaming 加载本地目录
         (4) streaming编程时在mapreduce脚本中读 hdfs 文件
         (5) streaming编程时在mapreduce脚本中读 hdfs 目录

1.  Java编写MapReduce程序时,如何向map、reduce函数传递参数
我开始使用如下方式进行传递.
在主类中声明两个静态变量, 然后在 main 函数中给变量赋值, 试图在 map和reduce函数中获得变量的值。
代码结构类似如下:
[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第1张图片
提交到集群运行发现在 map 和 reduce函数中, 静态变量MaxScore的值始终是初值1。
于是试图在主类的静态区中给变量赋值 (因为静态区中的代码比main中的代码要先执行), 仍是不成功, MaxScore的值始终是初值1。
将上述代码在 单机hadoop上运行, 结果正常, map 函数中能获得变量的值。
思考是这个原因: 在提交作业到hadoop集群后,mapper类和reducer类就到各个 tasktracker上去运行了, 与主类独立, 不能交互。
因此,上述往 map 和 reduce 函数传参数的方法实在太天真。
于是想到其它一些方法: 例如将参数写入hdfs文件中, 然后在 mapper 和 reducer 类的 run方法中读取文件, 并将值读到相应变量,这是可行的,但是方法较复杂,代码如下:
[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第2张图片
上述方法尽管可用, 但是不是常规方法, 下面介绍常用的方法:
(1) 通过 Configuration 来传递参数
在main函数中调用set方法设置参数, 例如:
[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第3张图片
在mapper中通过上下文context来获取当前作业的配置, 并获取参数, 例如:
[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第4张图片
注: context 很有用, 能获取当前作业的大量信息,例如上面就获取了任务ID.

(2) 利用org.apache.hadoop.io.DefaultStringifier类

示例:

main中:

    Configuration conf = new Configuration();

    Text maxscore = new Text("12989");

    DefaultStringifier.store(conf, maxscore ,"maxscore");

这样,Text对象maxscore就以“maxscore”作为key存储在conf对象中了,然后在map和reduce函数中调用load的方法便可以把对象读出。

mapper获取:

    Configuration conf = context.getConfiguration()

    Text out = DefaultStringifier.load(conf, "maxscore", Text.class);

   需要说明的是,这个需要传递的对象必须要先实现序列化的接口,Hadoop的序列化是通过Writable接口来实现的

(2) 参考自:http://blog.sina.com.cn/s/blog_6b7cf18f0100x9jg.html


2.  编写 Streaming 程序时,如何向map、reduce函数传递参数

可以通过 streaming 的 cmdenv 选项设置环境变量,然后在 map 和 reduce 脚本中获取环境变量。

可参考 << hadoop streaming 高级编程 >>

http://dongxicheng.org/mapreduce/hadoop-streaming-advanced-programming/

(0) 作业提交脚本:

#!/usr/bin/env bash

max_read_count=${array[0]}

min_read_count=${array[1]}

max_write_count=${array[2]}

min_write_count=${array[3]}


hadoop jar $HADOOP_HOME/contrib/streaming/hadoop-0.20.2-streaming.jar \

  -D mapred.reduce.tasks=1 \   

  -input $input \

  -output $output  \

  -mapper $mapper_script \

  -file $map_file  \

  -reducer $reducer_script \

  -file $reduce_file \

  -cmdenv "max_read_count=${array[0]}" \      # 设置环境变量   max_read_count .   

  -cmdenv "min_read_count=${array[1]}" \       # 多个变量时请多次使用 -cmdenv   

  -cmdenv "max_write_count=${array[2]}" \

  -cmdenv "min_write_count=${array[3]}" \  

(1) Python mapper.py

#!/usr/bin/env python

import sys

import os


min_r_count = float(os.environ.get('min_read_count')) # get environment variables.

max_r_count = float(os.environ.get('max_read_count'))

min_w_count = float(os.environ.get('min_write_count'))

max_w_count = float(os.environ.get('max_write_count'))


(2)Shell mapper.sh

#!/usr/bin/env bash 

while read line  # 读入行

do

   a=$line  

done

echo $min_read_count $max_read_count  # get environment variables.


(3)C/C++ mapper.c

#include

#include

int main(int argc, char *argv[], char *env[])

{

  double min_r_count;

  int i = 0;

  for (i = 0; env[i] != NULL; i++) // env[i] 存储了环境变量, 每项的值为此种形式: PATH=******, 所以需要截取变量值

  { 

     if( strstr(env[i], "PATH=") ) {

       char *p =NULL;

       p = strstr(env[i], "=");

       if( (p-env[i]) == 4 )

         printf("%s\n", ++p); // 获取 PATH 环境变量

     }

      if( strstr(env[i], "min_write_count=") ) {

       char *p =NULL;

       p = strstr(env[i], "=");

       if( (p-env[i]) == strlen("min_write_count") )

         printf("%s\n", ++p); // 获取  min_write_count  环境变量

     }

  }

  char eachLine[200]={0};

  while(fgets(eachLine, 199, stdin)) // read line from stdin

  {

     printf("%s", eachLine);

  }

}


注意:
    Hadoop执行命令时的选项有顺序的, 顺序是 bin/hadoop command [genericOptions] [commandOptions]. 
    对于streaming, -D 属于genericOptions, 即hadoop的通用选项, 所以必须写在前面.
    Streaming 的所有选项可以参考:  
    hadoop jar $HADOOP_HOME/contrib/streaming/hadoop-0.20.2-streaming.jar -info

3.  编写 Streaming 程序时,如何向map、reduce函数传递文件或文件夹。

(1) streaming 加载本地单个文件

streaming 支持 -file 选项, 可以把 -file 后面的本地文件(注意是本地文件)打包成作业提交的一部分, 即打包到作业的jar文件当中, 这样在mapreduce脚本中就可以像访问本地文件一样访问打包的文件了.

实例:

作业提交文件 run.sh

[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第5张图片

mapper.py

[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第6张图片

注意:在提交作业时使用的是 -file logs/wbscoretest.log 指定需要加载的文件. 在 map 脚本中只需要直接读取文件 wbscoretest.log 即可, 不需要写 logs/wbscoretest.log, 因为只加载了文件 wbscoretest.log, 而不会加载 logs 目录和 

wbscoretest.log 文件.

(2) streaming 加载本地多个文件 

[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第7张图片

(3) streaming 加载本地目录 ( 若加载多个目录,用逗号隔开,-files dir1, dir2, dir3 )

使用streaming的 -file 选项不能加载本地目录, 我实验是如此.

我们可以使用 hadoop 的通用选项 -files 来加载本地目录, 加载成功后在mapreduce脚本中可以像访问本地目录一样访问加载的目录.

实际应用中,我们在编写 分词MapReduce作业时需要加载分词词典,就使用该方法.

作业提交脚本:  

[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第8张图片

map 脚本: 读取目录下的文件.

[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第9张图片


加载多个目录:

[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第10张图片


注意:多个目录之间用逗号隔开,且不能有空格,否则会出错,这个限制太蛋疼了。

例如:

(4) streaming编程时在mapreduce脚本中读 hdfs 文件

使用 -files 选项, 后面跟需要读的 hdfs 文件路径. 这样在 mapreduce 脚本中就可以直接通过文件名来访问该文件.

作业提交脚本:

[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第11张图片

map脚本: 

[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第12张图片

如果需要加载大文件, 我们可以将文件先上传到 hdfs 中, 然后在 mapreduce 脚本中读取 hdfs 文件.

(5) streaming编程时在mapreduce脚本中读 hdfs 目录

使用 -files 选项, 后面跟需要读的 hdfs 目录. 这样在 mapreduce 脚本中就可以像访问本地目录一样访问该目录.

作业提交脚本:

[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第13张图片

map脚本:  直接读取 tmp_kentzhan 目录.

[MapReduce] 如何向map和reduce脚本传递参数,加载文件和目录_第14张图片


 写MapReduce程序通常要传递各种各样的参数,选择合适的方式来传递参数既能提高工作效率,也可以避免bug的产生。根据参数的大小,可以粗略的分为以下几种。

最直接的方式就是使用Configuration的各种set方法,对于基本数据类型都有很好的支持,比如传递kmeans聚类算法的中心点个数。

如何传递一个对象型参数?话说所有的对象都是由基本类型构建的,所以我们可以覆盖这个对象的toString()方法,将它的所有元素表示成字符串,然后使用Configuration.set(name, value)传递这个字符串。然后在Mapper端获得这个字符串,做析构。这种朴素的方法有两个缺点。首先,将对象变成字符串会有精度上的损失,比如 double类型转换成字符串,不仅精度有损失,而且8字节的空间用字符串来表示可能会变成几十字节。其次,由于字符串化和反字符串化分散在不同的地方,很容易产生bug,如果修改了这个对象的结构,这种bug产生的几率非常大。既然有这种需求存在,难道hadoop没有提供nice点的方法吗?有,不过在api文档中没有直接说明。

正确的方法是,让这个对象实现Writable接口,使它具有序列化的能力,然后使用org.apache.hadoop.io.DefaultStringifier的store(conf,  obj, keyname)和load(conf, keyname, itemclass)静态方法设置和获取这个对象。他的主要思想就是将这个对象序列化成一个字节数组后,用Base64编码成一个字符串,然后传递给 conf, 解析的时候与之类似。

如何传递更大的参数,比如分词用的语料库等等?可以使用hadoop的缓存文件DistributedCache。

1、使用configuration的set()和get()方法,这里的name和value都是String型

Configuration.set(name, value)

Configuration.get(name)

这种方法适合基本数据类型的传递。

2、使用Stringifier 接口。

DefaultStringifier.store(conf, object ,"key");

将object以序列化后以指定的key存在conf中。

object = DefaultStringifier.load(conf, "key", variableClass );

从conf中取出object。

需要指出的是使用第二种方法的对象必须是可序列化的。Hadoop的序列化是通过Writable接口来实现的,在org.apache.hadoop.io包下包含了大量的可序列化的组件,它们都实现了Writable接口,Writable接口提供了两个方法,write和readFields,分别用来序列化和反序列化,




你可能感兴趣的:(Hadoop)