From 48e60ad749d15a8e118fb8fb1e22ba96058a56e3 Mon Sep 17 00:00:00 2001 From: kangfoo-mac Date: Tue, 10 Dec 2013 16:07:38 +0800 Subject: [PATCH 01/23] GitHubWagon: Deploying OpooPress to GitHub Pages. --- about/index.html | 60 ++- archives/index.html | 76 ++-- .../12/hadoop-eclipse-plugin-1.2.1/index.html | 354 ++++++++++++++++++ .../index.html | 139 ++++--- atom.xml | 156 ++++++-- category/hadoop/index.html | 153 ++++++++ category/index.html | 37 +- category/java/index.html | 153 ++++++++ index.html | 208 ++++++++-- sample-page.html | 31 +- sitemap.xml | 12 +- stylesheets/screen.css | 2 +- tag/ant/index.html | 153 ++++++++ tag/hadoop/index.html | 153 ++++++++ tag/java/index.html | 75 +++- tag/mac/index.html | 62 ++- tag/maven/index.html | 62 ++- 17 files changed, 1624 insertions(+), 262 deletions(-) create mode 100644 article/2013/12/hadoop-eclipse-plugin-1.2.1/index.html create mode 100644 category/hadoop/index.html create mode 100644 category/java/index.html create mode 100644 tag/ant/index.html create mode 100644 tag/hadoop/index.html diff --git a/about/index.html b/about/index.html index 218c33b..1294845 100644 --- a/about/index.html +++ b/about/index.html @@ -12,7 +12,7 @@ - + @@ -20,7 +20,16 @@ + + + + + @@ -43,20 +52,28 @@

工作学习笔记,生活掠影。

@@ -87,25 +104,30 @@

近期文章

  • - Mac Java乱码 Maven OOM 异常 + 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件
  • - 世界,你好! + Mac Java乱码 Maven OOM 异常
  • -
    -

    分类

    -
    +
    +

    近期评论

    + +
    @@ -116,7 +138,7 @@

    分类

    + @@ -135,7 +157,7 @@

    分类

    + + + +

    +

    相关文章

    + +

    + « Mac java乱码 maven OOM 异常 +

    + + +
    +

    评论

    +
    +
    + + + + + + + + + + + + + + + diff --git a/article/2013/12/ping-guo-dian-nao-mavn-luan-ma-he-java-oom--yi-chang/index.html b/article/2013/12/ping-guo-dian-nao-mavn-luan-ma-he-java-oom--yi-chang/index.html index 463583d..7dc6ac2 100644 --- a/article/2013/12/ping-guo-dian-nao-mavn-luan-ma-he-java-oom--yi-chang/index.html +++ b/article/2013/12/ping-guo-dian-nao-mavn-luan-ma-he-java-oom--yi-chang/index.html @@ -12,7 +12,7 @@ - + @@ -20,8 +20,16 @@ + + + + + @@ -44,20 +52,28 @@

    工作学习笔记,生活掠影。

    @@ -77,19 +93,17 @@

    Mac Java乱码 Maven OOM 异常

    -

    在中文环境的 java,javac 默认以GBK编码输出信息到控制台,但是mac默认以UTF-8编码,就出现了编码错误。 -可如下3种方式修改:

    +

    在中文环境下苹果电脑中的 java,javac 默认以GBK编码输出信息到控制台,终端terminal默认以UTF-8编码,就出现了编码错误。

    +

    可如下2种方式修改:

      -
    1. 更改系统语言环境
      export LC_ALL=en 或者 export LANG=zh_CN.UTF-8
      +
    2. 更改系统语言环境
      export LC_ALL=en 或者 export LANG=zh_CN.UTF-8
      +
    3. +
    4. 指定输出编码方式
      javac -Dfile.encoding=UTF-8
       
    5. -
    6. 指定输出编码方式
      javac -Dfile.encoding=UTF-8
      -
    7. -
    8. 通过iconv转码
      javac something|iconv -f GBK -t UTF-8
      -通常我使用 export LANG=zh_CN.UTF-8 解决。 -但在有maven的情况下还需要这么设置。
      export MAVEN_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
      -否则可能出现内存溢出。
    -
    +

    但在有maven的情况下还需要这么设置:先调整JVM大小,避免工程太大中途出现OOM,再设置文件的编码。

    +
    export MAVEN_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
    +
    @@ -131,25 +158,30 @@

    近期文章

  • - Mac Java乱码 Maven OOM 异常 + 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件
  • - 世界,你好! + Mac Java乱码 Maven OOM 异常
  • -
    -

    分类

    -
    +
    +

    近期评论

    + +
    @@ -160,7 +192,7 @@

    分类

    - - - - - diff --git a/atom.xml b/atom.xml index 48e1973..7f77286 100644 --- a/atom.xml +++ b/atom.xml @@ -3,40 +3,152 @@ <![CDATA[kangfoo's 博客]]> - 2013-12-06T17:43:30+08:00 + 2013-12-10T16:00:27+08:00 http://kangfoo.u.qiniudn.com// - + OpooPress + + <![CDATA[编译hadoop 1.2.1 hadoop-eclipse-plugin插件]]> + + 2013-12-09T22:52:00+08:00 + http://kangfoo.u.qiniudn.com//article/2013/12/hadoop-eclipse-plugin-1.2.1/ + 编译hadoop1.x.x版本的eclipse插件为何如此繁琐?

    +

    个人理解,ant的初衷是打造一个本地化工具,而编译hadoop插件的资源间的依赖超出了这一目标。导致我们在使用ant编译的时候需要手工去修改配置。那么自然少不了设置环境变量、设置classpath、添加依赖、设置主函数、javac、jar清单文件编写、验证、部署等步骤。

    +

    那么我们开始动手

    +

    主要步骤如下

    +
      +
    • 设置环境变量
    • +
    • 设置ant初始参数
    • +
    • 调整java编译参数
    • +
    • 设置java classpath
    • +
    • 添加依赖
    • +
    • 修改META-INF文件
    • +
    • 编译打包、部署、验证
    • +
    +

    具体操作

    +
      +
    1. 设置语言环境 +
      $ export LC_ALL=en
      +
    2. +
    3. 设置ant初始参数 +修改build-contrib.xml文件 +
      $ cd /hadoop-1.2.1/src/contrib
      +$ vi build-contrib.xml
      +
      编辑并修改hadoop.root值为实际hadoop解压的根目录 +
      <property name="hadoop.root" location="/Users/kangfoo-mac/study/hadoop-1.2.1"/>
      +
      添加eclipse依赖 +
      <property name="eclipse.home" location="/Users/kangfoo-mac/work/soft/eclipse-standard-kepler-SR1-macosx-cocoa" />
      +
      设置版本号 +
      <property name="version" value="1.2.1"/>
      +
    4. +
    5. 调整java编译设置 +启用javac.deprecation +
      $ cd /hadoop-1.2.1/src/contrib
      +$ vi build-contrib.xml
      +

      +
      <property name="javac.deprecation" value="off"/>
      +
      改为 +
      <property name="javac.deprecation" value="on"/>
      +
    6. +
    7. ant 1.8+ 版本需要额外的设置javac includeantruntime=“on” 参数 +
      <!-- ====================================================== -->
      +<!-- Compile a Hadoop contrib's files                       -->
      +<!-- ====================================================== -->
      +<target name="compile" depends="init, ivy-retrieve-common" unless="skip.contrib">
      +<echo message="contrib: ${name}"/>
      +<javac
      + encoding="${build.encoding}"
      + srcdir="${src.dir}"
      + includes="**/*.java"
      + destdir="${build.classes}"
      + debug="${javac.debug}"
      + deprecation="${javac.deprecation}"
      + includeantruntime="on">
      + <classpath refid="contrib-classpath"/>
      +</javac>
      +</target> 
      +
    8. +
    9. 修改编译hadoop插件 classpath +
      $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
      +$ vi build.xml
      +
      添加 文件路径 hadoop-jars +
      <path id="hadoop-jars">
      +  <fileset dir="${hadoop.root}/">
      +    <include name="hadoop-*.jar"/>
      +  </fileset>
      +</path>
      +
      将hadoop-jars 添加到classpath +
      <path id="classpath">
      +  <pathelement location="${build.classes}"/>
      +  <pathelement location="${hadoop.root}/build/classes"/>
      +  <path refid="eclipse-sdk-jars"/>
      +  <path refid="hadoop-jars"/>
      +</path> 
      +
    10. +
    11. 修改或添加额外的jar依赖 +因为我们根本都没有直接编译过hadoop,所以就直接使用${HADOOP_HOME}/lib下的资源.需要注意,这里将依赖jar的版本后缀去掉了。 +同样还是在hadoop-1.2.1/src/contrib/eclipse-plugin/build.xml文件中修改或添加 +
      $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
      +$ vi build.xml
      +
      找到 <!-- Override jar target to specify manifest --> 修改target name为 jar 中的 copy file 的路径,具体如下: +
      <copy file="${hadoop.root}/hadoop-core-${version}.jar" tofile="${build.dir}/lib/hadoop-core.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/commons-cli-${commons-cli.version}.jar"  tofile="${build.dir}/lib/commons-cli.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/commons-configuration-1.6.jar"  tofile="${build.dir}/lib/commons-configuration.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/commons-httpclient-3.0.1.jar"  tofile="${build.dir}/lib/commons-httpclient.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/commons-lang-2.4.jar"  tofile="${build.dir}/lib/commons-lang.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/jackson-core-asl-1.8.8.jar"  tofile="${build.dir}/lib/jackson-core-asl.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/jackson-mapper-asl-1.8.8.jar"  tofile="${build.dir}/lib/jackson-mapper-asl.jar" verbose="true"/>
      +
    12. +
    13. 修改 jar 清单文件 +
      $ cd ./hadoop-1.2.1/src/contrib/eclipse-plugin/META-INF
      +$ vi MANIFEST.MF
      +
      找到这个文件的Bundle-ClassPath这一行,然后,修改成 +
      Bundle-ClassPath: classes/,lib/commons-cli.jar,lib/commons-httpclient.jar,lib/hadoop-core.jar,lib/jackson-mapper-asl.jar,lib/commons-configuration.jar,lib/commons-lang.jar,lib/jackson-core-asl.jar
      +
      请保证上述字符占用一行,或者满足osgi bundle 配置文件的换行标准语法也行的。省事就直接写成一行,搞定。
    14. +
    15. 新建直接打包并部署jar到eclipse/plugin目录的target +
      $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
      +$ vi build.xml
      +
      添加target直接将编译的插件拷贝到eclipse插件目录 +
      <target name="deploy" depends="jar" unless="skip.contrib"> 
      +<copy file="${build.dir}/hadoop-${name}-${version}.jar" todir="${eclipse.home}/plugins" verbose="true"/> </target>
      +
      修改ant默认target为deploy +
      <project default="deploy" name="eclipse-plugin">
      +
    16. +
    17. 编译并启动eclipse验证插件 +
      $ ant -f ./hadoop-1.2.1/src/contrib/eclipse-plugin/build.xml
      +
      启动eclipse,新建Map/Reduce Project,配置hadoop location.验证插件完全分布式的插件配置截图和core-site.xml端口配置
    18. +
    19. 效果图 +image
    20. +
    +

    相关源文件

    + +

    在此非常感谢kinuxroot这位博主的的博文参考。

    +]]>
    +
    <![CDATA[Mac java乱码 maven OOM 异常]]> 2013-12-05T23:55:00+08:00 http://kangfoo.u.qiniudn.com//article/2013/12/ping-guo-dian-nao-mavn-luan-ma-he-java-oom--yi-chang/ - 在中文环境的 java,javac 默认以GBK编码输出信息到控制台,但是mac默认以UTF-8编码,就出现了编码错误。 -可如下3种方式修改:

    + 在中文环境下苹果电脑中的 java,javac 默认以GBK编码输出信息到控制台,终端terminal默认以UTF-8编码,就出现了编码错误。

    +

    可如下2种方式修改:

      -
    1. 更改系统语言环境
      export LC_ALL=en 或者 export LANG=zh_CN.UTF-8
      -
    2. -
    3. 指定输出编码方式
      javac -Dfile.encoding=UTF-8
      -
    4. -
    5. 通过iconv转码
      javac something|iconv -f GBK -t UTF-8
      -通常我使用 export LANG=zh_CN.UTF-8 解决。 -但在有maven的情况下还需要这么设置。
      export MAVEN_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
      -否则可能出现内存溢出。
    6. +
    7. 更改系统语言环境
      export LC_ALL=en 或者 export LANG=zh_CN.UTF-8
      +
    8. +
    9. 指定输出编码方式
      javac -Dfile.encoding=UTF-8
      +
    -]]>
    -
    - - <![CDATA[世界,你好!]]> - - 2013-12-05T23:23:00+08:00 - http://kangfoo.u.qiniudn.com//article/2013/12/hello-world/ - 欢迎使用 OpooPress!

    -

    这是系统自动生成的演示文章。编辑或者删除它,然后开始您的博客!

    -]]>
    +

    但在有maven的情况下还需要这么设置:先调整JVM大小,避免工程太大中途出现OOM,再设置文件的编码。

    +
    export MAVEN_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
    +
    ]]>
    diff --git a/category/hadoop/index.html b/category/hadoop/index.html new file mode 100644 index 0000000..35828e2 --- /dev/null +++ b/category/hadoop/index.html @@ -0,0 +1,153 @@ + + + + + + + 分类: hadoop - kangfoo's 博客 + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    kangfoo's 博客

    +

    工作学习笔记,生活掠影。

    +
    +
    + +
    +
    +
    + +
    + +
    +
    + + + + + diff --git a/category/index.html b/category/index.html index 8f16cd6..e2c58dc 100644 --- a/category/index.html +++ b/category/index.html @@ -12,7 +12,7 @@ - + @@ -20,7 +20,16 @@ + + + + + @@ -43,20 +52,28 @@

    工作学习笔记,生活掠影。

    @@ -71,11 +88,9 @@

    Categories

    diff --git a/category/java/index.html b/category/java/index.html new file mode 100644 index 0000000..be30208 --- /dev/null +++ b/category/java/index.html @@ -0,0 +1,153 @@ + + + + + + + 分类: java - kangfoo's 博客 + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    kangfoo's 博客

    +

    工作学习笔记,生活掠影。

    +
    +
    + +
    +
    +
    + +
    + +
    +
    + + + + + diff --git a/index.html b/index.html index 6924f8b..ad9ab6a 100644 --- a/index.html +++ b/index.html @@ -12,7 +12,7 @@ - + @@ -20,7 +20,16 @@ + + + + + @@ -44,20 +53,28 @@

    工作学习笔记,生活掠影。

    @@ -66,47 +83,159 @@

    工作学习笔记,生活掠影。

    -

    Mac Java乱码 Maven OOM 异常

    +

    编译hadoop 1.2.1 Hadoop-eclipse-plugin插件

    - - | 评论 + + | 评论

    -

    在中文环境的 java,javac 默认以GBK编码输出信息到控制台,但是mac默认以UTF-8编码,就出现了编码错误。 -可如下3种方式修改:

    +

    编译hadoop1.x.x版本的eclipse插件为何如此繁琐?

    +

    个人理解,ant的初衷是打造一个本地化工具,而编译hadoop插件的资源间的依赖超出了这一目标。导致我们在使用ant编译的时候需要手工去修改配置。那么自然少不了设置环境变量、设置classpath、添加依赖、设置主函数、javac、jar清单文件编写、验证、部署等步骤。

    +

    那么我们开始动手

    +

    主要步骤如下

    +
      +
    • 设置环境变量
    • +
    • 设置ant初始参数
    • +
    • 调整java编译参数
    • +
    • 设置java classpath
    • +
    • 添加依赖
    • +
    • 修改META-INF文件
    • +
    • 编译打包、部署、验证
    • +
    +

    具体操作

      -
    1. 更改系统语言环境
      export LC_ALL=en 或者 export LANG=zh_CN.UTF-8
      +
    2. 设置语言环境 +
      $ export LC_ALL=en
      +
    3. +
    4. 设置ant初始参数 +修改build-contrib.xml文件 +
      $ cd /hadoop-1.2.1/src/contrib
      +$ vi build-contrib.xml
      +
      编辑并修改hadoop.root值为实际hadoop解压的根目录 +
      <property name="hadoop.root" location="/Users/kangfoo-mac/study/hadoop-1.2.1"/>
      +
      添加eclipse依赖 +
      <property name="eclipse.home" location="/Users/kangfoo-mac/work/soft/eclipse-standard-kepler-SR1-macosx-cocoa" />
      +
      设置版本号 +
      <property name="version" value="1.2.1"/>
      +
    5. +
    6. 调整java编译设置 +启用javac.deprecation +
      $ cd /hadoop-1.2.1/src/contrib
      +$ vi build-contrib.xml
      +

      +
      <property name="javac.deprecation" value="off"/>
      +
      改为 +
      <property name="javac.deprecation" value="on"/>
       
    7. -
    8. 指定输出编码方式
      javac -Dfile.encoding=UTF-8
      -
    9. -
    10. 通过iconv转码
      javac something|iconv -f GBK -t UTF-8
      -通常我使用 export LANG=zh_CN.UTF-8 解决。 -但在有maven的情况下还需要这么设置。
      export MAVEN_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
      -否则可能出现内存溢出。
    11. +
    12. ant 1.8+ 版本需要额外的设置javac includeantruntime=“on” 参数 +
      <!-- ====================================================== -->
      +<!-- Compile a Hadoop contrib's files                       -->
      +<!-- ====================================================== -->
      +<target name="compile" depends="init, ivy-retrieve-common" unless="skip.contrib">
      +<echo message="contrib: ${name}"/>
      +<javac
      + encoding="${build.encoding}"
      + srcdir="${src.dir}"
      + includes="**/*.java"
      + destdir="${build.classes}"
      + debug="${javac.debug}"
      + deprecation="${javac.deprecation}"
      + includeantruntime="on">
      + <classpath refid="contrib-classpath"/>
      +</javac>
      +</target> 
      +
    13. +
    14. 修改编译hadoop插件 classpath +
      $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
      +$ vi build.xml
      +
      添加 文件路径 hadoop-jars +
      <path id="hadoop-jars">
      +  <fileset dir="${hadoop.root}/">
      +    <include name="hadoop-*.jar"/>
      +  </fileset>
      +</path>
      +
      将hadoop-jars 添加到classpath +
      <path id="classpath">
      +  <pathelement location="${build.classes}"/>
      +  <pathelement location="${hadoop.root}/build/classes"/>
      +  <path refid="eclipse-sdk-jars"/>
      +  <path refid="hadoop-jars"/>
      +</path> 
      +
    15. +
    16. 修改或添加额外的jar依赖 +因为我们根本都没有直接编译过hadoop,所以就直接使用${HADOOP_HOME}/lib下的资源.需要注意,这里将依赖jar的版本后缀去掉了。 +同样还是在hadoop-1.2.1/src/contrib/eclipse-plugin/build.xml文件中修改或添加 +
      $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
      +$ vi build.xml
      +
      找到 <!-- Override jar target to specify manifest --> 修改target name为 jar 中的 copy file 的路径,具体如下: +
      <copy file="${hadoop.root}/hadoop-core-${version}.jar" tofile="${build.dir}/lib/hadoop-core.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/commons-cli-${commons-cli.version}.jar"  tofile="${build.dir}/lib/commons-cli.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/commons-configuration-1.6.jar"  tofile="${build.dir}/lib/commons-configuration.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/commons-httpclient-3.0.1.jar"  tofile="${build.dir}/lib/commons-httpclient.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/commons-lang-2.4.jar"  tofile="${build.dir}/lib/commons-lang.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/jackson-core-asl-1.8.8.jar"  tofile="${build.dir}/lib/jackson-core-asl.jar" verbose="true"/>
      +<copy file="${hadoop.root}/lib/jackson-mapper-asl-1.8.8.jar"  tofile="${build.dir}/lib/jackson-mapper-asl.jar" verbose="true"/>
      +
    17. +
    18. 修改 jar 清单文件 +
      $ cd ./hadoop-1.2.1/src/contrib/eclipse-plugin/META-INF
      +$ vi MANIFEST.MF
      +
      找到这个文件的Bundle-ClassPath这一行,然后,修改成 +
      Bundle-ClassPath: classes/,lib/commons-cli.jar,lib/commons-httpclient.jar,lib/hadoop-core.jar,lib/jackson-mapper-asl.jar,lib/commons-configuration.jar,lib/commons-lang.jar,lib/jackson-core-asl.jar
      +
      请保证上述字符占用一行,或者满足osgi bundle 配置文件的换行标准语法也行的。省事就直接写成一行,搞定。
    19. +
    20. 新建直接打包并部署jar到eclipse/plugin目录的target +
      $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
      +$ vi build.xml
      +
      添加target直接将编译的插件拷贝到eclipse插件目录 +
      <target name="deploy" depends="jar" unless="skip.contrib"> 
      +<copy file="${build.dir}/hadoop-${name}-${version}.jar" todir="${eclipse.home}/plugins" verbose="true"/> </target>
      +
      修改ant默认target为deploy +
      <project default="deploy" name="eclipse-plugin">
      +
    21. +
    22. 编译并启动eclipse验证插件 +
      $ ant -f ./hadoop-1.2.1/src/contrib/eclipse-plugin/build.xml
      +
      启动eclipse,新建Map/Reduce Project,配置hadoop location.验证插件完全分布式的插件配置截图和core-site.xml端口配置
    23. +
    24. 效果图 +image
    +

    相关源文件

    + +

    在此非常感谢kinuxroot这位博主的的博文参考。

    -

    世界,你好!

    +

    Mac Java乱码 Maven OOM 异常

    - - | 评论 + + | 评论

    -

    欢迎使用 OpooPress!

    -

    这是系统自动生成的演示文章。编辑或者删除它,然后开始您的博客!

    -
    +

    在中文环境下苹果电脑中的 java,javac 默认以GBK编码输出信息到控制台,终端terminal默认以UTF-8编码,就出现了编码错误。

    +

    可如下2种方式修改:

    +
      +
    1. 更改系统语言环境
      export LC_ALL=en 或者 export LANG=zh_CN.UTF-8
      +
    2. +
    3. 指定输出编码方式
      javac -Dfile.encoding=UTF-8
      +
    4. +
    +

    但在有maven的情况下还需要这么设置:先调整JVM大小,避免工程太大中途出现OOM,再设置文件的编码。

    +
    export MAVEN_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
    +
    @@ -160,7 +294,7 @@

    分类

    + + + + + + + + + diff --git a/tag/hadoop/index.html b/tag/hadoop/index.html new file mode 100644 index 0000000..11cac59 --- /dev/null +++ b/tag/hadoop/index.html @@ -0,0 +1,153 @@ + + + + + + + 标签:hadoop - kangfoo's 博客 + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    kangfoo's 博客

    +

    工作学习笔记,生活掠影。

    +
    +
    + +
    +
    +
    + +
    + +
    +
    + + + + + diff --git a/tag/java/index.html b/tag/java/index.html index 85a6cd0..59f3a44 100644 --- a/tag/java/index.html +++ b/tag/java/index.html @@ -12,7 +12,7 @@ - + @@ -20,7 +20,16 @@ + + + + + @@ -43,20 +52,28 @@

    工作学习笔记,生活掠影。

    @@ -71,11 +88,24 @@

    标签:java

    2013

    +

    Mac java乱码 maven OOM 异常

    @@ -122,7 +157,7 @@

    分类

    + @@ -122,7 +144,7 @@

    分类

    + @@ -122,7 +144,7 @@

    分类

    +
    +

    相关文章

    « Mac java乱码 maven OOM 异常 + 在oracle virtual box 虚拟机中搭建hadoop1.2.1完全分布式环境 »

    @@ -264,6 +265,9 @@

    近期文章

    @@ -222,19 +230,34 @@

    近期文章

  • - Hadoop1.x Wordcount分析 + Hadoop MapReduce 学习参考博客汇总 +
  • +
  • + Hadoop Pipes & Streaming +
  • +
  • + Hadoop MapReduce Sort +
  • +
  • + Hadoop MapReduce Join +
  • +
  • + Hadoop MapReduce 计数器 +
  • +
  • + Hadoop MapReduce RecordReader 组件
  • - Hadoop1.x 命令手册列举 + Hadoop MapReduce Partitioner 组件
  • - 编译hadoop 2.x Hadoop-eclipse-plugin插件 + Hadoop MapReduce Combiner 组件
  • - 在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境 + Hadoop MapReduce 类型与格式
  • - 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件 + Hadoop MapReduce 工作机制
  • diff --git a/article/2014/01/hadoop1.x-wordcount-fen-xi/index.html b/article/2014/01/hadoop1.x-wordcount-fen-xi/index.html index b6d13ba..bf16843 100644 --- a/article/2014/01/hadoop1.x-wordcount-fen-xi/index.html +++ b/article/2014/01/hadoop1.x-wordcount-fen-xi/index.html @@ -4,7 +4,7 @@ - hadoop1.x wordcount分析 - kangfoo's 博客 + hadoop1.x wordcount分析 - kangfoo's blog @@ -12,12 +12,12 @@ - + - + @@ -36,7 +36,7 @@
    -

    kangfoo's 博客

    +

    kangfoo's blog

    工作学习笔记,生活掠影。

    @@ -95,6 +95,7 @@

    Hadoop1.x Wordcount分析

    hadoop mapreduce 过程粗略的分为:map, redurce(copy, sort, reduce)两个阶段。具体的工作机制还是挺复杂的,这里主要通过hadoop example jar中提供的wordcount来对hadoop mapredurce做个简单的理解。Wordcount程序输入文件类型,计算单词的频率。输出是文本文件:每行是单词和它出现的频率,用Tab键隔开。

    +

    Hadoop内置的计数器,

    1. 首先确保Hadoop集群正常运行,并了解mapredurce工作时涉及到的基本的文件备配。vi mapred-site.xml

      @@ -224,7 +225,7 @@

      Hadoop1.x Wordcount分析

      分类 -被贴了 hadoop +被贴了 hadoop1 标签

    2. - 编译hadoop 2.x Hadoop-eclipse-plugin插件 -
      + Hadoop1.x 学习准备 +
    3. - 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件 -
      + Hadoop 分布式文件系统 +
    4. - 在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境 -
      + 模拟使用 SecondaryNameNode 恢复 NameNode +
      +
    5. +
    6. + Hadoop 机架感知 +
    7. « hadoop1.x 命令手册列举 + Hadoop1.x 学习准备 »

      @@ -272,19 +278,34 @@

      近期文章

    8. - Hadoop1.x Wordcount分析 + Hadoop MapReduce 学习参考博客汇总
    9. - Hadoop1.x 命令手册列举 + Hadoop Pipes & Streaming +
    10. +
    11. + Hadoop MapReduce Sort +
    12. +
    13. + Hadoop MapReduce Join +
    14. +
    15. + Hadoop MapReduce 计数器 +
    16. +
    17. + Hadoop MapReduce RecordReader 组件 +
    18. +
    19. + Hadoop MapReduce Partitioner 组件
    20. - 编译hadoop 2.x Hadoop-eclipse-plugin插件 + Hadoop MapReduce Combiner 组件
    21. - 在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境 + Hadoop MapReduce 类型与格式
    22. - 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件 + Hadoop MapReduce 工作机制
    23. diff --git a/article/2014/02/hadoop-c-pipes--bian-yi/index.html b/article/2014/02/hadoop-c-pipes--bian-yi/index.html new file mode 100644 index 0000000..469d728 --- /dev/null +++ b/article/2014/02/hadoop-c-pipes--bian-yi/index.html @@ -0,0 +1,333 @@ + + + + + + + Hadoop pipes 编译 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop Pipes 编译

      + +

      + + + + + | 评论 +

      +
      + +

      我在编译 Hadoop Pipes 的时候,出现了些小问题。主要是我没有安装 openssl-devel。本以为安装 openssl 就差不多了,可这个就是问题的根源, 我现在是自己动手编译 pipes, 而 Hadoop 的 pipes 编译需要 openssl 的依赖,那么在编译的时候最好还是将 openssl-devel 开发支持的依赖补上比较省事。在解决问题的时发现网上向我一样的同学还是有的。在此我就贴下我编译时的部分日志。

      +
        +
      1. 在Hadoop 根目录下执行

        +
        ant -Dcompile.c++=yes examples
        +##错误
        +[exec] checking for HMAC_Init in -lssl... no
        +BUILD FAILED
        +/home/hadoop/env/hadoop-1.2.1/build.xml:2164: exec returned: 255
        +… … 
        +./configure: line 5234: exit: please: numeric argument required
        +##具体日志:
        +… … 
        + [exec] configure: error: Cannot find libssl.so ## 没有 libssl.so
        + [exec] /home/hadoop/env/hadoop-1.2.1/src/c++/pipes/configure: line 5234: exit: please: numeric argument required
        + [exec] /home/hadoop/env/hadoop-1.2.1/src/c++/pipes/configure: line 5234: exit: please: numeric argument required
        + [exec] checking for HMAC_Init in -lssl... no 
        +
      2. +
      3. 检查 ssl

        +
        $ yum info openssl
        +$ ll /usr/lib64/libssl*
        +-rwxr-xr-x. 1 root root 221568 2月  23 2013 /usr/lib64/libssl3.so
        +lrwxrwxrwx. 1 root root     16 12月  8 18:14 /usr/lib64/libssl.so.10 -> libssl.so.1.0.1e
        +-rwxr-xr-x. 1 root root 436984 12月  4 04:21 /usr/lib64/libssl.so.1.0.1e
        +## 缺个 libssl.so 的文件, 于是添加软链接:
        +sudo ln -s /usr/lib64/libssl.so.1.0.1e /usr/lib64/libssl.so
        +
      4. +
      5. 切换目录到 pipes 下再次编译

        +
        $cd /home/hadoop/env/hadoop/src/c++/pipes
        +执行
        +$ make distclean
        +$ ./configure 
        +[hadoop@master11 pipes]$ ./configure 
        +checking for a BSD-compatible install... /usr/bin/install -c
        +… … 
        +checking whether it is safe to define __EXTENSIONS__... yes
        +checking for special C compiler options needed for large files... no
        +checking for _FILE_OFFSET_BITS value needed for large files... no
        +checking pthread.h usability... yes
        +checking pthread.h presence... yes
        +checking for pthread.h... yes
        +checking for pthread_create in -lpthread... yes
        +checking for HMAC_Init in -lssl... no
        +configure: error: Cannot find libssl.so ## 还是没找到
        +./configure: line 5234: exit: please: numeric argument required
        +./configure: line 5234: exit: please: numeric argument required
        +
      6. +
      7. 安装openssl-devel, sudo yum install openssl-devel

        +
      8. +
      9. 再切换到Hadoop根目录下执行

        +
        ant -Dcompile.c++=yes examples
        +##搞定,编译通过
        +compile-examples:
        +[javac] /home/hadoop/env/hadoop-1.2.1/build.xml:742: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
        +[javac] Compiling 24 source files to /home/hadoop/env/hadoop-1.2.1/build/examples
        +[javac] 警告: [options] 未与 -source 1.6 一起设置引导类路径
        +[javac] 注: /home/hadoop/env/hadoop-1.2.1/src/examples/org/apache/hadoop/examples/MultiFileWordCount.java使用或覆盖了已过时的 API。
        +[javac] 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
        +[javac] 1 个警告
        +examples:
        +  [jar] Building jar: /home/hadoop/env/hadoop-1.2.1/build/hadoop-examples-1.2.2-SNAPSHOT.jar
        +BUILD SUCCESSFUL
        +Total time: 1 minute 11 seconds
        +
      10. +
      +
      +
      +

      + + + + + +属于 hadoop + 分类 + + +被贴了 hadoop1 + 标签 +

      + +

      +

      相关文章

      + +

      + « native-hadoop 编译 + Hadoop I/O » +

      +
      +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/02/hadoop-hdfs-1/index.html b/article/2014/02/hadoop-hdfs-1/index.html new file mode 100644 index 0000000..bf35b4e --- /dev/null +++ b/article/2014/02/hadoop-hdfs-1/index.html @@ -0,0 +1,603 @@ + + + + + + + Hadoop 分布式文件系统 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop 分布式文件系统

      + +

      + + + + + | 评论 +

      +
      + +

      管理网络跨多台计算机存储的文件系统称为分布式文件系统。当数据的大小超过单台物理计算机存储能力,就需要对它进行分区存储。Hadoop提供了一个综合性的文件系统抽象, Hadoop Distributed FileSystem 简称 HDFS或者DFS,Hadoop 分布式文件系统。它是Hadoop 3大组件之一。其他两大组件为 Hadoop-common 和 Hadoop-mapreduce。

      +

      传统以文件为基本单位的存储缺点:首先它很难实现并行化处理某个文件。单个节点一次只能处理一个文件,无法同时处理其他文件;再者,文件大小不同很难实现负载均衡。

      +

      HDFS的设计

      +
        +
      • HDFS以流式数据访问模式来存储超大文件,部署运行于廉价的机器上。
      • +
      • 可存储超大文件;流式访问,一次写入,多次读取;商用廉价PC,并不需要高昂的高可用的硬件。
      • +
      • 但不适用于,低时间延迟的访问;大小文件处理(浪费namenode内存,浪费磁盘空间。);多用户写入,任意修改文件(不支持并发写入。 同一时刻只能一个进程写入,不支持随机修改。)。
      • +
      +

      数据块

      +

      块是磁盘进行数据读写的最小单位,默认是512字节,构建单个磁盘之上的文件系统通过磁盘块来管理文件系统的来管理该文件系统中的块。HDFS的块默认是64MB,HDFS上的文件也被划分为块大小的多个分块(chunk),作为独立的存储单元。HDFS块默认64MB的好处是为了简化磁盘寻址的开销。

      +

      HDFS块的抽象好处

      +
        +
      • 一个文件的大小,可以大于网络中任意一个硬盘的大小。文件的块并不需要存储在同一个硬盘上可以存储在分布式文件系统集群中任意一个硬盘上。
      • +
      • 大大简化系统设计。这点对于故障种类繁多的分布式系统来说尤为重要。以块为单位,不仅简化存储管理(块大小是固定的,很容易计算一个硬盘放多少个块);而且,消除了元数据的顾虑(因为Block仅仅是存储的一块数据,其文件的元数据,例如权限等就不需要跟数据块一起存储,可以交由另外的其他系来处理)。适合批处理。支持离线的批量数据处理,支持高吞吐量。
      • +
      • 块更适合于数据备份。进而提供数据容错能力和系统可用性(将每个块复制至少几个独立的机器上,可以确保在发生块、磁盘或机器故障后数据不丢失。一旦发生某个块不可用,系统将从其他地方复制一份复本。以保证复本的数量恢复到正常水平)。容错性高,容易实现负载均衡。
      • +
      +

      Namenode 和 Datanode

      +

      HDFS采用master/slave架构。一个HDFS集群是由一个Namenode和一定数目的 Datanodes 组成。Namenode是一个中心服务器,负责管理文件系统的名字空间(namespace)以及客户端对文件的访问。集群中的Datanode一般是一个节点一个,负责管理它所在节点上的存储。HDFS暴露了文件系统的名字空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组Datanode上。Namenode执行文件系统的namespace操作。比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的 映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。

      +

      NameNode上维护文件系统树及整棵树内所有的文件和目录,并永久保存在本地磁盘,fsimage和editslog。

      +

      NameNode 将对文件系统的改动追加保存到本地文件系统上的一个日志文件(edits)。当一个 NameNode 启动时,它首先从一个映像文件(fsimage)中读取 HDFS 的状态,接着应用日志文件中的 edits 操作。然后它将新的 HDFS 状态写入(fsimage)中,并使用一个空的 edits 文件开始正常操作。因为 NameNode 只有在启动阶段才合并 fsimage 和 edits,所以久而久之日志文件可能会变得非常庞大,特别是对大型的集群。日志文件太大的副作用是下一次 NameNode 启动会花很长时间。

      +

      Hadoop HDFS 架构图

      +

      image

      +

      在上图中NameNode是Master上的进程,复制控制底层文件的io操作,处理mapreduce任务等。 +DataNode运行在slave机器上,负责实际的地层文件io读写。由NameNode存储管理文件系统的命名空间。

      +

      客户端代表用户通过与NameNode和DataNode交互来访问整个文件系统。

      +

      HDFS 读过程

      +

      image

      +
        +
      1. 客户端通过调用 FileSystem 对象的 open() 方法来打开要读取的文件。(步骤 1)在HDFS中是个 DistributedFileSystem 中的一个实例对象。
      2. +
      3. (步骤 2)DistributedFileSystem 通过PRC[需要提供一个外链接介绍RPC技术]调用 namenode ,获取文件起始块的位置。对于每个块,namenode 返回存有该块复本的 datanode 地址。Datanode 根据它们于该客户端的距离排序,如果该客户端就是一个 datanode 并保存有该数据块的一个复本,该节点就直接从本地 datanode 中读取数据。反之取网路拓扑最短路径。
      4. +
      5. DistributedFileSystem 返回 FSDataInputStream 给客户端,用来读取数据。FSDataInputStream 类封装 DFSInputStream 对象。由 DFSInputStream 负责管理 DataNode 和 NameNode 的 I/O。
      6. +
      7. (步骤 3)客户端调用 stream 的 read() 函数开始读取数据。
      8. +
      9. 存储着文件起始块的 DataNode 地址的 DFSInputStream 随即连接距离最近的 DataNode。反复 read() 方法,将数据从 datanode 传输到客户端。(网络拓扑与Hadoop[机架感知] ? 连接待补。)
      10. +
      11. 到达块的末端时,DFSInputStream 关闭和此数据节点的连接,然后连接此文件下一个数据块的最佳DataNode。
      12. +
      13. 客户端读取数据时,块也是按照打开 DFSInputStream 与 datanode 建立连接的顺序读取的。以通过询问NameNode 来检索下一批所需块的 datanode。当客户端读取完毕数据的时候,调用 FSDataInputStream 的 close() 函数。
      14. +
      15. 在读取数据的过程中,如果客户端在与数据节点通信出现错误,则尝试连接包含此数据块的下一个数据节点。失败的数据节点将被记录,以后不再连接。
      16. +
      +
      源代码理解
      +
        +
      1. 在客户端执行 class DistributedFileSystem open() 方法(装饰模式),打开文件并返回 DFSInputStream。

        +
        // DistributedFileSystem extends FileSystem --> 调用 open() 方法
        +public FSDataInputStream open(Path f, int bufferSize) throws IOException {
        +    statistics.incrementReadOps(1);
        +    return new DFSClient.DFSDataInputStream(
        +        dfs.open(getPathName(f), bufferSize, verifyChecksum, statistics));
        +}
        +
        // dfs 是 DFSClient 的实例对象。
        +public DFSInputStream open(String src, int buffersize, boolean verifyChecksum,
        +               FileSystem.Statistics stats
        +) throws IOException {
        +    checkOpen(); 
        +    //    Get block info from namenode
        +    return new DFSInputStream(src, buffersize, verifyChecksum);
        +}
        +
        // DFSInputStream 是 DFSClient 的内部类,继承自 FSInputStream。
        +// 调用的构造函数(具体略)中调用了 openInfo() 方法。在 openInfo() 中 重要的是 fetchLocatedBlocks() 向 NameNode 询问所需要的数据的元信息,通过 callGetBlockLocations() 实现。 此过程若没有找到将尝试3次。 
        +//
        +// 由 callGetBlockLocations()通过 RPC 方式询问 NameNode 获取到 LocatedBlocks 信息。
        +static LocatedBlocks callGetBlockLocations(ClientProtocol namenode,
        +  String src, long start, long length) throws IOException {
        +… … 
        +    return namenode.getBlockLocations(src, start, length);
        +… … 
        +}    
        +
        // 此处的 namenode 是通过代理模式创建的。它是 namenode  ClientProtocol 的实现(interface ClientProtocol extends VersionedProtocol)。
        +private static ClientProtocol createNamenode(ClientProtocol rpcNamenode,
        +Configuration conf) throws IOException {
        +… … 
        +    final ClientProtocol cp = (ClientProtocol)RetryProxy.create(ClientProtocol.class, rpcNamenode, defaultPolicy, methodNameToPolicyMap);
        +    RPC.checkVersion(ClientProtocol.class, ClientProtocol.versionID, cp);
        +… … 
        +}  
        +
      2. +
      3. 在 class NameNode 端获取数据块位置信息并排序

        +
        public LocatedBlocks   getBlockLocations(String src, long offset, long length) throws IOException {
        +    myMetrics.incrNumGetBlockLocations();
        +    // 获取数据块信息。namenode 为 FSNamesystem 实例。
        +    // 保存的是NameNode的name space树,其属性 FSDirectory dir 关联着 FSImage fsimage 信息,
        +    // fsimage 关联 FSEditLog editLog。
        +    return namesystem.getBlockLocations(getClientMachine(), src, offset, length);
        +}
        +
        // 类 FSNamesystem.getBlockLocationsInternal() 是具体获得块信息的实现。
        +private synchronized LocatedBlocks getBlockLocationsInternal(String src,
        +long offset, long length, int nrBlocksToReturn, 
        +boolean doAccessTime,  boolean needBlockToken) throws IOException {
        +    … … 
        +}  
        +
      4. +
      5. 在客户端DFSClient将步骤1中打开的读文件, DFSDataInputStream 对象内部的 DFSInputStream 对象的 read(long position, byte[] buffer, int offset, int length)方法进行实际的文件读取

        +
        // class DFSInputStream
        +public int read(long position, byte[] buffer, int offset, int length)
        +  throws IOException {
        +  // sanity checks
        +  checkOpen();
        +  if (closed) {
        +    throw new IOException("Stream closed");
        +  }
        +  failures = 0;
        +  long filelen = getFileLength();
        +  if ((position < 0) || (position >= filelen)) {
        +    return -1;
        +  }
        +  int realLen = length;
        +  if ((position + length) > filelen) {
        +    realLen = (int)(filelen - position);
        +  }
        +  //
        +  // determine the block and byte range within the block
        +  // corresponding to position and realLen
        +  // 判断块内的块和字节范围,位置和实际的长度
        +  List<LocatedBlock> blockRange = getBlockRange(position, realLen);
        +  int remaining = realLen;
        +  for (LocatedBlock blk : blockRange) {
        +    long targetStart = position - blk.getStartOffset();
        +    long bytesToRead = Math.min(remaining, blk.getBlockSize() - targetStart);
        +    fetchBlockByteRange(blk, targetStart, 
        +                        targetStart + bytesToRead - 1, buffer, offset);
        +    remaining -= bytesToRead;
        +    position += bytesToRead;
        +    offset += bytesToRead;
        +  }
        +  assert remaining == 0 : "Wrong number of bytes read.";
        +  if (stats != null) {
        +    stats.incrementBytesRead(realLen);
        +  }
        +  return realLen;
        +} 
        +
        // fetchBlockByteRange() 通过 socket 连接一个最优的 DataNode 来读取数据
        +private void fetchBlockByteRange(LocatedBlock block, long start,
        +                                 long end, byte[] buf, int offset) throws IOException {
        +  //
        +  // Connect to best DataNode for desired Block, with potential offset
        +  //
        +  Socket dn = null;
        +  int refetchToken = 1; // only need to get a new access token once
        +  //      
        +  while (true) {
        +    // cached block locations may have been updated by chooseDataNode()
        +    // or fetchBlockAt(). Always get the latest list of locations at the 
        +    // start of the loop.
        +    block = getBlockAt(block.getStartOffset(), false);
        +    DNAddrPair retval = chooseDataNode(block); // 选者最DataNode
        +    DatanodeInfo chosenNode = retval.info;
        +    InetSocketAddress targetAddr = retval.addr;
        +    BlockReader reader = null;
        +    try {
        +      Token<BlockTokenIdentifier> accessToken = block.getBlockToken();
        +      int len = (int) (end - start + 1);
        +  //
        +      // first try reading the block locally.
        +      if (shouldTryShortCircuitRead(targetAddr)) {// 本地优先
        +        try {
        +          reader = getLocalBlockReader(conf, src, block.getBlock(),
        +              accessToken, chosenNode, DFSClient.this.socketTimeout, start);
        +        } catch (AccessControlException ex) {
        +          LOG.warn("Short circuit access failed ", ex);
        +          //Disable short circuit reads
        +          shortCircuitLocalReads = false;
        +          continue;
        +        }
        +      } else {
        +        // go to the datanode
        +        dn = socketFactory.createSocket(); // socke datanode
        +        LOG.debug("Connecting to " + targetAddr);
        +        NetUtils.connect(dn, targetAddr, getRandomLocalInterfaceAddr(),
        +            socketTimeout);
        +        dn.setSoTimeout(socketTimeout);
        +        reader = RemoteBlockReader.newBlockReader(dn, src, 
        +            block.getBlock().getBlockId(), accessToken,
        +            block.getBlock().getGenerationStamp(), start, len, buffersize, 
        +            verifyChecksum, clientName);
        +      }
        +      int nread = reader.readAll(buf, offset, len); // BlockReader 负责读取数据
        +      return;
        +    }
        +    … … 
        +    finally {
        +      IOUtils.closeStream(reader);
        +      IOUtils.closeSocket(dn);
        +    }
        +    // Put chosen node into dead list, continue
        +    addToDeadNodes(chosenNode); // dead datanode
        +  }
        +}
        +
      6. +
      7. NameNode 实例化启动时便监听客户端请求

        +
        DataNode(final Configuration conf,
        +       final AbstractList<File> dataDirs, SecureResources resources) throws IOException {
        +super(conf);
        +SecurityUtil.login(conf, DFSConfigKeys.DFS_DATANODE_KEYTAB_FILE_KEY, 
        +    DFSConfigKeys.DFS_DATANODE_USER_NAME_KEY);
        +//
        +datanodeObject = this;
        +durableSync = conf.getBoolean("dfs.durable.sync", true);
        +this.userWithLocalPathAccess = conf
        +    .get(DFSConfigKeys.DFS_BLOCK_LOCAL_PATH_ACCESS_USER_KEY);
        +try {
        +  startDataNode(conf, dataDirs, resources);// startDataNode
        +} catch (IOException ie) {
        +  shutdown();
        +  throw ie;
        +}   
        +}
        +
        // startDataNode
        +void startDataNode(Configuration conf, 
        +                 AbstractList<File> dataDirs, SecureResources resources
        +                 ) throws IOException {
        +… …                      
        +// find free port or use privileged port provide
        +ServerSocket ss;
        +if(secureResources == null) {
        +  ss = (socketWriteTimeout > 0) ? 
        +    ServerSocketChannel.open().socket() : new ServerSocket();
        +  Server.bind(ss, socAddr, 0);
        +} else {
        +  ss = resources.getStreamingSocket();
        +}
        +ss.setReceiveBufferSize(DEFAULT_DATA_SOCKET_SIZE); 
        +// adjust machine name with the actual port
        +tmpPort = ss.getLocalPort();
        +selfAddr = new InetSocketAddress(ss.getInetAddress().getHostAddress(),
        +//                                     tmpPort);
        +this.dnRegistration.setName(machineName + ":" + tmpPort);
        +LOG.info("Opened data transfer server at " + tmpPort);
        +//
        +this.threadGroup = new ThreadGroup("dataXceiverServer");
        +this.dataXceiverServer = new Daemon(threadGroup, 
        +    new DataXceiverServer(ss, conf, this));
        +this.threadGroup.setDaemon(true); // DataXceiverServer为守护线程监控客户端连接
        +}
        +
        // class DataXceiverServer.run()
        +public void run() {
        +while (datanode.shouldRun) {
        +  try {
        +    Socket s = ss.accept();
        +    s.setTcpNoDelay(true);
        +    new Daemon(datanode.threadGroup, 
        +        new DataXceiver(s, datanode, this)).start();
        +  } catch (SocketTimeoutException ignored) {
        +  }
        +}
        +}   
        +
        // class DataXceiver.run()
        +// Read/write data from/to the DataXceiveServer.
        +// 操作类型:OP_READ_BLOCK,OP_WRITE_BLOCK,OP_REPLACE_BLOCK,
        +// OP_COPY_BLOCK,OP_BLOCK_CHECKSUM
        +public void run() {
        +DataInputStream in=null; 
        +try {
        +  in = new DataInputStream(
        +      new BufferedInputStream(NetUtils.getInputStream(s), 
        +                              SMALL_BUFFER_SIZE));
        +… … 
        +  switch ( op ) {
        +  case DataTransferProtocol.OP_READ_BLOCK:
        +    readBlock( in );// 读数据
        +    datanode.myMetrics.addReadBlockOp(DataNode.now() - startTime);
        +    if (local)
        +      datanode.myMetrics.incrReadsFromLocalClient();
        +    else
        +      datanode.myMetrics.incrReadsFromRemoteClient();
        +    break;
        +… … 
        +  default:
        +    throw new IOException("Unknown opcode " + op + " in data stream");
        +  }
        +}  
        +
        // class DataXceiver.readBlock()
        +// Read a block from the disk.
        +private void readBlock(DataInputStream in) throws IOException {
        +//
        +// Read in the header,读指令
        +//
        +long blockId = in.readLong();          
        +Block block = new Block( blockId, 0 , in.readLong());
        +// 
        +long startOffset = in.readLong();
        +long length = in.readLong();
        +String clientName = Text.readString(in);
        +Token<BlockTokenIdentifier> accessToken = new Token<BlockTokenIdentifier>();
        +accessToken.readFields(in);
        +// 向客户端写数据
        +OutputStream baseStream = NetUtils.getOutputStream(s, 
        +    datanode.socketWriteTimeout);
        +DataOutputStream out = new DataOutputStream(
        +             new BufferedOutputStream(baseStream, SMALL_BUFFER_SIZE));
        +… … 
        +// send the block,读取本地的block的数据,并发送给客户端
        +BlockSender blockSender = null;
        +final String clientTraceFmt =
        +  clientName.length() > 0 && ClientTraceLog.isInfoEnabled()
        +    ? String.format(DN_CLIENTTRACE_FORMAT, localAddress, remoteAddress,
        +        "%d", "HDFS_READ", clientName, "%d", 
        +        datanode.dnRegistration.getStorageID(), block, "%d")
        +    : datanode.dnRegistration + " Served " + block + " to " +
        +        s.getInetAddress();
        +try {
        +  try {
        +    blockSender = new BlockSender(block, startOffset, length,
        +        true, true, false, datanode, clientTraceFmt);
        +  } catch(IOException e) {
        +    out.writeShort(DataTransferProtocol.OP_STATUS_ERROR);
        +    throw e;
        +  }
        +  out.writeShort(DataTransferProtocol.OP_STATUS_SUCCESS); // send op status
        +  long read = blockSender.sendBlock(out, baseStream, null); // send data,发送数据
        +  … … 
        +} finally {
        +  IOUtils.closeStream(out);
        +  IOUtils.closeStream(blockSender);
        +}
        +}
        +
      8. +
      +

      HDFS 写过程

      +

      image

      +
        +
      1. 客户端通过对 DistributedFileSystem 对象调用 create() 方法来创建文件(步骤1)。
      2. +
      3. DistributedFileSystem 通过 PRC 对 namenode 调用create() 方法,在文件系统的命名空间中创建一个新的没有数据块文件(步骤2)。
      4. +
      5. namenode 检查并确保此文件不存在,并且客户端由创建该文件的权限。通过 namenode 即为创建的新文件创建一条纪录;否则,创建失败,并向客户端抛出 IOException 异常。
      6. +
      7. DistributedFileSystem 向客户端返回一个 FSDataOutputStream 对象。客户端可以开始写数据。FSDataOutputStream 同样封装一个 DFSoutPutstream 对象。由 DFSInputStream 负责处理 DataNode 和 NameNode 的 I/O。
      8. +
      9. (步骤3)在客户端写数据时,DFSoutPutstream 将它分成一个个的数据包,并写入内部队列(数据队列)。
      10. +
      11. 由 DataStreamer(DFSClient 内部类) 处理数据队列。它根据 datanode 列表要求 namenode 分配适合的新块来存储数据备份。这组 datanode 构成一个管线。假设当前复制数为3,那么管线中将有3个节点。DataStreamer 将数据包流式传输到管线(pipeline)的第一个 datanode 节点。该 datanode 存储数据包并将它发送到管线中的第2个 datanode。 同样地,第二个 datanode 存储该数据包并发哦少年宫到管线中的第3个 datanode(步骤4)。
      12. +
      13. DFSOutputStream 内部维护一个对应的数据包队列等待 datanode 收到确认确认回执(ack queue),当 DFSOutputStream 收到所有的 datanode 确认信息之后,该数据包才从确认队列中删除。
      14. +
      15. 若在写数据时,datanode 发生故障。则先关闭管线,确认把队列中任何数据包都添加回数据队列的最前端,以确保故障节点下游的 datanode 不会漏掉任何一个数据包。并为存储在另一个 datanode 的当前数据块指定一个新的标志,并将该标志发送个 namenode,以便故障的 datanode 在恢复后可以删除存储的部分数据块。从管线中删除故障 datanode 节点并把余下的数据块写入管线中的2个 datanode。Namenode 注意到块复本量不足时,会在另一个节点上创建一个新的复本。后续数据块继续正常处理。一个块在写入期间发生多个 datanode 故障的概率不高,只要写入了最小复本数(dfs.replication.min默认为1),写入即为成功。此块由异步执行复制以达到目标复本数,默认为3。
      16. +
      17. 当客户端结束写入数据,则调用 stream 的 close()函数。此操作将剩余所有的数据包写入 datanode pipeline 中,并等待 ack queue 返回成功。最后通知元数据节点写入完毕。namenode 是通过 Datastreamer 询问的数据块的分配,它在返回成功前只需要等待数据块进行最小量的复制。
      18. +
      +
      源代码理解
      +

      TODO,原笔记已丢失,待补。 +hdfs 架构一页。且读过程和写过程各独立一页。

      +

      NadeNode 和 DataNode 实现的协议

      +

      TODO ,独立 一页

      +

      补充

      +

      详细介绍HDFS读写过程解析

      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/02/hadoop-io-1/index.html b/article/2014/02/hadoop-io-1/index.html new file mode 100644 index 0000000..078c569 --- /dev/null +++ b/article/2014/02/hadoop-io-1/index.html @@ -0,0 +1,597 @@ + + + + + + + Hadoop I/O - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop I/O

      + +

      + + + + + | 评论 +

      +
      + +

      HDFS 对网络IO, 磁盘IO 的操作是比较复杂且开销还比较高的。Hadoop 在设计中使用了内部的原子操作、压缩、随机读写、流式存储、数据完整性校验、序列化、基于文件的数据结构等方面进行 IO 操作。

      +

      数据完整性

      +

      保证数据在传输过程中不损坏,常见的保证数据完整性采用的技术

      +
        +
      • 奇偶校验技术
      • +
      • ECC 内存纠错校验技术
      • +
      • CRC-32 循环冗余校验技术
      • +
      +

      HDFS的数据完整性

      +

      HDFS 会对写入的所有数据计算校验和,并在读取数据时验证校验和。它针对每个由 io.bytes.per.checksum(默认512字节,开销低于1%)指定字节数据技术校验和。

      +

      DataNode 负责在验证收到的数据后存储数据及其校验和。从客户端和其它数据节点复制过来的数据。客户端写入数据并且将它发送到一个数据节点管线中,在管线的最后一个数据节点验证校验和。

      +

      客户端读取 DataNode 上的数据时,也会验证校验和。将其与 DataNode 上存储的校验和进行对比。每个 DataNode 维护一个连续的校验和验证日志,因此它知道每个数据块最后验证的时间。

      +

      每个 DataNode 还会在后台线程运行一个 DataBlockScanner(数据块检测程序),定期验证存储在数据节点上的所有块,以解决物理存储媒介上位损坏问题。

      +

      HDFS 通过复制完整的数据复本来修复损坏的数据块,进而得到一个新的、完好无损的复本。基本思路:如果客户端读取数据块时检测到错误,就向 NameNode 汇报已损坏的数据块及它试图从名称节点中要读取的 DataNode,并抛出 ChecksumException。 NameNode 将这个已损坏的数据块复本标记为已损坏,并不直接与 datanode 联系,或尝试将这个个复本复制到另一个 datanode。之后,namennode 安排这个数据块的一个复本复制到另一个 datanode。 至此,数据块复制因子恢复到期望水平。此后,并将已损坏的数据块复本删除。

      +

      LocalFileSystem

      +

      Hadoop的 LocalFileSystem 执行客户端校验。意味着,在写一个名filename的文件时,文件系统的客户端以透明的方式创建一个隐藏.filename.crc。在同一个文件夹下,包含每个文件块的校验和。

      +

      禁用校验和,使用底层文件系统原生支持校验和。这里通过 RawLocalFileSystem 来替代 LocalFileSystem 完成。要在一个应用中全局使用,只需要设置 fs.file.impl值为 org.apache.hadoop.fs.RawLocalFileSystem 来重新 map 执行文件的 URL。或者只想对某些读取禁用校验和校验。例:

      +
      Configuration conf = ...
      +FileSystem fs = new RawLocalFileSystem();
      +fs.initialize(null, conf);
      +

      ChecksumFileSystem

      +

      LocalFileSystem 继承自 ChecksumFileSystem(校验和文件系统),ChecksumFileSystem 继承自 FileSystem。ChecksumFileSystem 可以很容易的添加校验和功能到其他文件系统中。

      +

      压缩

      +

      将文件压缩有两大好处

      +
        +
      • 减少存储文件所需要的磁盘空间
      • +
      • 加速数据在网络和磁盘上的传输
      • +
      +

      编译native-hadoop

      +

      参见 《Native-hadoop 编译》

      +

      压缩算法

      +

      所有的压缩算法都需要权衡时间/空间比.压缩和解压缩速度越快,节省空间越少。gizp压缩空间/时间性能比较适中。bzip2比gzip更高效,但数度更慢; lzo 压缩速度比gzip比较快,但是压缩效率稍微低一点。

      + +

      Hadoop支持的压缩格式

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      压缩格式 工具 算法 文件扩展名 多文件 可切分
      DEFLATE DEFLATE .deflate
      GzipgzipDEFLATE.gz
      bzip2bzip2bzip2.bz
      LZOLzopLZO.lzo
      +
      +


      +编码/解码 +用以执行压缩解压算法,是否有java/原生实现

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      压缩格式 codecjava实现原生实现
      DEFLATEorg.apache.hadoop.io.compress.DefaultCodec
      gziporg.apache.hadoop.io.compress.GzipCodec
      bzip2org.apache.hadoop.io.compress.Bzip2Codec
      LZOcom.hadoop.compression.lzo.LzopCodec
      +
      +


      +压缩算法相关的 API

      +

      使用 CompressionCodecFactory.getCodec()方法来推断 CompressionCodec 具体实现。由 CompressionCodec 接口的实现对流进行进行压缩与解压缩。CodecPool 提供了重复利用压缩和解压缩的对象的机制。

      +

      … … 画个类图。## TOTO

      +

      NativeCodeLoader 加载 native-hadoop library +若想使用 snappycode 首先加载 snappy.so,再判断加载 native hadoop–>hadoop.so。native hadoop 中包含了 java 中申明的native 方法,由 native 方法去调用第三方的 natvie library。native_libraries官方参考文档

      +

      在 Hadoop core-site.xml 配置文件中可以设置是否使用本地库,默认以启用。

      +
      <property>
      +  <name>hadoop.native.lib</name>
      +  <value>true</value>
      +  <description>Should native hadoop libraries, if present, be used.</description>
      +</property>
      +

      编写使用压缩的测试程序

      +
        +
      1. 首先下载并编译 snappy,zlib
      2. +
      3. 编写 java 代码 CompressionTest.java, DeCompressionTest.java。 程序是由 maven test 进行。
      4. +
      5. 执行
        $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/hadoop/env/hadoop/lib/native/Linux-amd64-64:/usr/local/lib
        +$ mvn test -Dtest=com.kangfoo.study.hadoop1.io.CompressionTest
        +$ mvn test -Dtest=com.kangfoo.study.hadoop1.io.DeCompressionTest
        +## 结果:
        +rw-rw-r--. 1 hadoop hadoop 531859 7月  23 2013 releasenotes.html
        +-rw-rw-r--. 1 hadoop hadoop 140903 1月  22 15:13 releasenotes.html.deflate
        +-rw-rw-r--. 1 hadoop hadoop 531859 1月  22 15:22 releasenotes.html.deflate.decp
        +-rw-rw-r--. 1 hadoop hadoop 140915 1月  22 15:13 releasenotes.html.gz
        +-rw-rw-r--. 1 hadoop hadoop 531859 1月  22 15:22 releasenotes.html.gz.decp
        +-rw-rw-r--. 1 hadoop hadoop 224661 1月  22 15:13 releasenotes.html.snappy
        +-rw-rw-r--. 1 hadoop hadoop 531859 1月  22 15:22 releasenotes.html.snappy.decp
        +## 日志:
        +Running com.kangfoo.study.hadoop1.io.CompressionTest
        +2014-01-22 15:13:31,312 WARN  snappy.LoadSnappy (LoadSnappy.java:<clinit>(36)) - Snappy native library is available
        +2014-01-22 15:13:31,357 INFO  util.NativeCodeLoader (NativeCodeLoader.java:<clinit>(43)) - Loaded the native-hadoop library
        +2014-01-22 15:13:31,357 INFO  snappy.LoadSnappy (LoadSnappy.java:<clinit>(44)) - Snappy native library loaded
        +2014-01-22 15:13:31,617 INFO  zlib.ZlibFactory (ZlibFactory.java:<clinit>(47)) - Successfully loaded & initialized native-zlib library
        +
      6. +
      +

      启用压缩

      +

      出于性能考虑,使用原生的压缩库要比同时提供 java 实现的开销更小。可以修改 Hadoop core-site.xml 配置文件 io.compression.codecs 以启用压缩,前提是必须安装好对应的原生压缩库依赖,并配置正确的 Codec。

      +
        +
      • 属性名: io.compression.codecs
      • +
      • 默认值:org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.ompress.Bzip2Codec
      • +
      +

      压缩与输入分割

      +

      考虑如何压缩将由 MapReduce 处理的数据时,是否支持分割很重要。

      +

      案例假设,一个gzip压缩的文件的为1GB。HDFS 将其分为16块(64mb 块大小),其中每一数据块最为一个 map 任务输入。那么在 map 任务中,每一个分块是无法独立工作的( gzip 是使用的 DEFLATE 算法,它将数据存储在一系列的压缩块中。无法实现从数据流的任意位置读取数据,那么这些分块必须全部读取并与整个数据流进行同步才能从任意位置进行读取数据)。这样就失去了本地化的优势。一个 map 要处理其他15个分块的数据,而大多数据并不存储在当前 map 节点上。Map的任务数越少,作业的粒度就较大,运行的时间可能会更长。

      +

      具体应该选择哪种压缩形式,还要经过测试,才可以决定。大文件选择支持分割的压缩形式,目前只有 bzip2 支持分片,但没有原生库的实现。或者使用 SequenceFile, MapFile 数据格式进行小文件的合并再存储,这样可以满足分片。

      +

      在 MapReduce 中使用压缩

      +

      如果文件是压缩过的,那么在被 MapReduce 读取时,它们会被解压,根据文件扩展名选择对应的解码器。可参考 MapReduce 块压缩相关知识。

      +

      压缩 MapReduce 的作业输出

      +
        +
      1. 在作业配置中将 mapred.output.compress 属性设置为 true
      2. +
      3. 将 mapred.output.compression.codec 属性设置为自己需要使用的压缩解码/编码器的类名。
      4. +
      +

      代码示例

      +
      conf.setBoolean(“mapred.output.compress”,true)
      +Conf.setClass(“mapred.output.compression.codec”,GizpCodec.class,CompressionCodec.class);
      +

      对 Map 任务输出结果的压缩

      +

      压缩 Map 作业的中间结果以减少网络传输。

      +

      Map输出压缩属性
      +属性名称: mapred.compress.map.output
      +类型: boolean
      +默认值: false
      +描述: 对 map 任务输出是否进行压缩
      +
      +属性名称: mapred.map.output.compression.codec
      +类型: Class
      +默认值: org.apache.hadoop.io.compress.DefaultCodec
      +描述: map 输出所用的压缩 codec

      +

      代码示例

      +
      conf.setCompressMapOutput(true);
      +conf.setMapOutputCompressorClass(GzipCodec.classs)
      +


      +

      序列化和反序列化

      +

      什么是Hadoop的序列化? 序列化,将结构化对象转换为字节流,以便于在网络传输和磁盘存储的过程。反序列化,将字节流转化为结构化的对象的逆过程。可用于进程间的通讯和永久存储,数据拷贝

      +

      序列化特点:

      +
        +
      • 紧凑:可充分利用网络带宽(哈夫曼编码)
      • +
      • 快速:尽量减少序列化和反序列化的开销
      • +
      • 可扩展:通讯协议升级向下兼容
      • +
      • 互操作:支持不同语言间的通讯
      • +
      +

      Hadoop1.x 仅满足了紧凑和快速两个特性。 +java 自身提供的序列化并不精简。Java Serializaiton 是序列化图对象的通讯机制,它有序列化和反序列化的开销。 +java 序列化比较复杂,不能很精简的控制对象的读写。连接/延迟/缓冲。java 序列化不能满足: 精简,快速,可扩展,可互操作。

      +

      Hadoop1.x 使用 Writable 实现自己的序列化格式。它结构紧凑,快速。但难以用 java 以外的语言进行扩展。

      +

      Writable 接口

      +

      Writeable 接口定义了2个方法:

      +
      void write(DataOutput out) throws IOException; // 将其状态写入二进制格式的 DataOutput 流;
      +void readFields(DataInput in) throws IOException; // 从二进制格式的 DataInput 流读取其状态
      +

      画个类图 ## TODO

      +
      writable
      +writableComparable(interface WritableComparable<T> extends Writable, Comparable<T> )
      +comparator(int compare(T o1, T o2);)
      +comparable(public int compareTo(T o);)
      +rawcomparator(public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2);)
      +writablecomparator(ReflectionUtils.newInstance(keyClass, null);)
      +

      Writable 类的层次结构 +image

      +

      部分类型列举

      +
        +
      • NullWritable 是一种特殊的Writable类型,单例的, 序列化的长度是零。可以做占位符。
      • +
      • Text 是针对UTF-8序列化的Writable类。一般可等价于 java.lang.String 的 Writable。Text是可变的。
      • +
      • BytesWritable 是一个对二进制的封装,序列化为一个格式为一个用于制定后面数据字节数的整数域(4字节),后跟字节本身。它是可变的。如:
        BytesWritable b = new BytesWritable(new byte[]{2,5,127}); // 3个长度
        +byte[] bytes = serialize(b);
        +assertThat(StringUtils.byteToHexString(bytes), is("0000000302057f"));
        +
      • +
      • ObjectWritable 适用于java基本类型(String,enum,Writable,null或者这些类型组成的数组)的一个封装。
      • +
      • Writable集合。ArrayWritable和TwoDArrayWritable针对于数组和二维数组,它们中所有的元素必须是同一个类的实例。MapWritable和SortedMapWritable是针对于 Map 和 SorMap。
      • +
      +

      自定义Writable +•实现WritableComparable +•实现

      +
      write(); // 将对象转换为字节流并写入到输出流 out 中
      +readFields(); // 从输入流 in 中读取字节流并反序列化为对象
      +compareTo()方法。 // 将 this 对像与对象 O 比较
      +

      示例程序代码

      + +

      序列化框架

      +
        +
      • apache avro 旨在解决Hadoop中Writable类型的不足:缺乏语言的可移植性。
      • +
      • apache thrift 可伸缩的跨语言, 提供了 PRC 实现层。
      • +
      • Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。
      • +
      +

      参考

      + +

      基于文件的数据结构

      +

      使用 SequenceFile, MapFile 主要解决的问题是:支持分片的数据压缩格式的比较有限,对于某些应用而言,需要处理的数据格式来存储自己的格式,MapRedurce 需要更高级的容器。

      +

      SequenceFile

      +
        +
      1. 文件是以二进制键/值对形式存储的平面文件
      2. +
      3. 可以作为小文件的容器,它将小文件包装起来,以获取更高效率的存储和处理
      4. +
      5. 存储在 SequenceFile 中的 key/valu e并不一定是 Writable 类型
      6. +
      7. 可使用 append()方法在文件末位附加 key/value 对
      8. +
      +

      好处

      +
        +
      1. 支持纪录或者块压缩
      2. +
      3. 支持splittable, 可作为mapreduce输入分片
      4. +
      5. 修改简单(har是不可以修改的)
      6. +
      +

      SequenceFile 压缩

      +

      SequenceFile 文件格式内部结构与是否启用压缩有关。启用压缩又分两类:纪录压缩;数据块压缩。

      +
        +
      1. 无压缩。 默认是不启用压缩,则每个纪录就是它的纪录长度(字节数)、键长度、键和值组成。长度字段为4字节的整数。

        +
      2. +
      3. 纪录压缩。其格式与无压缩情况相同,不同在于纪录压缩的值需要通过文件头中定义的压缩codec进行压缩。键不压缩。
        +无压缩和纪录压缩的示意图: +image

        +
      4. +
      5. 块压缩。一次压缩多条纪录,比单条纪录压缩效率高。可以不断的向数据块中压缩纪录,直到字节数不小于io.seqfile.compress.blocksize属性中设置的字节数。默认1MB.每个新的块的开始处都需要插入同步标识。数据块的格式如下:首先是一个指示数据块中字节数的字段;紧跟是4个压缩字段(键长度、键、值长度、值)。块压缩示意图如下: +image

        +
      6. +
      +

      实例程序代码

      + +

      运行结果

      +
      ## 查看 sequence file
      +$ ./bin/hadoop fs -text /numbers.seq
      +## 排序
      +$ ./bin/hadoop jar ./hadoop-examples-1.2.1.jar sort -r 1 -inFormat org.apache.hadoop.mapred.SequenceFileInputFormat -outFormat org.apache.hadoop.mapred.SequenceFileOutputFormat -outKey org.apache.hadoop.io.IntWritable -outValue org.apache.hadoop.io.Text /numbers.seq sorted
      +## 查看排序后的结果(原键降序排列为从1到100升序排列)
      +$ ./bin/hadoop fs -text /user/hadoop/sorted/part-00000
      +

      博客参考

      +

      MapFile

      +

      MapFile 是已经排序的 SequenceFile,可以视为 java.util.Map 持久化形式。它已加入了搜索键的索引,可以根据 key 进行查找。它的键必须是 WritableComparable 类型的实例,值必须是 Writable 类型的实例,而 SequenceFile 无此要求。使用 MapFile.fix() 方法进行索引重建,把 SequenceFile 转换为 MapFile。

      +

      MapFile java 源代码

      +
      org.apache.hadoop.io.MapFile.Writer{ 
      +// 类的内部结构(MapFile是已经排序的SequenceFile):
      +private SequenceFile.Writer data;
      +private SequenceFile.Writer index;
      +… … 
      +}
      +org.apache.hadoop.io.MapFile.Reader{
      +// 二分法查找。一次磁盘寻址 + 一次最多顺序128(默认值等于每128下一个索引)个条目顺序扫瞄
      +public synchronized Writable get(WritableComparable key, Writable val){
      +… … 
      +}
      +

      实例程序代码

      + +

      运行结果

      +
      $ ./bin/hadoop fs -text /numbers.map/data 
      +$ ./bin/hadoop fs -text /numbers.map/index
      +

      SequenceFile合并为MapFile

      +
        +
      1. 新建SequenceFile文件
        $ ./bin/hadoop jar ./hadoop-examples-1.2.1.jar sort -r 1 -inFormat org.apache.hadoop.mapred.SequenceFileInputFormat -outFormat org.apache.hadoop.mapred.SequenceFileOutputFormat -outKey org.apache.hadoop.io.IntWritable -outValue org.apache.hadoop.io.Text /numbers.seq /numbers2.map
        +
      2. +
      3. 重命名文件夹
        $ ./bin/hadoop fs -mv /numbers2.map/part-00000 /numbers2.map/data
        +
      4. +
      5. 运行测试用例
      6. +
      7. 验证结果
        Created MapFile hdfs://master11:9000/numbers2.map with 100 entries
        +rw-r--r--   2 hadoop      supergroup       4005 2014-02-09 20:06 /numbers2.map/data
        +-rw-r--r--   3 kangfoo-mac supergroup        136 2014-02-09 20:13 /numbers2.map/index
        +
      8. +
      + +
      +
      +

      + + + + + +属于 hadoop + 分类 + + +被贴了 hadoop1 + 标签 +

      + +

      +

      相关文章

      + +

      + « Hadoop pipes 编译 + Hadoop RPC » +

      +
      +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/02/hadoop-ji-jia-gan-zhi/index.html b/article/2014/02/hadoop-ji-jia-gan-zhi/index.html new file mode 100644 index 0000000..c806289 --- /dev/null +++ b/article/2014/02/hadoop-ji-jia-gan-zhi/index.html @@ -0,0 +1,235 @@ + + + + + + + Hadoop 机架感知 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop 机架感知

      + +

      + + + + + | 评论 +

      +
      + +

      HDFS 和 Map/Reduce 的组件是能够感知机架的。

      +

      NameNode 和 JobTracker 通过调用管理员配置模块中的 API resolve 来获取集群里每个 slave 的机架id。该 API 将 slave 的 DNS 名称(或者IP地址)转换成机架id。使用哪个模块是通过配置项 topology.node.switch.mapping.impl 来指定的。模块的默认实现会调用 topology.script.file.name 配置项指定的一个的脚本/命令。 如果 topology.script.file.name 未被设置,对于所有传入的IP地址,模块会返回 /default-rack 作为机架 id。

      +

      在 Map/Reduce 部分还有一个额外的配置项 mapred.cache.task.levels ,该参数决定 cache 的级数(在网络拓扑中)。例如,如果默认值是2,会建立两级的 cache—— 一级针对主机(主机 -> 任务的映射)另一级针对机架(机架 -> 任务的映射)。

      +

      我目前没有模拟环境先纪录个参考博客 机架感知 以备后用。

      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + diff --git a/article/2014/02/hadoop1.x--xue-xi-zhun-bei/index.html b/article/2014/02/hadoop1.x--xue-xi-zhun-bei/index.html new file mode 100644 index 0000000..0276ad8 --- /dev/null +++ b/article/2014/02/hadoop1.x--xue-xi-zhun-bei/index.html @@ -0,0 +1,254 @@ + + + + + + + Hadoop1.x 学习准备 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop1.x 学习准备

      + +

      + + + + + | 评论 +

      +
      + +

      开始学习Hadoop之前先了解下Hadoop之父Doug Cutting 膜拜是必须的。路子是一步不走出来了。各位前辈给我们留下了宝贝的资源。不加以学习利用有些说不过去。

      +

      我个人是从2013年夏天开始拿到 《Hadoop权威指南》第二版的,但由于各种原因,也可以直接说我比较懒,从夏天到冬季中途到是翻过那书,感觉每次都没由什么实际的记忆和理解过程。在12月初,发现开源力量提供了相关的网上视频在线教学课程。选择适合自己的就是对的。 +,我毅然成为了其中的一员。 +目前我没有从事相关的工作,出于在工作之外寻找乐趣,就来了。

      +

      学习之前还是多看些相关的资源比较容易找到赶脚。

      +
      资源列举:
      + +

      部分网站可能是比较难打开的。经常遇到问题谷歌之后发现很多问题还是跳转到这些权威论坛上面了。先登记在案,以备使用。在此仅以纪录我学习《Hadoop权威指南》一书的笔记。 +笔记中图片资源大部分出自于原书英文版,章节内容来自于原书中/英文版 + 开源力量 LouisT 老师ppt/课堂示例及扩展,当然不乏在网站上找到各位博主的精品博客参考。

      +

      我自己的练习代码存放在 github。

      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + diff --git a/article/2014/02/hdfs-api--lian-xi-shi-yong/index.html b/article/2014/02/hdfs-api--lian-xi-shi-yong/index.html new file mode 100644 index 0000000..76cd9db --- /dev/null +++ b/article/2014/02/hdfs-api--lian-xi-shi-yong/index.html @@ -0,0 +1,245 @@ + + + + + + + HDFS API 练习使用 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      HDFS API 练习使用

      + +

      + + + + + | 评论 +

      +
      + +

      经过前几页博客的知识巩固,现在开始使用 Hadoop API 不是什么难事。此处不重点讲述。参考 Hadoop API 利用 FileSystem 实例对象操作 FSDataInputStream/FSDataOutputStream 基本不是问题。

      +

      HDFS API入门级别的使用

      +
        +
      1. 获取 FileSystem 对象
        +get(Configuration conf)
        +Configuration 对象封装了客户端或者服务器端的 conf/core-site.xml 配置

        +
      2. +
      3. 通过 FileSystem 对象进行文件操作
        +读数据:open()获取FSDataInputStream(它支持随机访问),
        +写数据:create()获取FSDataOutputStream

        +
      4. +
      +

      参考代码:HDFSTest.java

      +

      代码中主要利用 FileSystem 对象进行文件的 读、写、重命名、删除、文件信息获取等操作。

      +
      +
      +

      + + + + + +属于 hadoop + 分类 + + +被贴了 hadoop + 标签 +

      + +

      +

      相关文章

      + +

      + « Hadoop 机架感知 + Java JNI练习 » +

      +
      +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + diff --git a/article/2014/02/java-jni--lian-xi/index.html b/article/2014/02/java-jni--lian-xi/index.html new file mode 100644 index 0000000..e530793 --- /dev/null +++ b/article/2014/02/java-jni--lian-xi/index.html @@ -0,0 +1,252 @@ + + + + + + + Java JNI练习 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Java JNI练习

      + +

      + + + + + | 评论 +

      +
      + +

      在 Hadoop 中大量使用了 JNI 技术以提高性能并利用其他语言已有的成熟算法简化开发难度。如压缩算法、pipes 等。那么在具体学习 native hadoop, hadoop io 前先简单复习下相关知识。在此我主要是参见了 Oracle 官方网站 + IBM developerworks + csdn 论坛 写了个简单的 Hello World 程序。

      +

      此处主要是将我参考的资源进行了列举,已备具体深入学习参考。

      + +

      JNI 编程主要步骤

      +
        +
      1. 编写一个.java
      2. +
      3. javac *.java
      4. +
      5. javah -jni className -> *.h
      6. +
      7. 创建一个.so/.dll 动态链接库文件
      8. +
      +

      编程注意事项

      +
        +
      1. 不要直接使用从java里面传递过来的value.(在java里面的对象在本地调用前可能被jvm析构函数了)
      2. +
      3. 一旦不使用某对象或者变量,要去ReleaseXXX()。
      4. +
      5. 不要在 native code 里面去申请内存
      6. +
      7. 使用 javap -s 查看java 签名
      8. +
      +
      +
      +

      + + + + + +属于 hadoop + 分类 + + +被贴了 hadoop1 + 标签 +

      + +

      +

      相关文章

      + +

      + « HDFS API 练习使用 + native-hadoop 编译 » +

      +
      +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + diff --git a/article/2014/02/nativehadoop--bian-yi/index.html b/article/2014/02/nativehadoop--bian-yi/index.html new file mode 100644 index 0000000..f84a4e0 --- /dev/null +++ b/article/2014/02/nativehadoop--bian-yi/index.html @@ -0,0 +1,253 @@ + + + + + + + native-hadoop 编译 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Native-hadoop 编译

      + +

      + + + + + | 评论 +

      +
      + +

      对我来讲编译 native hadoop 并不是很顺利。现将问题纪录在案。

      +

      主要问题

      +
        +
      1. ivy 联网获取资源并不稳定
      2. +
      3. hadoop-1.2.1/build.xml:62: Execute failed: java.io.IOException: Cannot run program “autoreconf” (in directory “/home/userxxx/hadoop/hadoop-1.2.1/src/native”): java.io.IOException: error=2, No such file or directory
      4. +
      5. [exec] configure: error: Zlib headers were not found… native-hadoop library needs zlib to build. Please install the requisite zlib development package.
      6. +
      7. 多次编译失败之后要记得执行 make distclean 清理一下。
      8. +
      9. 编译完 ant compile-native 之后,启动 hadoop 使用 http 访问 /dfshealth.jsp /jobtracker.jsp HTTP ERROR 404
      10. +
      11. 在 Linux 平台下编译 native hadoop 是不可以的,目前。错误:/hadoop-1.2.1/build.xml:694: exec returned: 1
      12. +
      +

      解决方案

      +
        +
      1. 第一个问题只能多次尝试。
      2. +
      3. 第二,第三个问题主要是是没有安装 zlib。顺便请保证 gcc c++, autoconf, automake, libtool, openssl,openssl-devel 也安装。安装 zlib 请参考 http://www.zlib.net/ 。
      4. +
      5. 第四个问题就是 基本的 make 三部曲的步骤。
      6. +
      7. 第五个问题原因是在 build native 库的同时,生成了 webapps 目录(在当前的 target 这个目录是个基本的结构,没有任何 jsp 等资源,404找不到很正常)。那么当我们编译过build之后,hadoop启动时又指向了这个目录,就导致这个错误。我们就可以直接将这个 build 文件夹删除了或者改脚本。问题搞定了。
      8. +
      9. 第六个问题,援引官方
        +Supported Platforms
        +The native hadoop library is supported on *nix platforms only. The library does not to work with Cygwin or the Mac OS X platform.
        +
        +那就老实点用 *nix platforms,就没事了。
      10. +
      +
      +
      +

      + + + + + +属于 hadoop + 分类 + + +被贴了 hadoop1 + 标签 +

      + +

      +

      相关文章

      + +

      + « Java JNI练习 + Hadoop pipes 编译 » +

      +
      +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + diff --git a/article/2014/02/shi-yong-secondenamenode-hui-fu-namenode/index.html b/article/2014/02/shi-yong-secondenamenode-hui-fu-namenode/index.html new file mode 100644 index 0000000..e875ea3 --- /dev/null +++ b/article/2014/02/shi-yong-secondenamenode-hui-fu-namenode/index.html @@ -0,0 +1,516 @@ + + + + + + + 模拟使用 SecondaryNameNode 恢复 NameNode - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      模拟使用 SecondaryNameNode 恢复 NameNode

      + +

      + + + + + | 评论 +

      +
      + +

      SecondaryNameNode

      +

      在试验前先了解下什么是 SecondaryNameNode、它的原理、检查点等知识点。再依次从开始配置 SecondaryNameNode 检查点、准备测试环境、模拟正常的 NameNode 故障,并手动启动 NameNode 并从 SecondaryNameNode 中恢复 fsimage。

      +

      :此试验思路主要借鉴于开源力量LouisT 老师 Hadoop Development 课程中的SecondaryNameNode章节。

      +

      作用

      +

      主要是为了解决namenode单点故障。不是 namenode 的备份。它周期性的合并 fsimage ( namenode 的镜像)和 editslog(或者 edits——所有对 fsimage 镜像文件操作的步骤),并推送给 namenode 以辅助恢复namenode。

      +

      SecondaryNameNode 定期合并 fsimage 和 edits 日志,将 edits 日志文件大小控制在一个限度下。因为内存需求和 NameNode 在一个数量级上,所以通常 SecondaryNameNode 和 NameNode 运行在不同的机器上。SecondaryNameNode 通过bin/start-dfs.sh 在 conf/masters 中指定的节点上启动。

      +

      在hadoop 2.x 中它的作用可以被两个节点替换:checkpoint node(于 SecondaryNameNode 作用相同), backup node( namenode 的完全备份)

      +

      原理(具体可参见《Hadoop权威指南》第10章 管理Hadoop)

      +

      edits 文件纪录了所有对 fsimage 镜像文件的写操作的步骤。文件系统客户端执行写操作时,这些操作首先会被记录到 edits 文件中。Nodename 在内存中维护文件系统的元数据;当 edits 被修改时,相关元数据也同步更新。内存中的元数据可支持客户端的读请求。

      +

      在每次执行写操作之后,且在向客户端发送成功代码之前,edits 编辑日志都需要更新和同步。当 namedone 向多个目录写数据时,只有在所有写操作执行完毕之后方可返回成功代码,以保证任何操作都不会因为机器故障而丢失。

      +

      fsimage 文件是文件系统元数据的一个永久检查点。它包含文件系统中的所有目录和文件 inode 的序列化谢谢。每个 inode 都是一个文件或目录的元数据的内部描述方式。对于文件来说,包含的信息有“复制级别”、修改时间、访问时间、访问许可、块大小、组成一个文件的块等;对于目录来说,包含的信息有修改时间、访问许可和配额元数据等信息。

      +

      fsimage 是一个大型文件,频繁执行写操作,会使系统运行极慢。并非每一写操作都会更新到 fsimage 文件。 +SecondaryNameNode 辅助 namenode,为 namenode 内存中的文件系统元数据创建检查点,并最终合并并更新 fsimage 镜像和减小 edits 文件。

      +

      SecondaryNameNode 的检查点

      +

      SecondaryNameNode 进程启动是由两个配置参数控制的。

      +
        +
      • fs.checkpoint.period,指定连续两次检查点的最大时间间隔, 默认值是1小时。
      • +
      • fs.checkpoint.size 定义了 edits 日志文件的最大值,一旦超过这个值会导致强制执行检查点(即使没到检查点的最大时间间隔)。默认值是64MB。
      • +
      +

      SecondaryNameNode 检查点的具体步骤

      +

      image

      +
        +
      1. SecondaryNameNode 请求主 namenode 停止使用 edits 文件,暂时将新的操作记录到 edits.new 文件中;
      2. +
      3. SecondaryNameNode 以 http get 复制 主 namenode 中的 fsimage, edits 文件;
      4. +
      5. SecondaryNameNode 将 fsimage 载入到内存,并逐一执行 edits 文件中的操作,创建新的fsimage.ckpt 文件;
      6. +
      7. SecondaryNameNode 以 http post 方式将新的fsimage.ckp 复制到主namenode.
      8. +
      9. 主 namenode 将 fsimage 文件替换为 fsimage.ckpt,同时将 edits.new 文件重命名为 edits。并更新 fstime 文件来记录下次检查点时间。
      10. +
      +

      SecondaryNameNode 保存最新检查点的目录与 NameNode 的目录结构相同。 所以 NameNode 可以在需要的时候读取 SecondaryNameNode上的检查点镜像。

      +

      模拟 NameNode 故障以从 SecondaryNameNode 恢复

      +

      场景假设:如果NameNode上除了最新的检查点以外,所有的其他的历史镜像和 edits 文件都丢失了,NameNode 可以引入这个最新的检查点以恢复。具体模拟步骤如下:

      +
        +
      1. 在配置参数 dfs.name.dir 指定的位置建立一个空文件夹;
      2. +
      3. 把检查点目录的位置赋值给配置参数 fs.checkpoint.dir;
      4. +
      5. 启动NameNode,并加上-importCheckpoint。
      6. +
      +

      NameNode 会从 fs.checkpoint.dir 目录读取检查点,并把它保存在 dfs.name.dir 目录下。 如果 dfs.name.dir 目录下有合法的镜像文件,NameNode 会启动失败。 NameNode 会检查fs.checkpoint.dir 目录下镜像文件的一致性,但是不会去改动它。

      +

      试验从 SecondaryNameNode 中备份恢复 NameNode

      +

      注意:此步骤执行并不能将原的数据文件系统从物理磁盘上移除,同样也不能在新格式化的 namenode 中查看旧的文件系统文件。请确定无误再试验。

      +

      试验知识准备

      +

      命令的使用方法请参考 SecondaryNameNode 命令。在试验前,可先了解些 hadoop 的默认配置 +core-site.xml-default, +hdfs-site.xml-default, +mapred-site.xml-default

      +

      SecondarynameNode 相关属性描述:

      +
      +属性:fs.checkpoint.dir     
      +值:${hadoop.tmp.dir}/dfs/namesecondary
      +描述:Determines where on the local filesystem the DFS secondary name node should store the temporary images to merge. If this is a comma-delimited list of directories then the image is replicated in all of the directories for redundancy.
      +fs.checkpoint.edits.dir
      +
      +属性:${fs.checkpoint.dir}     
      +值:Determines where on the local filesystem the DFS secondary name node should 
      +描述:store the temporary edits to merge. If this is a comma-delimited list of directoires then teh edits is replicated in all of the directoires for redundancy. Default value is same as fs.checkpoint.dir
      +
      +属性:fs.checkpoint.period  
      +值:3600   
      +描述:The number of seconds between two periodic checkpoints.
      +
      +属性:fs.checkpoint.size    
      +值:67108864   
      +描述:The size of the current edit log (in bytes) that triggers a periodic checkpoint even if the fs.checkpoint.period hasn't expired.
      +
      +

      试验环境配置

      +
        +
      1. 首先修改 core-site.xml 文件中的配置,主要是调小了 checkpoint 的周期并指定 SSN 的目录。

        +
        <property>
        +<name>fs.checkpoint.period</name>
        +<value>120</value>
        +</property>
        +<property>
        +<name>fs.checkpoint.dir</name>
        +<value>/home/${user.name}/env/data/snn</value>
        +</property>
        +

        vi hdfs-site.xml 查看 NameNode 数据文件存储路径

        +
        <property>
        + <name>dfs.name.dir</name>
        + <value>/home/${user.name}/env/data/name</value>
        +</property>
        +<property>
        + <name>dfs.data.dir</name>
        + <value>/home/${user.name}/env/data/data</value>
        +</property>
        +
      2. +
      3. 再次,format namenode 。 ./bin/hadoop namenode -format。查看当前的 master namenode namespaceID cat ./name/current/VERSION

        +
        #Tue Jan 21 15:14:40 CST 2014
        +namespaceID=1816120670 ## 文件系统的唯一标识符
        +cTime=0 ## namenode的创建时间,刚格式化为0,升级之后为时间戳
        +storageType=NAME_NODE ## 存储类型
        +layoutVersion=-41 ## 负的整数。描述了hdfs永久性数据结构的版本。与Hadoop的版本无关。与升级有关。
        +
      4. +
      5. 查看 datanode 下的version。cat data/current/VERSION

        +
        #Tue Jan 21 09:51:42 CST 2014
        +namespaceID=80003531
        +storageID=DS-949100596-192.168.56.12-50010-1387691685116
        +cTime=0
        +storageType=DATA_NODE
        +layoutVersion=-41
        +

        若 namespaceID 不相同,请将 datanode 中的id修改为 namenode 相同的 namespaceID。 +同样的步骤修改其他的 datanode. +若是第一次format可以跳过此步骤。此步骤要注意避免如下错误(Incompatible namespaceID):

        +
        2014-01-21 15:07:54,890 ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: java.io.IOException: Incompatible namespaceIDs in /home/hadoop/env/data/data: namenode namespaceID = 2020545490; datanode namespaceID = 80003531
        +
      6. +
      +

      查看 NameNode 试验前正常环境状况

      +
        +
      1. 启动hdfs./bin/start-dfs.sh

        +
      2. +
      3. jps 检查所有的进程(当前NameNode进程正常)

        +
        5832 SecondaryNameNode
        +6293 Jps
        +5681 NameNode
        +2212 DataNode
        +2198 DataNode
        +
      4. +
      5. 创建测试数据

        +
        $ ./bin/hadoop fs -mkdir /test
        +$ ./bin/hadoop fs -lsr /
        +drwxr-xr-x   - hadoop supergroup          0 2014-01-21 15:21 /test
        +[hadoop@master11 hadoop]$ ./bin/hadoop fs -put ivy.xml /test
        +[hadoop@master11 hadoop]$ ./bin/hadoop fs -lsr /
        +drwxr-xr-x   - hadoop supergroup          0 2014-01-21 15:22 /test
        +-rw-r--r--   2 hadoop supergroup      10525 2014-01-21 15:22 /test/ivy.xml
        +
      6. +
      7. 查看 SecondaryNameNode 文件目录

        +
        watch ls ./data/snn/ 
        +current
        +image
        +in_use.l
        +
      8. +
      9. namenode 对应日志

        +
        2014-01-21 15:54:02,654 INFO org.apache.hadoop.hdfs.server.namenode.FSNamesystem: Roll Edit Log from 192.168.56.11
        +2014-01-21 15:54:02,654 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: Number of transactions: 0 Total time for transactions(ms): 0 Number of transactions batched in Syncs: 0 Number of syncs: 0 SyncTimes(ms): 0
        +2014-01-21 15:54:02,655 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: closing edit log: position=4, editlog=/home/hadoop/env/data/name/current/edits
        +2014-01-21 15:54:02,655 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: close success: truncate to 4, editlog=/home/hadoop/env/data/name/current/edits
        +2014-01-21 15:54:02,778 INFO org.apache.hadoop.hdfs.server.namenode.TransferFsImage: Opening connection to http://0.0.0.0:50090/getimage?getimage=1
        +2014-01-21 15:54:02,781 INFO org.apache.hadoop.hdfs.server.namenode.GetImageServlet: Downloaded new fsimage with checksum: 4a75545e83f108e21ef321fb0066ede4
        +2014-01-21 15:54:02,781 INFO org.apache.hadoop.hdfs.server.namenode.FSNamesystem: Roll FSImage from 192.168.56.11
        +2014-01-21 15:54:02,781 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: Number of transactions: 0 Total time for transactions(ms): 0 Number of transactions batched in Syncs: 0 Number of syncs: 1 SyncTimes(ms): 56
        +2014-01-21 15:54:02,784 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: closing edit log: position=4, editlog=/home/hadoop/env/data/name/current/edits.new
        +2014-01-21 15:54:02,784 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: close success: truncate to 4, editlog=/home/hadoop/env/data/name/current/edits.new
        +
      10. +
      11. namenode 文件目录

        +
        $ cd name/
        +$ tree
        +.
        +├── current
        +│   ├── edits
        +│   ├── fsimage
        +│   ├── fstime
        +│   └── VERSION
        +├── image
        +│   └── fsimage
        +├── in_use.lock
        +└── previous.checkpoint
        +├── edits
        +├── fsimage
        +├── fstime
        +└── VERSION
        +
      12. +
      +

      模拟 NameNode 故障

      +
        +
      1. 人为的杀掉 namenode 进程
        kill -9 6690 ## 6690 NameNode
        +删除 namenode 元数据
        +$ rm -rf ./data/name/*
        +删除 Secondary NameNode in_use.lock 文件 
        +$ rm -rf ./snn/in_use.lock
        +
      2. +
      +

      从 SecondaryNameNode 中恢复 NameNode

      +
        +
      1. 启动以 importCheckpoint 方式启动 NameNode。$ ./bin/hadoop namenode -importCheckpoint

        +
      2. +
      3. 验证是否恢复成功

        +
        ## HDFS 文件系统正常
        +$ ./bin/hadoop fsck /
        +The filesystem under path '/' is HEALTHY
        +$ ./bin/hadoop fs -lsr /
        +## 元文件信息已恢复
        +drwxr-xr-x   - hadoop supergroup          0 2014-01-21 15:22 /test
        +-rw-r--r--   2 hadoop supergroup      10525 2014-01-21 15:22 /test/ivy.xml
        +$ tree
        +.
        +├── data
        +├── name(已恢复)
        +│   ├── current
        +│   │   ├── edits
        +│   │   ├── fsimage
        +│   │   ├── fstime
        +│   │   └── VERSION
        +│   ├── image
        +│   │   └── fsimage
        +│   ├── in_use.lock
        +│   └── previous.checkpoint
        +│       ├── edits
        +│       ├── fsimage
        +│       ├── fstime
        +│       └── VERSION
        +├── snn
        +│   ├── current
        +│   │   ├── edits
        +│   │   ├── fsimage
        +│   │   ├── fstime
        +│   │   └── VERSION
        +│   ├── image
        +│   │   └── fsimage
        +│   └── in_use.lock
        +└── tmp
        +9 directories, 16 files
        +
      4. +
      5. 查看恢复日志信息(截取部分信息)

        +
        ## copy fsimage
        +14/01/21 16:57:52 INFO common.Storage: Storage directory /home/hadoop/env/data/name is not formatted.
        +14/01/21 16:57:52 INFO common.Storage: Formatting ...
        +14/01/21 16:57:52 INFO common.Storage: Start loading image file /home/hadoop/env/data/snn/current/fsimage
        +14/01/21 16:57:52 INFO common.Storage: Number of files = 3
        +14/01/21 16:57:52 INFO common.Storage: Number of files under construction = 0
        +14/01/21 16:57:52 INFO common.Storage: Image file /home/hadoop/env/data/snn/current/fsimage of size 274 bytes loaded in 0 seconds.
        +##copy edits
        +4/01/21 16:57:52 INFO namenode.FSEditLog: Start loading edits file /home/hadoop/env/data/snn/current/edits
        +14/01/21 16:57:52 INFO namenode.FSEditLog: EOF of /home/hadoop/env/data/snn/current/edits, reached end of edit log Number of transactions found: 0.  Bytes read: 4
        +14/01/21 16:57:52 INFO namenode.FSEditLog: Start checking end of edit log (/home/hadoop/env/data/snn/current/edits) ...
        +14/01/21 16:57:52 INFO namenode.FSEditLog: Checked the bytes after the end of edit log (/home/hadoop/env/data/snn/current/edits):
        +14/01/21 16:57:52 INFO namenode.FSEditLog:   Padding position  = -1 (-1 means padding not found)
        +14/01/21 16:57:52 INFO namenode.FSEditLog:   Edit log length   = 4
        +14/01/21 16:57:52 INFO namenode.FSEditLog:   Read length       = 4
        +14/01/21 16:57:52 INFO namenode.FSEditLog:   Corruption length = 0
        +14/01/21 16:57:52 INFO namenode.FSEditLog:   Toleration length = 0 (= dfs.namenode.edits.toleration.length)
        +14/01/21 16:57:52 INFO namenode.FSEditLog: Summary: |---------- Read=4 ----------|-- Corrupt=0 --|-- Pad=0 --|
        +14/01/21 16:57:52 INFO namenode.FSEditLog: Edits file /home/hadoop/env/data/snn/current/edits of size 4 edits # 0 loaded in 0 seconds.
        +14/01/21 16:57:52 INFO common.Storage: Image file /home/hadoop/env/data/name/current/fsimage of size 274 bytes saved in 0 seconds.
        +14/01/21 16:57:54 INFO namenode.FSEditLog: closing edit log: position=4, editlog=/home/hadoop/env/data/name/current/edits
        +14/01/21 16:57:54 INFO namenode.FSEditLog: close success: truncate to 4, editlog=/home/hadoop/env/data/name/current/edits
        +14/01/21 16:57:54 INFO namenode.FSEditLog: Number of transactions: 0 Total time for transactions(ms): 0 Number of transactions batched in Syncs: 0 Number of syncs: 0 SyncTimes(ms): 0 
        +## 恢复 fsimage
        +14/01/21 16:57:54 INFO namenode.FSNamesystem: Finished loading FSImage in 1971 msecs
        +14/01/21 16:57:54 INFO hdfs.StateChange: STATE* Safe mode ON
        +... ...
        +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Safe mode termination scan for invalid, over- and under-replicated blocks completed in 15 msec
        +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Leaving safe mode after 33 secs
        +## 离开安全模式 Safe mode is OFF
        +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Safe mode is OFF
        +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Network topology has 1 racks and 2 datanodes
        +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* UnderReplicatedBlocks has 0 blocks
        +
      6. +
      +
      +
      +

      + + + + + +属于 hadoop + 分类 + + +被贴了 hadoop1 + 标签 +

      + +

      +

      相关文章

      + +

      + « Hadoop 分布式文件系统 + Hadoop 机架感知 » +

      +
      +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/03/hadoop--xue-xi-can-kao-bo-ke-hui-zong/index.html b/article/2014/03/hadoop--xue-xi-can-kao-bo-ke-hui-zong/index.html new file mode 100644 index 0000000..3ee0f1d --- /dev/null +++ b/article/2014/03/hadoop--xue-xi-can-kao-bo-ke-hui-zong/index.html @@ -0,0 +1,203 @@ + + + + + + + Hadoop MapReduce 学习参考博客汇总 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop MapReduce 学习参考博客汇总

      + +

      + + + + + | 评论 +

      +
      + +

      TODO.

      +
      +
      +

      + + + + + + + +

      + +

      + « Hadoop Pipes & Streaming +

      +
      +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + diff --git a/article/2014/03/hadoop-mapreduce--gong-zuo-ji-zhi/index.html b/article/2014/03/hadoop-mapreduce--gong-zuo-ji-zhi/index.html new file mode 100644 index 0000000..27634e1 --- /dev/null +++ b/article/2014/03/hadoop-mapreduce--gong-zuo-ji-zhi/index.html @@ -0,0 +1,633 @@ + + + + + + + Hadoop MapReduce 工作机制 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop MapReduce 工作机制

      + +

      + + + + + | 评论 +

      +
      + +

      工作流程

      +
        +
      1. 作业配置
      2. +
      3. 作业提交
      4. +
      5. 作业初始化
      6. +
      7. 作业分配
      8. +
      9. 作业执行
      10. +
      11. 进度和状态更新
      12. +
      13. 作业完成
      14. +
      15. 错误处理
      16. +
      17. 作业调度
      18. +
      19. shule(mapreduce核心)和sort
      20. +
      +

      作业配置

      +

      相对不难理解。 具体略。

      +

      作业提交

      +

      image

      +

      首先熟悉上图,4个实例对象: client jvm、jobTracker、TaskTracker、SharedFileSystem

      +

      MapReduce 作业可以使用 JobClient.runJob(conf) 进行 job 的提交。如上图,这个执行过程主要包含了4个独立的实例。

      +
        +
      • 客户端。提交MapReduce作业。
      • +
      • jobtracker:协调作业的运行。jobtracker一个java应用程序。
      • +
      • tasktracker:运行作业划分后的任务。tasktracker一个java应用程序。
      • +
      • shared filesystem(分布式文件系统,如:HDFS)
      • +
      +

      以下是Hadoop1.x 中旧版本的 MapReduce JobClient API. org.apache.hadoop.mapred.JobClient

      +
      /** JobClient is the primary interface for the user-job to interact with the JobTracker. JobClient provides facilities to submit jobs, track their progress, access component-tasks' reports/logs, get the Map-Reduce cluster status information etc.
      +The job submission process involves:
      +Checking the input and output specifications of the job.
      +Computing the InputSplits for the job.
      +Setup the requisite accounting information for the DistributedCache of the job, if necessary.
      +Copying the job's jar and configuration to the map-reduce system directory on the distributed file-system.
      +Submitting the job to the JobTracker and optionally monitoring it's status.
      +Normally the user creates the application, describes various facets of the job via JobConf and then uses the JobClient to submit the job and monitor its progress. */ 
      +Here is an example on how to use JobClient:
      +     // Create a new JobConf
      +     JobConf job = new JobConf(new Configuration(), MyJob.class);
      +     // Specify various job-specific parameters     
      +     job.setJobName("myjob");
      +     job.setInputPath(new Path("in"));
      +     job.setOutputPath(new Path("out"));
      +     job.setMapperClass(MyJob.MyMapper.class);
      +     job.setReducerClass(MyJob.MyReducer.class);
      +     // Submit the job, then poll for progress until the job is complete
      +     JobClient.runJob(job);   
      +// JobClient.runJob(job) --> JobClient. submitJob(job) -->  submitJobInternal(job) 
      +

      新API放在 org.apache.hadoop.mapreduce.* 包下. 使用 Job 类代替 JobClient。又由job.waitForCompletion(true) 内部进行 JobClient.submitJobInternal() 封装。

      +

      新旧API请参考博文 Hadoop编程笔记(二):Hadoop新旧编程API的区别

      +

      hadoop1.x 旧 API JobClient.runJob(job) 调用submitJob() 之后,便每秒轮询作业进度monitorAndPrintJob。并将其进度、执行结果信息打印到控制台上。

      +

      接着再看看 JobClient 的 submitJob() 方法的实现基本过程。上图步骤 2,3,4.

      +
        +
      1. 向 jobtracker 请求一个新的 jobId. (JobID jobId = jobSubmitClient.getNewJobId(); void org.apache.hadoop.mapred.JobClient.init(JobConf conf) throws IOException , 集群环境下是 RPC JobSubmissionProtocol 代理。本地环境使用 LocalJobRunner。

        +
      2. +
      3. 检查作业的相关的输出路径并提交 job 以及相关的 jar 到 job tracker, 相关的 libjar 通过distributedCache 传递给 jobtracker.

        +
        submitJobInternal(… …); 
        +// -->
        +copyAndConfigureFiles(jobCopy, submitJobDir); 
        +// --> 
        +copyAndConfigureFiles(job, jobSubmitDir, replication); 
        +… 
        +// --> 
        +output.checkOutputSpecs(context);
        +
      4. +
      5. 计算作业的分片。将 SplitMetaInfo 信息写入 JobSplit。 Maptask 的个数 = 输入的文件大小除以块的大小。

        +
        int maps = writeSplits(context, submitJobDir);
        +(JobConf)jobCopy.setNumMapTasks(maps);
        +// --> 
        +maps = writeNewSplits(job, jobSubmitDir); 
        +// --> (重写,要详细)
        +JobSplitWriter.createSplitFiles(jobSubmitDir, conf,
        +    jobSubmitDir.getFileSystem(conf), array); // List<InputSplit> splits = input.getSplits(job); 
        +// -->
        +SplitMetaInfo[] info = writeNewSplits(conf, splits, out);
        +
      6. +
      7. 写JobConf信息到配置文件 job.xml。 jobCopy.writeXml(out);

        +
      8. +
      9. 准备提交job。 RPC 通讯到 JobTracker 或者 LocalJobRunner.

        +
        jobSubmitClient.submitJob(jobId, submitJobDir.toString(), jobCopy.getCredentials());
        +
      10. +
      +

      作业初始化

      +
        +
      1. 当 JobTracker 接收到了 submitJob() 方法的调用后,会把此调用放入一个内部队列中,交由作业调度器(job scheduler)进行调度。

        +
        submitJob(jobId, jobSubmitDir, null, ts, false);
        +// -->
        +jobInfo = new JobInfo(jobId, new Text(ugi.getShortUserName()),
        +      new Path(jobSubmitDir));
        +
      2. +
      3. 作业调度器并对job进行初始化。初始化包括创建一个表示正在运行作业的对象——封装任务和纪录信息,以便跟踪任务的状态和进程(步骤5)。

        +
        job = new JobInProgress(this, this.conf, jobInfo, 0, ts);
        +// -->
        +status = addJob(jobId, job);
        +// -->
        +synchronized (jobs) {
        +  synchronized (taskScheduler) {
        +    jobs.put(job.getProfile().getJobID(), job);
        +    for (JobInProgressListener listener : jobInProgressListeners) {
        +      listener.jobAdded(job);
        +    }
        +  }
        +}
        +
      4. +
      5. 创建任务列表。在 JobInProgress的 initTask()方法中

        +
      6. +
      7. 从共享文件系统中获取 JobClient 已计算好的输入分片信息(步骤6)

        +
      8. +
      9. 创建 Map 任务和 Reduce 任务,为每个 MapTask 和 ReduceTask 生成 TaskProgress 对象。

        +
      10. +
      11. 创建的 reduce 任务的数量由 JobConf 的 mapred.reduce.task 属性决定,可用 setNumReduceTasks() 方法设置,然后调度器创建相应数量的要运行的 reduce 任务。任务被分配了 id。

        +
        JobInProgress initTasks() 
        +… …
        +TaskSplitMetaInfo[] splits = createSplits(jobId); // read input splits and create a map per a split
        +// -->
        +allSplitMetaInfo[i] = new JobSplit.TaskSplitMetaInfo(splitIndex, 
        +      splitMetaInfo.getLocations(), 
        +      splitMetaInfo.getInputDataLength());
        +maps = new TaskInProgress[numMapTasks]; // 每个分片创建一个map任务
        +this.reduces = new TaskInProgress[numReduceTasks]; // 创建reduce任务
        +
      12. +
      +

      任务分配

      +

      Tasktracker 和 JobTracker 通过心跳通信分配一个任务

      +
        +
      1. TaskTracker 定期发送心跳,告知 JobTracker, tasktracker 是否还存活,并充当两者之间的消息通道。

        +
      2. +
      3. TaskTracker 主动向 JobTracker 询问是否有作业。若自己有空闲的 solt,就可在心跳阶段得到 JobTracker 发送过来的 Map 任务或 Reduce 任务。对于 map 任务和 task 任务,TaskTracker 有固定数量的任务槽,准确数量由 tasktracker 核的个数核内存的大小来确定。默认调度器在处理 reduce 任务槽之前,会填充满空闲的 map 任务槽,因此,如果 tasktracker 至少有一个空闲的 map 任务槽,tasktracker 会为它选择一个 map 任务,否则选择一个 reduce 任务。选择 map 任务时,jobTracker 会考虑数据本地化(任务运行在输入分片所在的节点),而 reduce 任务不考虑数据本地化。任务还可能是机架本地化。

        +
      4. +
      5. TaskTracker 和 JobTracker heartbeat代码

        +
        TaskTracker.transmitHeartBeat()
        +// -->
        +//
        +// Check if we should ask for a new Task
        +//
        +if (askForNewTask) {
        +  askForNewTask = enoughFreeSpace(localMinSpaceStart);
        +  long freeDiskSpace = getFreeSpace();
        +  long totVmem = getTotalVirtualMemoryOnTT();
        +  long totPmem = getTotalPhysicalMemoryOnTT();
        +  long availableVmem = getAvailableVirtualMemoryOnTT();
        +  long availablePmem = getAvailablePhysicalMemoryOnTT();
        +  long cumuCpuTime = getCumulativeCpuTimeOnTT();
        +  long cpuFreq = getCpuFrequencyOnTT();
        +  int numCpu = getNumProcessorsOnTT();
        +  float cpuUsage = getCpuUsageOnTT();
        +// -->
        +// Xmit the heartbeat
        +HeartbeatResponse heartbeatResponse = jobClient.heartbeat(status, 
        +                                                          justStarted,
        +                                                          justInited,
        +                                                          askForNewTask, 
        +                                                          heartbeatResponseId);
        +注: InterTrackerProtocol jobClient RPC 到 JobTracker.heartbeat() 
        +JobTracker.heartbeat()
        +// -->
        +// Process this heartbeat 
        +short newResponseId = (short)(responseId + 1);
        +status.setLastSeen(now);
        +if (!processHeartbeat(status, initialContact, now)) {
        +  if (prevHeartbeatResponse != null) {
        +    trackerToHeartbeatResponseMap.remove(trackerName);
        +  }
        +  return new HeartbeatResponse(newResponseId, 
        +               new TaskTrackerAction[] {new ReinitTrackerAction()});
        +}
        +
      6. +
      +

      任务执行

      +

      tasktracker 执行任务大致步骤:

      +
        +
      1. 被分配到一个任务后,从共享文件中把作业的jar复制到本地,并将程序执行需要的全部文件(配置信息、数据分片)复制到本地
      2. +
      3. 为任务新建一个本地工作目录
      4. +
      5. 内部类TaskRunner实例启动一个新的jvm运行任务
      6. +
      +

      Tasktracker.TaskRunner.startNewTask()代码

      +
      // -->
      +RunningJob rjob = localizeJob(tip);
      +// -->
      +launchTaskForJob(tip, new JobConf(rjob.getJobConf()), rjob); 
      +// -->
      +tip.launchTask(rjob);
      +// -->
      +setTaskRunner(task.createRunner(TaskTracker.this, this, rjob));
      +this.runner.start(); // MapTaskRunner 或者 ReduceTaskRunner
      +//
      +//startNewTask 方法完整代码:
      +void startNewTask(final TaskInProgress tip) throws InterruptedException {
      +    Thread launchThread = new Thread(new Runnable() {
      +      @Override
      +      public void run() {
      +        try {
      +          RunningJob rjob = localizeJob(tip);//初始化job工作目录
      +          tip.getTask().setJobFile(rjob.getLocalizedJobConf().toString());
      +          // Localization is done. Neither rjob.jobConf nor rjob.ugi can be null
      +          launchTaskForJob(tip, new JobConf(rjob.getJobConf()), rjob); // 启动taskrunner执行task
      +        } catch (Throwable e) {
      +          String msg = ("Error initializing " + tip.getTask().getTaskID() + 
      +                        ":\n" + StringUtils.stringifyException(e));
      +          LOG.warn(msg);
      +          tip.reportDiagnosticInfo(msg);
      +          try {
      +            tip.kill(true);
      +            tip.cleanup(false, true);
      +          } catch (IOException ie2) {
      +            LOG.info("Error cleaning up " + tip.getTask().getTaskID(), ie2);
      +          } catch (InterruptedException ie2) {
      +            LOG.info("Error cleaning up " + tip.getTask().getTaskID(), ie2);
      +          }
      +          if (e instanceof Error) {
      +            LOG.error("TaskLauncher error " + 
      +                StringUtils.stringifyException(e));
      +          }
      +        }
      +      }
      +    });
      +    launchThread.start();
      +  }
      +

      进度和状态更新

      +
        +
      1. 状态包括:作业或认为的状态(成功,失败,运行中)、map 和 reduce 的进度、作业计数器的值、状态消息或描述
      2. +
      3. task 运行时,将自己的状态发送给 TaskTracker,由 TaskTracker 心跳机制向 JobTracker 汇报
      4. +
      5. 状态进度由计数器实现
      6. +
      +

      如图: +image

      +

      作业完成

      +
        +
      1. jobtracker收到最后一个任务完成通知后,便把作业任务状态置为成功
      2. +
      3. 同时jobtracker,tasktracker清理作业的工作状态
      4. +
      +

      错误处理

      +

      task 失败

      +
        +
      1. map 或者 reduce 任务中的用户代码运行异常,子 jvm 在进程退出之前向其父 tasktracker 发送报告, 并打印日志。tasktracker 会将此 task attempt 标记为 failed,释放一个任务槽 slot,以运行另一个任务。streaming 任务以非零退出代码,则标记为 failed.
      2. +
      3. 子进程jvm突然退出(jvm bug)。tasktracker 注意到会将其标记为 failed。
      4. +
      5. 任务挂起。tasktracker 注意到一段时间没有收到进度的更新,便将任务标记为 failed。此 jvm 子进程将被自动杀死。任务超时时间间隔通常为10分钟,使用 mapred.task.timeout 属性进行配置。以毫秒为单位。超时设置为0表示将关闭超时判定,长时间运行不会被标记为 failed,也不会释放任务槽。
      6. +
      7. tasktracker 通过心跳将子任务标记为失败后,自身计数器减一,以便向 jobtracker 申请新的任务
      8. +
      9. jobtracker 通过心跳知道一个 task attempt 失败之后,便重新调度该任务的执行(避开将失败的任务分配给执行失败的tasktracker)。默认执行失败尝试4次,若仍没有执行成功,整个作业就执行失败。
      10. +
      +

      tasktracker 失败

      +
        +
      1. 一个 tasktracker 由于崩溃或者运行过于缓慢而失败,就会停止将 jobtracker 心跳。默认间隔可由 mapred.tasktracker.expriy.interval 设置,毫秒为单位。
      2. +
      3. 同时 jobtracker 将从等待任务调度的 tasktracker 池将此 tasktracker 移除。jobtracker 重新安排此 tasktracker 上已运行并成功完成的 map 任务重新运行。
      4. +
      5. 若 tasktracker 上面的失败任务数远远高于集群的平均失败数,tasktracker 将被列入黑名单。重启后失效。
      6. +
      +

      jobtracker失败

      +

      Hadoop jobtracker 失败是一个单点故障。作业失败。可在后续版本中启动多个 jobtracker,使用zookeeper协调控制(YARN)。

      +

      作业调度

      +
        +
      1. hadoop默认使用先进先出调度器(FIFO) +先遵循优先级优先,在按作业到来顺序调度。缺点:高优先级别的长时间运行的task占用资源,低级优先级,短作业得不到调度。
      2. +
      3. 公平调度器(FairScheduler) +目标:让每个用户公平的共享集群的能力.默认情况下,每个用户都有自己的池。支持抢占,若一个池在特定的时间内未得到公平的资源分配共享,调度器将终止运行池中得到过多资源的任务,以便将任务槽让给资源不足的池。 +详细文档参见:http://hadoop.apache.org/docs/r1.2.1/fair_scheduler.html
      4. +
      5. 容量调度器(CapacityScheduler) +支持多队列,每个队列配置一定的资源,采用FIFO调度策略。对每个用户提交的作业所占的资源进行限定。 +详细文档参见:http://hadoop.apache.org/docs/r1.2.1/capacity_scheduler.html
      6. +
      +

      shuffle和sort

      +

      mapreduce 执行排序,将 map 输出作为输入传递给 reduce 称为 shuffle。其确保每个 reduce 的输入都时按键排序。shuffle 是调优 mapreduce 重要的阶段。

      +

      mapreduce 的 shuffle 和排序如下图: +image

      +

      map端

      +
        +
      1. map端并不是简单的将中间结果输出到磁盘。而是先用缓冲的方式写到内存,并预排序。
      2. +
      3. 每个map任务都有一个环形缓冲区,用于存储任务的输出。默认100mb,由 io.sort.mb 设置。 io.sort.spill.percent 设置阀值,默认80%。
      4. +
      5. 一旦内存缓冲区到达阀值,由一个后台线程将内存中内容 spill 到磁盘中。在写磁盘前,线程会根据数据最终要传送的 reducer 数目划分成相应的分区。每一个分区中,后台线程按键进行内排序,如果有一个 combiner 它会在排序后的输出上运行。
      6. +
      7. 在任务完成之前,多个溢出写文件会被合并成一个已分区已排序的输出文件。最终成为 reduce 的输入文件。属性 io.sort.factor 控制一次最多能合并多少流(分区),默认10.
      8. +
      9. 如果已指定 combiner,并且溢出写文件次数至少为3(min.num.spills.for.combiner 属性),则 combiner 就会在输出文件写到磁盘之前运行。目的时 map 输出更紧凑,写到磁盘上的数据更少。combiner 在输入上反复运行并不影响最终结果。
      10. +
      11. 压缩 map 输出。写磁盘速度更快、节省磁盘空间、减少传给 reduce 数据量。默认不压缩。可使 mapred.compress.map.output=true 启用压缩,并指定压缩库, mapred.map.output.compression.codec。
      12. +
      13. reducer 通过HTTP方式获取输出文件的分区。由于文件分区的工作线程数量任务的 tracker.http.threads 属性控制。
      14. +
      +

      MapTask代码,内部类MapOutputBuffer.collect()方法在收集key/value到容器中,一旦满足预值,则开始溢出写文件由sortAndSpill() 执行。

      +
      // sufficient acct space
      +          kvfull = kvnext == kvstart;
      +          final boolean kvsoftlimit = ((kvnext > kvend)
      +              ? kvnext - kvend > softRecordLimit
      +              : kvend - kvnext <= kvoffsets.length - softRecordLimit);
      +          if (kvstart == kvend && kvsoftlimit) {
      +            LOG.info("Spilling map output: record full = " + kvsoftlimit);
      +            startSpill();
      +          }
      +// --> startSpill();
      + spillReady.signal(); //    private final Condition spillReady = spillLock.newCondition();
      +// --> 溢出写文件主要由内部类 SpillThread(Thread) 执行
      +    try {
      +              spillLock.unlock();
      +              sortAndSpill(); // 排序并溢出
      +            } 
      +// --> sortAndSpill()
      + // create spill file
      +        final SpillRecord spillRec = new SpillRecord(partitions);
      + // sorter = ReflectionUtils.newInstance(job.getClass("map.sort.class", QuickSort.class, IndexedSorter.class), job);
      +… …
      + sorter.sort(MapOutputBuffer.this, kvstart, endPosition, reporter);
      +// -->
      + if (combinerRunner == null) {
      +… …
      + // Note: we would like to avoid the combiner if we've fewer
      +              // than some threshold of records for a partition
      +              if (spstart != spindex) {
      +                combineCollector.setWriter(writer);
      +                RawKeyValueIterator kvIter =
      +                  new MRResultIterator(spstart, spindex);
      +                combinerRunner.combine(kvIter, combineCollector);
      +              }
      +}
      +

      reduce 端

      +
        +
      1. reduce 端 shuffle 过程分为三个阶段:复制 map 输出、排序合并、reduce 处理
      2. +
      3. reduce 可以接收多个 map 的输出。若 map 相当小,则会复制到 reduce tasktracker 的内存中(mapred.job.shuffle.input.buffer.pecent控制百分比)。一旦内存缓冲区达到阀值大小(由 mapped.iob.shuffle.merge.percent 决定)或者达到map输出阀值( mapred.inmem.merge.threshold 控制),则合并后溢出写到磁盘
      4. +
      5. map任务在不同时间完成,tasktracker 通过心跳从 jobtracker 获取 map 输出位置。并开始复制 map 输出文件。
      6. +
      7. reduce 任务由少量复制线程,可并行复制 map 输出文件。由属性 mapred.reduce.parallel.copies 控制。
      8. +
      9. reduce 阶段不会等待所有输入合并成一个大文件后在进行处理,而是把部分合并的结果直接进行处理。
      10. +
      +

      ReduceTask源代码,run()方法

      +
      // --> 3个阶段
      + if (isMapOrReduce()) {
      +      copyPhase = getProgress().addPhase("copy");
      +      sortPhase  = getProgress().addPhase("sort");
      +      reducePhase = getProgress().addPhase("reduce");
      +    }
      +// --> copy 阶段
      +if (!isLocal) {
      +      reduceCopier = new ReduceCopier(umbilical, job, reporter);
      +      if (!reduceCopier.fetchOutputs()) {
      +        if(reduceCopier.mergeThrowable instanceof FSError) {
      +          throw (FSError)reduceCopier.mergeThrowable;
      +        }
      +        throw new IOException("Task: " + getTaskID() + 
      +            " - The reduce copier failed", reduceCopier.mergeThrowable);
      +      }
      +    }
      +    copyPhase.complete();                         // copy is already complete
      +// --> sort 阶段
      +setPhase(TaskStatus.Phase.SORT);
      +    statusUpdate(umbilical);
      +    final FileSystem rfs = FileSystem.getLocal(job).getRaw();
      +    RawKeyValueIterator rIter = isLocal
      +      ? Merger.merge(job, rfs, job.getMapOutputKeyClass(),
      +          job.getMapOutputValueClass(), codec, getMapFiles(rfs, true),
      +          !conf.getKeepFailedTaskFiles(), job.getInt("io.sort.factor", 100),
      +          new Path(getTaskID().toString()), job.getOutputKeyComparator(),
      +          reporter, spilledRecordsCounter, null)
      +      : reduceCopier.createKVIterator(job, rfs, reporter);
      +    // free up the data structures
      +    mapOutputFilesOnDisk.clear();
      +    sortPhase.complete();                         // sort is complete
      +// --> reduce 阶段
      +setPhase(TaskStatus.Phase.REDUCE); 
      +    statusUpdate(umbilical);
      +    Class keyClass = job.getMapOutputKeyClass();
      +    Class valueClass = job.getMapOutputValueClass();
      +    RawComparator comparator = job.getOutputValueGroupingComparator();
      +    if (useNewApi) {
      +      runNewReducer(job, umbilical, reporter, rIter, comparator, 
      +                    keyClass, valueClass);
      +    } else {
      +      runOldReducer(job, umbilical, reporter, rIter, comparator, 
      +                    keyClass, valueClass);
      +    }
      +// --> done 执行结果
      +    done(umbilical, reporter);
      +

      有关mapreduce shuffle和sort 原理、过程和调优

      +

      hadoop作业调优参数整理及原理, MapReduce:详解Shuffle过程 介绍的非常详尽。

      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/03/hadoop-mapreduce--ji-shu-qi/index.html b/article/2014/03/hadoop-mapreduce--ji-shu-qi/index.html new file mode 100644 index 0000000..0afa586 --- /dev/null +++ b/article/2014/03/hadoop-mapreduce--ji-shu-qi/index.html @@ -0,0 +1,342 @@ + + + + + + + Hadoop MapReduce 计数器 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop MapReduce 计数器

      + +

      + + + + + | 评论 +

      +
      + +

      计数器是一种收集系统信息有效手段,用于质量控制或应用级统计。可辅助诊断系统故障。计数器可以比日志更方便的统计事件发生次数。

      +

      内置计数器

      +

      Hadoop 为每个作业维护若干内置计数器,主要用来记录作业的执行情况。

      +

      内置计数器包括

      +
        +
      • MapReduce 框架计数器(Map-Reduce Framework)
      • +
      • 文件系统计数器(FielSystemCounters)
      • +
      • 作业计数器(Job Counters)
      • +
      • 文件输入格式计数器(File Output Format Counters)
      • +
      • 文件输出格式计数器(File Input Format Counters)
      • +
      + + +

      计数器由其关联的 task 进行维护,定期传递给 tasktracker,再由 tasktracker 传给 jobtracker。因此,计数器能够被全局地聚集。内置计数器实际由 jobtracker 维护,不必在整个网络发送。

      +

      一个任务的计数器值每次都是完整传输的,仅当一个作业执行成功之后,计数器的值才完整可靠的。

      +

      自定义Java计数器

      +

      MapReduce 允许用户自定义计数器,MapReduce 框架将跨所有 map 和 reduce 聚集这些计数器,并在作业结束的时候产生一个最终的结果。

      +

      计数器的值可以在 mapper 或者 reducer 中添加。多个计数器可以由一个 java 枚举类型来定义,以便对计数器分组。一个作业可以定义的枚举类型数量不限,个个枚举类型所包含的数量也不限。

      +

      枚举类型的名称即为组的名称,枚举类型的字段即为计数器名称。

      +

      在 TaskInputOutputContext 中的 counter

      +
       public Counter getCounter(Enum<?> counterName) {
      +    return reporter.getCounter(counterName);
      +  }
      +  public Counter getCounter(String groupName, String counterName) {
      +    return reporter.getCounter(groupName, counterName);
      +  }
      +

      计数器递增

      +

      org.apache.hadoop.mapreduce.Counter类

      +
        public synchronized void increment(long incr) {
      +    value += incr;
      +  }
      +

      计数器使用

      +
        +
      • WebUI 查看(50030);
      • +
      • 命令行方式:hadoop job [-counter ];
      • +
      • 使用Hadoop API。 +通过job.getCounters()得到Counters,而后调用counters.findCounter()方法去得到计数器对象;可参见《Hadoop权威指南》第8章 示例 8-2 MissingTemperaureFields.java
      • +
      +

      命令行方式示例

      +
      $ ./bin/hadoop job -counter  job_201402211848_0004 FileSystemCounters HDFS_BYTES_READ
      +177
      +

      自定义计数器

      +

      统计词汇行中词汇数超过2个或少于2个的行数。 源代码: TestCounter.javaTestCounter.java

      +

      输入数据文件值 counter.txt:

      +
      hello world
      +hello
      +hello world 111
      +hello world 111 222
      +

      执行参数

      +
      hdfs://master11:9000/counter/input/a.txt hdfs://master11:9000/counter/output1
      +

      计数器统计(hadoop eclipse 插件执行)结果:

      +
      2014-02-21 00:03:38,676 INFO  mapred.JobClient (Counters.java:log(587)) -   ERROR_COUNTER
      +2014-02-21 00:03:38,677 INFO  mapred.JobClient (Counters.java:log(589)) -     Above_2=2
      +2014-02-21 00:03:38,677 INFO  mapred.JobClient (Counters.java:log(589)) -     BELOW_2=1
      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/03/hadoop-mapreduce--lei-xing-yu-ge-shi/index.html b/article/2014/03/hadoop-mapreduce--lei-xing-yu-ge-shi/index.html new file mode 100644 index 0000000..1a39b53 --- /dev/null +++ b/article/2014/03/hadoop-mapreduce--lei-xing-yu-ge-shi/index.html @@ -0,0 +1,598 @@ + + + + + + + Hadoop MapReduce 类型与格式 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop MapReduce 类型与格式

      + +

      + + + + + | 评论 +

      +
      + +

      MapReduce 的 map和reduce函数的输入和输出是键/值对(key/value pair) 形式的数据处理模型。

      +

      MapReduce 的类型

      +

      Hadoop1.x MapReduce 有2套API.旧api偏向与接口,新api偏向与抽象类,如无特殊默认列举为旧的api作讨论.

      +

      在Hadoop的MapReduce中,map和reduce函数遵循如下格式:

      +
        +
      • map(K1, V1) –> list (K2, V2) // map:对输入分片数据进行过滤数据,组织 key/value 对等操作
      • +
      • combine(K2, list(V2)) –> list(K2, V2) // 在map端对输出进行预处理,类似 reduce。combine 不一定适用任何情况,如:对总和求平均数。选用。
      • +
      • partition(K2, V2) –> integer // 将中间键值对划分到一个 reduce 分区,返回分区索引号。实际上,分区单独由键决定(值是被忽略的),分区内的键会排序,相同的键的所有值会合成一个组(list(V2))
      • +
      • reduce(K2, list(V2)) –> list(K3, V3) // 每个 reduce 会处理具有某些特性的键,每个键上都有值的序列,是通过对所有 map 输出的值进行统计得来的,reduce 根据所有map传来的结果,最后进行统计合并操作,并输出结果。
      • +
      +

      旧api类代码

      +
      public interface Mapper<K1, V1, K2, V2> extends JobConfigurable, Closeable {  
      +  void map(K1 key, V1 value, OutputCollector<K2, V2> output, Reporter reporter) throws IOException;
      +}
      +//
      +public interface Reducer<K2, V2, K3, V3> extends JobConfigurable, Closeable {
      +  void reduce(K2 key, Iterator<V2> values, OutputCollector<K3, V3> output, Reporter reporter) throws IOException;
      +}
      +//
      +public interface Partitioner<K2, V2> extends JobConfigurable {
      +   int getPartition(K2 key, V2 value, int numPartitions);
      +}
      +

      新api类代码

      +
      public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
      +… …
      +  protected void map(KEYIN key, VALUEIN value, 
      +                     Context context) throws IOException, InterruptedException {
      +    context.write((KEYOUT) key, (VALUEOUT) value);
      +  }
      +… …
      +}
      +//
      +public class Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
      +… …
      + protected void reduce(KEYIN key, Iterable<VALUEIN> values, Context context
      +                        ) throws IOException, InterruptedException {
      +    for(VALUEIN value: values) {
      +      context.write((KEYOUT) key, (VALUEOUT) value);
      +    }
      +  }
      +… …
      +}
      +//
      +public interface Partitioner<K2, V2> extends JobConfigurable {
      +  int getPartition(K2 key, V2 value, int numPartitions);
      +}
      +

      默认的 partitioner 是 HashPartitioner,对键进行哈希操作以决定该记录属于哪个分区让 reduce 处理,每个分区对应一个 reducer 任务。总槽数 solt=集群中节点数 * 每个节点的任务槽。实际值应该比理论值要小,以空闲一部分在错误容忍是备用。

      +

      HashPartitioner的实现

      +
      public class HashPartitioner<K, V> extends Partitioner<K, V> {
      +    public int getPartition(K key, V value, int numReduceTasks) {
      +        return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
      +    }
      +}
      +

      hadooop1.x 版本中

      +
        +
      • 旧的api,map 默认的 IdentityMapper, reduce 默认的是 IdentityReducer
      • +
      • 新的api,map 默认的 Mapper, reduce 默认的是 Reducer
      • +
      +

      默认MapReduce函数实例程序

      +
      public class MinimalMapReduceWithDefaults extends Configured implements Tool {
      +    @Override
      +    public int run(String[] args) throws Exception {
      +        Job job = JobBuilder.parseInputAndOutput(this, getConf(), args);
      +        if (job == null) {
      +            return -1;
      +            }
      +        //
      +        job.setInputFormatClass(TextInputFormat.class);
      +        job.setMapperClass(Mapper.class);
      +        job.setMapOutputKeyClass(LongWritable.class);
      +        job.setMapOutputValueClass(Text.class);
      +        job.setPartitionerClass(HashPartitioner.class);
      +        job.setNumReduceTasks(1);
      +        job.setReducerClass(Reducer.class);
      +        job.setOutputKeyClass(LongWritable.class);
      +        job.setOutputValueClass(Text.class);
      +        job.setOutputFormatClass(TextOutputFormat.class);
      +        return job.waitForCompletion(true) ? 0 : 1;
      +        }
      +    //
      +    public static void main(String[] args) throws Exception {
      +        int exitCode = ToolRunner.run(new MinimalMapReduceWithDefaults(), args);
      +        System.exit(exitCode);
      +        }
      +}
      +

      输入格式

      +

      输入分片与记录

      +

      一个输入分片(input split)是由单个 map 处理的输入块,即每一个 map 只处理一个输入分片,每个分片被划分为若干个记录( records ),每条记录就是一个 key/value 对,map 一个接一个的处理每条记录,输入分片和记录都是逻辑的,不必将他们对应到文件上。数据分片由数据块大小决定的。

      +

      注意,一个分片不包含数据本身,而是指向数据的引用( reference )。

      +

      输入分片在Java中被表示为InputSplit抽象类

      +
      public interface InputSplit extends Writable {
      +  long getLength() throws IOException;
      +  String[] getLocations() throws IOException;
      +}
      +

      InputFormat负责创建输入分片并将它们分割成记录,抽象类如下:

      +
      public interface InputFormat<K, V> {
      +  InputSplit[] getSplits(JobConf job, int numSplits) throws IOException;
      +  RecordReader<K, V> getRecordReader(InputSplit split,
      +                                     JobConf job, 
      +                                     Reporter reporter) throws IOException;
      +}
      +

      客户端通过调用 getSpilts() 方法获得分片数目(怎么调到的?),在 TaskTracker 或 NodeManager上,MapTask 会将分片信息传给 InputFormat 的 +createRecordReader() 方法,进而这个方法来获得这个分片的 RecordReader,RecordReader 基本就是记录上的迭代器,MapTask 用一个 RecordReader 来生成记录的 key/value 对,然后再传递给 map 函数,如下步骤:

      +
        +
      1. jobClient调用getSpilts()方法获得分片数目,将numSplits作为参数传入,以参考。InputFomat实现有自己的getSplits()方法。
      2. +
      3. 客户端将他们发送到jobtracker
      4. +
      5. jobtracker使用其存储位置信息来调度map任务从而在tasktracker上处理分片数据
      6. +
      7. 在tasktracker上,map任务把输入分片传给InputFormat上的getRecordReader()方法,来获取分片的RecordReader。
      8. +
      9. map 用一个RecordReader来生成纪录的键值对。
      10. +
      11. RecordReader的next()方法被调用,知道返回false。map任务结束。
      12. +
      +

      MapRunner 类部分代码(旧api)

      +
      public class MapRunner<K1, V1, K2, V2>
      +    implements MapRunnable<K1, V1, K2, V2> {
      +… … 
      + public void run(RecordReader<K1, V1> input, OutputCollector<K2, V2> output,
      +                  Reporter reporter)
      +    throws IOException {
      +    try {
      +      // allocate key & value instances that are re-used for all entries
      +      K1 key = input.createKey();
      +      V1 value = input.createValue();
      +      //
      +      while (input.next(key, value)) {
      +        // map pair to output
      +        mapper.map(key, value, output, reporter);
      +        if(incrProcCount) {
      +          reporter.incrCounter(SkipBadRecords.COUNTER_GROUP, 
      +              SkipBadRecords.COUNTER_MAP_PROCESSED_RECORDS, 1);
      +        }
      +      }
      +    } finally {
      +      mapper.close();
      +    }
      +  }
      +……
      +}
      +

      FileInputFormat类

      +

      FileInputFormat是所有使用文件为数据源的InputFormat实现的基类,它提供了两个功能:一个定义哪些文件包含在一个作业的输入中;一个为输入文件生成分片的实现,把分片割成记录的作业由其子类来完成。

      +

      下图为InputFormat类的层次结构: +image

      +

      FileInputFormat 类输入路径

      +

      FileInputFormat 提供四种静态方法来设定 Job 的输入路径,其中下面的 addInputPath() 方法 addInputPaths() 方法可以将一个或多个路径加入路径列表,setInputPaths() 方法一次设定完整的路径列表(可以替换前面所设路 径)

      +
      public static void addInputPath(Job job, Path path);
      +public static void addInputPaths(Job job, String commaSeparatedPaths);
      +public static void setInputPaths(Job job, Path... inputPaths);
      +public static void setInputPaths(Job job, String commaSeparatedPaths);
      +

      如果需要排除特定文件,可以使用 FileInputFormat 的 setInputPathFilter() 设置一个过滤器: +public static void setInputPathFilter(Job job, Class<? extends PathFilter> filter); +它默认过滤隐藏文件中以”_“和”.“开头的文件

      +
        private static final PathFilter hiddenFileFilter = new PathFilter(){
      +      public boolean accept(Path p){
      +        String name = p.getName(); 
      +        return !name.startsWith("_") && !name.startsWith("."); 
      +      }
      +    }; 
      +

      FileInputFormat 类的输入分片

      +

      FileInputFormat 类一般分割超过 HDFS 块大小的文件。通常分片与 HDFS 块大小一样,然后分片大小也可以改变的,下面展示了控制分片大小的属性:

      +

      待补。 TODO

      +
      FileInputFormat computeSplitSize(long goalSize, long minSize,long blockSize) {
      +    return Math.max(minSize, Math.min(goalSize, blockSize));
      +}
      +

      即: +minimumSize < blockSize < maximumSize 分片的大小即为块大小。

      +

      重载 FileInputFormat 的 isSplitable() =false 可以避免 mapreduce 输入文件被分割。

      +

      小文件与CombineFileInputFormat

      +
        +
      1. CombineFileInputFormat 是针对小文件设计的,CombineFileInputFormat 会把多个文件打包到一个分片中,以便每个 mapper 可以处理更多的数据;减少大量小文件的另一种方法可以使用 SequenceFile 将这些小文件合并成一个或者多个大文件。

        +
      2. +
      3. CombineFileInputFormat 不仅对于处理小文件实际上对于处理大文件也有好处,本质上,CombineFileInputFormat 使 map 操作中处理的数据量与 HDFS 中文件的块大小之间的耦合度降低了

        +
      4. +
      5. CombineFileInputFormat 是一个抽象类,没有提供实体类,所以需要实现一个CombineFileInputFormat 具体 +类和 getRecordReader() 方法(旧的接口是这个方法,新的接口InputFormat中则是createRecordReader())

        +
      6. +
      +

      把整个文件作为一条记录处理

      +

      有时,mapper 需要访问问一个文件中的全部内容。即使不分割文件,仍然需要一个 RecordReader 来读取文件内容为 record 的值,下面给出实现这个功能的完整程序,详细解释见《Hadoop权威指南》。

      +

      文本处理

      +
        +
      1. TextInputFileFormat 是默认的 InputFormat,每一行就是一个纪录

        +
      2. +
      3. TextInputFileFormat 的 key 是 LongWritable 类型,存储该行在整个文件的偏移量,value 是每行的数据内容,不包括任何终止符(换行符和回车符),它是Text类型. +如下例 +On the top of the Crumpetty Tree
        +
        +The Quangle Wangle sat,
        +But his face you could not see,
        +On account of his Beaver Hat.
        +每条记录表示以下key/value对
        +(0, On the top of the Crumpetty Tree)
        +(33, The Quangle Wangle sat,)
        +(57, But his face you could not see,)
        +(89, On account of his Beaver Hat.

        +
      4. +
      5. 输入分片与 HDFS 块之间的关系:TextInputFormat 每一条纪录就是一行,很可能某一行跨数据库存放。

        +
      6. +
      +

      image

      +
        +
      1. KeyValueTextInputFormat。对下面的文本,KeyValueTextInputFormat 比较适合处理,其中可以通过 +mapreduce.input.keyvaluelinerecordreader.key.value.separator 属性设置指定分隔符,默认 +值为制表符,以下指定”→“为分隔符 +
        +line1→On the top of the Crumpetty Tree
        +line2→The Quangle Wangle sat,
        +line3→But his face you could not see,
        +line4→On account of his Beaver Hat.

        +
      2. +
      3. NLineInputFormat。如果希望 mapper 收到固定行数的输入,需要使用 NLineInputFormat 作为 InputFormat 。与 TextInputFormat 一样,key是文件中行的字节偏移量,值是行本身。

        +
      4. +
      +

      N 是每个 mapper 收到的输入行数,默认时 N=1,每个 mapper 会正好收到一行输入,mapreduce.input.lineinputformat.linespermap 属性控制 N 的值。以刚才的文本为例。 +如果N=2,则每个输入分片包括两行。第一个 mapper 会收到前两行 key/value 对:

      +

      (0, On the top of the Crumpetty Tree)
      +(33, The Quangle Wangle sat,)
      +另一个mapper则收到:
      +(57, But his face you could not see,)
      +(89, On account of his Beaver Hat.)

      +

      二进制输入

      +

      SequenceFileInputFormat +如果要用顺序文件数据作为 MapReduce 的输入,应用 SequenceFileInputFormat。key 和 value 顺序文件,所以要保证map输入的类型匹配

      +

      SequenceFileInputFormat 可以读 MapFile 和 SequenceFile,如果在处理顺序文件时遇到目录,SequenceFileInputFormat 类会认为值正在读 MapFile 数据文件。

      +

      SequenceFileAsTextInputFormat 是 SequenceFileInputFormat 的变体。将顺序文件(其实就是SequenceFile)的 key 和 value 转成 Text 对象

      +

      SequenceFileAsBinaryInputFormat是 SequenceFileInputFormat 的变体。将顺序文件的key和value作为二进制对象

      +

      多种输入

      +

      对于不同格式,不同表示的文本文件输出的处理,可以用 MultipleInputs 类里处理,它允许为每条输入路径指定 InputFormat 和 Mapper。

      +

      MultipleInputs 类有一个重载版本的 addInputPath()方法:

      +
        +
      • 旧api列举
        public static void addInputPath(JobConf conf, Path path, Class<? extends InputFormat> inputFormatClass) 
        +
      • +
      • 新api列举
        public static void addInputPath(Job job, Path path, Class<? extends InputFormat> inputFormatClass) 
        +
        在有多种输入格式只有一个mapper时候(调用Job的setMapperClass()方法),这个方法会很有用。
      • +
      +

      DBInputFormat

      +

      JDBC从关系数据库中读取数据的输入格式(参见权威指南)

      +

      输出格式

      +

      OutputFormat类的层次结构

      +

      image

      +

      文本输出

      +

      默认输出格式是 TextOutputFormat,它本每条记录写成文本行,key/value 任意,这里 key和value 可以用制表符分割,用 mapreduce.output.textoutputformat.separator 书信可以改变制表符,与TextOutputFormat 对应的输入格式是 KeyValueTextInputFormat。

      +

      可以使用 NullWritable 来省略输出的 key 和 value。

      +

      二进制输出

      +
        +
      • SequenceFileOutputFormat 将它的输出写为一个顺序文件,因为它的格式紧凑,很容易被压缩,所以易于作为 MapReduce 的输入
      • +
      • 把key/value对作为二进制格式写到一个 SequenceFile 容器中
      • +
      • MapFileOutputFormat 把 MapFile 作为输出,MapFile 中的 key 必需顺序添加,所以必须确保 reducer 输出的 key 已经排好序。
      • +
      +

      多个输出

      +
        +
      • MultipleOutputFormat 类可以将数据写到多个文件中,这些文件名称源于输出的键和值。MultipleOutputFormat是个抽象类,它有两个子类:MultipleTextOutputFormatMultipleSequenceFileOutputFormat 。它们是 TextOutputFormat 的和 SequenceOutputFormat 的多版本。

        +
      • +
      • MultipleOutputs 类 +用于生成多个输出的库,可以为不同的输出产生不同的类型,无法控制输出的命名。它用于在原有输出基础上附加输出。输出是制定名称的。

        +
      • +
      +

      MultipleOutputFormat和MultipleOutputs的区别

      +

      这两个类库的功能几乎相同。MultipleOutputs 功能更齐全,但 MultipleOutputFormat 对 目录结构和文件命令更多de控制。

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      特征 MultipleOutputFormat MultipleOutputs
      完全控制文件名和目录名
      不同输出有不同的键和值类型
      从同一作业的map和reduce使用
      每个纪录多个输出
      与任意OutputFormat一起使用 否,需要子类
      +

      延时输出

      +

      有些文件应用倾向于不创建空文件,此时就可以利用 LazyOutputFormat (Hadoop 0.21.0版本之后开始提供),它是一个封装输出格式,可以保证指定分区第一条记录输出时才真正的创建文件,要使用它,用JobConf和相关输出格式作为参数来调用 setOutputFormatClass() 方法.

      +

      Streaming 和 Pigs 支持 -LazyOutput 选项来启用 LazyOutputFormat功能。

      +

      数据库输出

      +

      参见 关系数据和 HBase的输出格式。

      +

      练习代码

      +

      代码路径 +https://github.com/kangfoo/hadoop1.study/blob/master/kangfoo/study.hdfs/src/main/java/com/kangfoo/study/hadoop1/mp/typeformat
      +使用 maven 打包之后用 hadoop jar 命令执行
      +步骤同 Hadoop example jar 类

      +
        +
      1. 使用 TextInputFormat 类型测试 wordcount +TestMapreduceInputFormat +上传一个文件

        +
        $ ./bin/hadoop fs -mkdir /test/input1
        +$ ./bin/hadoop fs -put ./wordcount.txt /test/input1
        +

        使用maven 打包 或者用eclipse hadoop 插件, 执行主函数时设置如下参数

        +
        hdfs://master11:9000/test/input1/wordcount.txt hdfs://master11:9000/numbers.seq hdfs://master11:9000/test/output5
        +

        没改过端口默认 namenode RPC 交互端口 8020 将上述的 9000 改成你自己的端口即可。
        +部分日志

        +
        ## 准备运行程序和测试数据
        +lrwxrwxrwx.  1 hadoop hadoop      86 2月  17 21:02 study.hdfs-0.0.1-SNAPSHOT.jar -> /home/hadoop/env/kangfoo.study/kangfoo/study.hdfs/target/study.hdfs-0.0.1-SNAPSHOT.jar
        +-rw-rw-r--.  1 hadoop hadoop    1983 2月  17 20:18 wordcount.txt
        +##执行
        +$ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestMapreduceInputFormat /test/input1/wordcount.txt /test/output1
        +
      2. +
      3. 使用SequenceInputFormat类型测试wordcound +使用Hadoop权威指南中的示例创建 /numbers.seq 文件

        +
        $ ./bin/hadoop fs -text /numbers.seq
        +$ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestMapreduceSequenceInputFormat /numbers.seq /test/output2
        +
      4. +
      5. 多文件输入

        +
        $  ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestMapreduceMultipleInputs /test/input1/wordcount.txt /numbers.seq /test/output3
        +
      6. +
      +

      博客参考

      +

      淘宝博客

      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/03/hadoop-mapreduce-combiner--zu-jian/index.html b/article/2014/03/hadoop-mapreduce-combiner--zu-jian/index.html new file mode 100644 index 0000000..bfc96b0 --- /dev/null +++ b/article/2014/03/hadoop-mapreduce-combiner--zu-jian/index.html @@ -0,0 +1,332 @@ + + + + + + + Hadoop MapReduce Combiner 组件 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop MapReduce Combiner 组件

      + +

      + + + + + | 评论 +

      +
      + +
      +

      combiner 作用是把一个 map 产生的多个 合并成一个新的 ,然后再将新的 作为 reduce 的输入;

      +

      combiner 函数在 map 函数与 reduce 函数之间,目的是为了减少 map 输出的中间结果,减少 reduce 复制 map 输出的数据,减少网络传输负载;

      +

      并不是所有情况下都能使用 Combiner 组件,它适用于对记录汇总的场景(如求和,平均数不适用)

      +

      什么时候运行 Combiner

      +
        +
      • 当 job 设置了 Combiner,并且 spill 的个数达到 min.num.spill.for.combine (默认是3)的时候,那么 combiner 就会 Merge 之前执行;
      • +
      • 但是有的情况下,Merge 开始执行,但 spill 文件的个数没有达到需求,这个时候 Combiner 可能会在Merge 之后执行;
      • +
      • Combiner 也有可能不运行,Combiner 会考虑当时集群的一个负载情况。
      • +
      +

      测试 Combinner 过程

      +

      代码 TestCombiner

      +
        +
      1. 以 wordcount.txt 为输入的词频统计

        +
        $ ./bin/hadoop fs -lsr /test3/input
        +drwxr-xr-x   - hadoop supergroup          0 2014-02-18 00:28 /test3/input/test
        +-rw-r--r--   2 hadoop supergroup        983 2014-02-18 00:28 /test3/input/test/wordcount.txt
        +-rw-r--r--   2 hadoop supergroup        626 2014-02-18 00:28 /test3/input/test/wordcount2.txt
        +
      2. +
      3. 不启用 Reducer (输出,字节变大)

        +
        drwxr-xr-x   - kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1
        +-rw-r--r--   3 kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1/_SUCCESS
        +-rw-r--r--   3 kangfoo-mac supergroup       1031 2014-02-18 00:29 /test3/output1/part-m-00000 (-m 没有 reduce 过程的中间结果,每个数据文件对应一个数据分片,每个分片对应一个map任务)
        +-rw-r--r--   3 kangfoo-mac supergroup        703 2014-02-18 00:29 /test3/output1/part-m-00001
        +

        结果如下(map过程并不合并相同key的value值):

        +
        drwxr-xr-x  1
        +-  1
        +hadoop  1
        +supergroup  1
        +0   1
        +2014-02-17  1
        +21:03   1
        +/home/hadoop/env/mapreduce  1
        +drwxr-xr-x  1
        +-  1
        +hadoop  1
        +
      4. +
      5. 启用 Reducer

        +
        drwxr-xr-x   - kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1
        +-rw-r--r--   3 kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1/_SUCCESS
        +-rw-r--r--   3 kangfoo-mac supergroup       1031 2014-02-18 00:29 /test3/output1/part-m-00000
        +-rw-r--r--   3 kangfoo-mac supergroup        703 2014-02-18 00:29 /test3/output1/part-m-00001
        +drwxr-xr-x   - kangfoo-mac supergroup          0 2014-02-18 00:31 /test3/output2
        +-rw-r--r--   3 kangfoo-mac supergroup          0 2014-02-18 00:31 /test3/output2/_SUCCESS
        +-rw-r--r--   3 kangfoo-mac supergroup        705 2014-02-18 00:31 /test3/output2/part-r-00000
        +

        结果:

        +
        0:17:31,680 6
        +014-02-18   1
        +2014-02-17  11
        +2014-02-18  5
        +21:02   7
        +
      6. +
      7. 在日志或者 http://master11:50030/jobtracker.jsp 页面查找是否执行过 Combine 过程。 +日志截取如下:

        +
        2014-02-18 00:31:29,894 INFO  SPLIT_RAW_BYTES=233
        +2014-02-18 00:31:29,894 INFO  Combine input records=140
        +2014-02-18 00:31:29,894 INFO  Reduce input records=43
        +2014-02-18 00:31:29,894 INFO  Reduce input groups=42
        +2014-02-18 00:31:29,894 INFO  Combine output records=43
        +2014-02-18 00:31:29,894 INFO  Reduce output records=42
        +2014-02-18 00:31:29,894 INFO  Map output records=140
        +
      8. +
      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/03/hadoop-mapreduce-join/index.html b/article/2014/03/hadoop-mapreduce-join/index.html new file mode 100644 index 0000000..e4f2d35 --- /dev/null +++ b/article/2014/03/hadoop-mapreduce-join/index.html @@ -0,0 +1,327 @@ + + + + + + + Hadoop MapReduce Join - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop MapReduce Join

      + +

      + + + + + | 评论 +

      +
      + +

      在 Hadoop 中可以通过 MapReduce,Pig,hive,Cascading编程进行大型数据集间的连接操作。连接操作如果由 Mapper 执行,则称为“map端连接”;如果由 Reduce 执行,则称为“Reduce端连接”。

      +

      连接操作的具体实现技术取决于数据集的规模以及分区方式。
      +若一个数据集很大而另一个数据集很小,以至于可以分发到集群中的每一个节点之中,则可以执行一个 MapReduce 作业,将各个数据集的数据放到一起,从而实现连接。
      +若两个数据规模均很大,没有哪个数据集可以完全复制到集群的每个节点,可以使用 MapReduce 作业进行连接,使用 Map 端连接还是 Reduce 端连接取决于数据的组织方式。

      +

      Map端连接将所有的工作在 map 中操作,效率高但是不通用。而 Reduce 端连接利用了 shuff 机制,进行连接,效率不高。

      +

      DistributedCache 能够在任务运行过程中及时地将文件和存档复制到任务节点进行本地缓存以供使用。各个文件通常只复制到一个节点一次。可用 api 或者命令行在需要的时候将本地文件添加到 hdfs 文件系统中。

      +

      本文中的示例 出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件。

      +

      Map端连接

      +

      Map 端联接是指数据到达 map 处理函数之前进行合并的。它要求 map 的输入数据必须先分区并以特定的方式排序。各个输入数据集被划分成相同数量的分区,并均按相同的键排序(连接键)。同一键的所有输入纪录均会放在同一个分区。以满足 MapReduce 作业的输出。

      +

      若作业的 Reduce 数量相同、键相同、输入文件是不可切分的,那么 map 端连接操作可以连接多个作业的输出。

      +

      在 Map 端连接效率比 Reduce 端连接效率高(Reduce端Shuff耗时),但是要求比较苛刻。

      +

      基本思路

      +
        +
      1. 将需要 join 的两个文件,一个存储在 HDFS 中,一个使用 DistributedCache.addCacheFile() 将需要 join 另一个文件加入到所有 Map 的缓存里(DistributedCache.addCacheFile() 需要在作业提交前设置);
      2. +
      3. 在 Map 函数里读取该文件,进行 Join;
      4. +
      5. 将结果输出到 reduce 端;
      6. +
      +

      使用步骤

      +
        +
      1. 在 HDFS 中上传文件(文本文件、压缩文件、jar包等);
      2. +
      3. 调用相关API添加文件信息;
      4. +
      5. task运行前直接调用文件读写API获取文件;
      6. +
      +

      Reduce端Join

      +

      reduce 端联接比 map 端联接更普遍,因为输入的数据不需要特定的结构;效率低(所有数据必须经过shuffle过程)。

      +

      基本思路

      +
        +
      1. Map 端读取所有文件,并在输出的内容里加上标识代表数据是从哪个文件里来的;
      2. +
      3. 在 reduce 处理函数里,对按照标识对数据进行保存;
      4. +
      5. 然后根据 Key 的 Join 来求出结果直接输出;
      6. +
      +

      示例程序

      +

      使用 MapReduce map 端join 或者 reduce 端 join 实现如下两张表 emp, dep 中的 SQL 联合查询的数据效果。

      +
      Table EMP:(新建文件EMP,第一行属性名不要)
      +----------------------------------------
      +Name      Sex      Age     DepNo
      +zhang      male     20           1     
      +li              female  25           2
      +wang       female  30           3
      +zhou        male     35           2
      +----------------------------------------
      +Table Dep:(新建文件DEP,第一行属性名不要)
      +DepNo     DepName
      +     1            Sales
      +     2            Dev
      +     3            Mgt
      +------------------------------------------------------------     
      +SQL:
      +select name,sex ,age, depName from emp inner join DEP on EMP.DepNo = Dep.DepNo
      +----------------------------------------
      +实现效果:
      +$ ./bin/hadoop fs -cat /reduceSideJoin/output11/part-r-00000
      +zhang male 20 sales
      +li female 25 dev
      +wang female 30 dev
      +zhou male 35 dev
      +

      Map 端 Join 的例子:TestMapSideJoin
      +Reduce 端 Join 的例子:TestReduceSideJoin

      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/03/hadoop-mapreduce-partitioner--zu-jian/index.html b/article/2014/03/hadoop-mapreduce-partitioner--zu-jian/index.html new file mode 100644 index 0000000..fc46b52 --- /dev/null +++ b/article/2014/03/hadoop-mapreduce-partitioner--zu-jian/index.html @@ -0,0 +1,325 @@ + + + + + + + Hadoop MapReduce Partitioner 组件 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop MapReduce Partitioner 组件

      + +

      + + + + + | 评论 +

      +
      + +

      Partitioner 过程发生在循环缓冲区发生溢写文件之后,merge 之前。可以让 Map 对 Key 进行分区,从而可以根据不同的 key 来分发到不同的 reducer 中去处理;

      +

      Hadoop默认的提供的是HashPartitioner。

      +

      可以自定义 key 的分发规则,自定义Partitioner:

      +
        +
      • 继承抽象类Partitioner,实现自定义的getPartition()方法;
      • +
      • 通过job.setPartitionerClass()来设置自定义的Partitioner;
      • +
      +

      Partitioner 类

      +

      旧api

      +
      public interface Partitioner<K2, V2> extends JobConfigurable {
      +  int getPartition(K2 key, V2 value, int numPartitions);
      +}
      +

      新api

      +
      public abstract class Partitioner<KEY, VALUE> {
      +  public abstract int getPartition(KEY key, VALUE value, int numPartitions);  
      +}
      +

      Partitioner应用场景演示

      +

      需求:利用 Hadoop MapReduce 作业 Partitioner 组件分别统计每种商品的周销售情况。源代码 TestPartitioner.java出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件。 (可使用 PM2.5 数据代替此演示程序)

      +
        +
      • site1的周销售清单(a.txt,以空格分开):

        +
        shoes   20
        +hat 10
        +stockings   30
        +clothes 40
        +
      • +
      • site2的周销售清单(b.txt,以空格分开):

        +
        shoes   15
        +hat 1
        +stockings   90
        +clothes 80
        +
      • +
      • 汇总结果:

        +
        shoes     35
        +hat       11
        +stockings 120
        +clothes   120
        +
      • +
      • 准备测试数据

        +
        $ ./bin/hadoop fs -mkdir /testPartitioner/input
        +$ ./bin/hadoop fs -put a.txt /testPartitioner/input
        +$ ./bin/hadoop fs -put b.txt /testPartitioner/input
        +$ ./bin/hadoop fs -lsr /testPartitioner/input
        +-rw-r--r--   2 hadoop supergroup         52 2014-02-18 22:53 /testPartitioner/input/a.txt
        +-rw-r--r--   2 hadoop supergroup         50 2014-02-18 22:53 /testPartitioner/input/b.txt
        +
      • +
      • 执行 MapReduce 作业 +此处使用 hadoop jar 命令执行,eclipse 插件方式有一定的缺陷。(hadoop eclipse 执行出现java.io.IOException: Illegal partition for hat (1))

        +
        $ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestPartitioner /testPartitioner/input /testPartitioner/output10
        +
      • +
      • 结果。 四个分区,分别存储上述四种产品的总销量的统计结果值。

        +
        -rw-r--r--   2 hadoop supergroup          9 2014-02-19 00:18 /testPartitioner/output10/part-r-00000
        +-rw-r--r--   2 hadoop supergroup          7 2014-02-19 00:18 /testPartitioner/output10/part-r-00001
        +-rw-r--r--   2 hadoop supergroup         14 2014-02-19 00:18 /testPartitioner/output10/part-r-00002
        +-rw-r--r--   2 hadoop supergroup         12 2014-02-19 00:18 /testPartitioner/output10/part-r-00003
        +
      • +
      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/03/hadoop-mapreduce-recordreader-zu-jian/index.html b/article/2014/03/hadoop-mapreduce-recordreader-zu-jian/index.html new file mode 100644 index 0000000..3463804 --- /dev/null +++ b/article/2014/03/hadoop-mapreduce-recordreader-zu-jian/index.html @@ -0,0 +1,413 @@ + + + + + + + Hadoop MapReduce RecordReader 组件 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop MapReduce RecordReader 组件

      + +

      + + + + + | 评论 +

      +
      + +

      由 RecordReader 决定每次读取以什么样的方式读取数据分片中的一条数据。Hadoop 默认的 RecordReader 是 LineRecordReader(TextInputFormat 的 getRecordReader() 方法返回即是 LineRecordReader。二进制输入 SequenceFileInputFormat 的 getRecordReader() 方法返回即是SequenceFileRecordReader。)。LineRecordReader是用每行的偏移量作为 map 的 key,每行的内容作为 map 的 value;

      +

      它可作用于,自定义读取每一条记录的方式;自定义读入 key 的类型,如希望读取的 key 是文件的路径或名字而不是该行在文件中的偏移量。

      +

      自定义RecordReader一般步骤

      +
        +
      1. 继承抽象类 RecordReader,实现 RecordReader 的实例;
      2. +
      3. 实现自定义 InputFormat 类,重写 InputFormat 中 createRecordReader() 方法,返回值是自定义的 RecordReader 实例; +(3)配置 job.setInputFormatClass() 设置自定义的 InputFormat 类型;
      4. +
      +

      TextInputFormat类源代码理解

      +

      源码见 org.apache.mapreduce.lib.input.TextInputFormat 类(新API);

      +

      Hadoop 默认 TextInputFormat 使用 LineRecordReader。具体分析见注释。

      +
        public RecordReader<LongWritable, Text> 
      +    createRecordReader(InputSplit split,
      +                       TaskAttemptContext context) {
      +    return new LineRecordReader();
      +  }
      +// --> LineRecordReader
      + public void initialize(InputSplit genericSplit,
      +                         TaskAttemptContext context) throws IOException {
      +    FileSplit split = (FileSplit) genericSplit;
      +    Configuration job = context.getConfiguration();
      +    this.maxLineLength = job.getInt("mapred.linerecordreader.maxlength",
      +                                    Integer.MAX_VALUE);
      +    start = split.getStart();  // 当前分片在整个文件中的起始位置
      +    end = start + split.getLength(); // 当前分片,在整个文件的位置
      +    final Path file = split.getPath();
      +    compressionCodecs = new CompressionCodecFactory(job);// 压缩
      +    codec = compressionCodecs.getCodec(file);
      +//
      +    // open the file and seek to the start of the split
      +    FileSystem fs = file.getFileSystem(job);
      +    FSDataInputStream fileIn = fs.open(split.getPath()); // 获取 FSDataInputStream
      +//
      +    if (isCompressedInput()) {
      +      decompressor = CodecPool.getDecompressor(codec);
      +      if (codec instanceof SplittableCompressionCodec) {
      +        final SplitCompressionInputStream cIn =
      +          ((SplittableCompressionCodec)codec).createInputStream(
      +            fileIn, decompressor, start, end,
      +            SplittableCompressionCodec.READ_MODE.BYBLOCK);
      +        in = new LineReader(cIn, job); //一行行读取
      +        start = cIn.getAdjustedStart(); // 可能跨分区读取
      +        end = cIn.getAdjustedEnd();// 可能跨分区读取
      +        filePosition = cIn;
      +      } else {
      +        in = new LineReader(codec.createInputStream(fileIn, decompressor),
      +            job);
      +        filePosition = fileIn;
      +      }
      +    } else {
      +      fileIn.seek(start);//  调整到文件起始偏移量
      +      in = new LineReader(fileIn, job); 
      +      filePosition = fileIn;
      +    }
      +    // If this is not the first split, we always throw away first record
      +    // because we always (except the last split) read one extra line in
      +    // next() method.
      +    if (start != 0) {
      +      start += in.readLine(new Text(), 0, maxBytesToConsume(start));
      +    }
      +    this.pos = start; // 在当前分片的位置
      +  }
      +//  --> getFilePosition() 指针读取到哪个位置
      +// filePosition 为 Seekable 类型
      +  private long getFilePosition() throws IOException {
      +    long retVal;
      +    if (isCompressedInput() && null != filePosition) {
      +      retVal = filePosition.getPos();
      +    } else {
      +      retVal = pos;
      +    }
      +    return retVal;
      +  }
      +//
      +// --> nextKeyValue() 
      +public boolean nextKeyValue() throws IOException {
      +    if (key == null) {
      +      key = new LongWritable();
      +    }
      +    key.set(pos);
      +    if (value == null) {
      +      value = new Text();
      +    }
      +    int newSize = 0;
      +    // We always read one extra line, which lies outside the upper
      +    // split limit i.e. (end - 1)
      +    // 预读取下一条纪录
      +    while (getFilePosition() <= end) {
      +      newSize = in.readLine(value, maxLineLength,
      +          Math.max(maxBytesToConsume(pos), maxLineLength));
      +      if (newSize == 0) {
      +        break;
      +      }
      +      pos += newSize; // 下一行的偏移量
      +      if (newSize < maxLineLength) {
      +        break;
      +      }
      +//
      +      // line too long. try again
      +      LOG.info("Skipped line of size " + newSize + " at pos " + 
      +               (pos - newSize));
      +    }
      +    if (newSize == 0) {
      +      key = null;
      +      value = null;
      +      return false;
      +    } else {
      +      return true;
      +    }
      +  }
      +

      自定义 RecordReader 演示

      +

      假设,现有如下数据 10 ~ 70 需要利用自定义 RecordReader 组件分别计算数据奇数行和偶数行的数据之和。结果为:奇数行之和等于 160,偶数和等于 120。出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件。

      +

      数据:
      +10
      +20
      +30
      +40
      +50
      +60
      +70

      +

      源代码

      +

      TestRecordReader.java

      +

      数据准备

      +
      $ ./bin/hadoop fs -mkdir /inputreader
      +$ ./bin/hadoop fs -put ./a.txt /inputreader
      +$ ./bin/hadoop fs -lsr /inputreader
      +-rw-r--r--   2 hadoop supergroup         21 2014-02-20 21:04 /inputreader/a.txt
      +

      执行

      +
      $ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestRecordReader  /inputreader /inputreaderout1
      +##
      +$ ./bin/hadoop fs -lsr /inputreaderout1
      +-rw-r--r--   2 hadoop supergroup          0 2014-02-20 21:12 /inputreaderout1/_SUCCESS
      +drwxr-xr-x   - hadoop supergroup          0 2014-02-20 21:11 /inputreaderout1/_logs
      +drwxr-xr-x   - hadoop supergroup          0 2014-02-20 21:11 /inputreaderout1/_logs/history
      +-rw-r--r--   2 hadoop supergroup      16451 2014-02-20 21:11 /inputreaderout1/_logs/history/job_201402201934_0002_1392901901142_hadoop_TestRecordReader
      +-rw-r--r--   2 hadoop supergroup      48294 2014-02-20 21:11 /inputreaderout1/_logs/history/job_201402201934_0002_conf.xml
      +-rw-r--r--   2 hadoop supergroup         23 2014-02-20 21:12 /inputreaderout1/part-r-00000
      +-rw-r--r--   2 hadoop supergroup         23 2014-02-20 21:12 /inputreaderout1/part-r-00001
      +##
      +$ ./bin/hadoop fs -cat /inputreaderout1/part-r-00000
      +偶数行之和:  120
      +##
      +$ ./bin/hadoop fs -cat /inputreaderout1/part-r-00001
      +奇数行之和:  160
      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/03/hadoop-mapreduce-sort/index.html b/article/2014/03/hadoop-mapreduce-sort/index.html new file mode 100644 index 0000000..b66e99b --- /dev/null +++ b/article/2014/03/hadoop-mapreduce-sort/index.html @@ -0,0 +1,461 @@ + + + + + + + Hadoop MapReduce Sort - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop MapReduce Sort

      + +

      + + + + + | 评论 +

      +
      + +

      排序是 MapReduce 的核心。排序可分为四种排序:普通排序、部分排序、全局排序、辅助排序

      +

      普通排序

      +

      Mapreduce 本身自带排序功能;Text 对象是不适合排序的;IntWritable,LongWritable 等实现了WritableComparable 类型的对象都是可以排序的。

      +

      部分排序

      +

      map 和 reduce 处理过程中包含了默认对 key 的排序,那么如果不要求全排序,可以直接把结果输出,每个输出文件中包含的就是按照key执行排序的结果。

      +

      控制排序顺序

      +

      键的排序是由 RawComparator 控制的,规则如下:

      +
        +
      1. 若属性 mapred.output.key.comparator.class 已设置,则使用该类的实例。调用 JobConf 的 setOutputKeyComparatorClass() 方法进行设置。
      2. +
      3. 否则,键必须是 WritableComparable 的子类,并使用针对该键类的已登记的 comparator.
      4. +
      5. 如果没有已登记的 comparator ,则使用 RawComparator 将字节流反序列化为一个对象,再由 WritableComparable 的 compareTo() 方法进行操作。
      6. +
      +

      全局排序(对所有数据排序)

      +

      Hadoop 没有提供全局数据排序,而全局排序是非常普遍的需求。

      +

      实现方案

      +
        +
      • 首先,创建一系列的排好序的文件;
      • +
      • 其次,串联这些文件;
      • +
      • 最后,生成一个全局排序的文件。
      • +
      +

      主要思路是使用一个partitioner来描述全局排序的输出。该方法关键在于如何划分各个分区。

      +

      例,对整数排序,[0,10000] 的在 partition 0 中,(10000,20000] 在 partition 1 中… …即第n个reduce 所分配到的数据全部大于第 n-1 个 reduce 中的数据。每个 reduce 的结果都是有序的。
      +然后再将所有的输出文件顺序合并成一个大的文件,那么就实现了全局排序。

      +

      在比较理想的数据分布均匀的情况下,每个分区内的数据量要基本相同。

      +

      但实际中数据往往分布不均匀,出现数据倾斜,这时按照此方法进行的分区划分数据就不适用,可对数据进行采样。

      +

      采样器

      +

      通过对 key 空间进行采样,可以较为均匀的划分数据集。采样的核心思想是只查看一小部分键,获取键的相似分布,并由此构建分区。采样器是在 map 阶段之前进行的, 在提交 job 的 client 端完成的。

      +

      Sampler接口

      +

      Sampler 接口是 Hadoop 的采样器,它的 getSample() 方法返回一组样本。此接口一般不由客户端调用,而是由 InputSampler 类的静态方法 writePartitionFile() 调用,以创建一个顺序文件来存储定义分区的键。

      +

      Sampler接口声明如下:

      +
        public interface Sampler<K,V> {
      +   K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException;
      +  }
      +

      继承 Sample 的类还有 IntervalSampler 间隔采样器,RandomSampler 随机采样器,SplitSampler 分词采样器。它们都是 InputSampler 的静态内部类。

      +

      getSample() 方法根据 job 的配置信息以及输入格式获得抽样结果,三个采样类各自有不同的实现。

      +

      IntervalSampler 根据一定的间隔从 s 个分区中采样数据,非常适合对排好序的数据采样。

      +
      public static class IntervalSampler<K,V> implements Sampler<K,V> {
      +    private final double freq;// 哪一条记录被选中的概率
      +    private final int maxSplitsSampled;// 采样的最大分区数
      +    /**
      +     * For each split sampled, emit when the ratio of the number of records
      +     * retained to the total record count is less than the specified
      +     * frequency.
      +     */
      +    @SuppressWarnings("unchecked") // ArrayList::toArray doesn't preserve type
      +    public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {
      +      InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());// 1. 得到输入分区数组
      +      ArrayList<K> samples = new ArrayList<K>();
      +      int splitsToSample = Math.min(maxSplitsSampled, splits.length);
      +      int splitStep = splits.length / splitsToSample; // 2. 分区采样时的间隔splitStep = 输入分区总数 除以 splitsToSample的 商;
      +      long records = 0;
      +      long kept = 0;
      +      for (int i = 0; i < splitsToSample; ++i) {
      +        RecordReader<K,V> reader = inf.getRecordReader(splits[i * splitStep], // 3. 采样下标为i * splitStep的数据
      +            job, Reporter.NULL);
      +        K key = reader.createKey();
      +        V value = reader.createValue();
      +        while (reader.next(key, value)) {// 6. 循环读取下一条记录
      +          ++records;
      +          if ((double) kept / records < freq) { // 4. 如果当前样本数与已经读取的记录数的比值小于freq,则将这条记录添加到样本集合
      +            ++kept;
      +            samples.add(key);// 5. 将记录添加到样本集合中
      +            key = reader.createKey();
      +          }
      +        }
      +        reader.close();
      +      }
      +      return (K[])samples.toArray();
      +    }
      +  }
      +… … 
      +}
      +

      RandomSampler 是常用的采样器,它随机地从输入数据中抽取 Key

      +
        public static class RandomSampler<K,V> implements Sampler<K,V> {
      +    private double freq;// 一个Key被选中的 概率
      +    private final int numSamples;// 从所有被选中的分区中获得的总共的样本数目
      +    private final int maxSplitsSampled;// 需要检查扫描的最大分区数目
      +/**
      +     * Randomize the split order, then take the specified number of keys from
      +     * each split sampled, where each key is selected with the specified
      +     * probability and possibly replaced by a subsequently selected key when
      +     * the quota of keys from that split is satisfied.
      +     */
      +    @SuppressWarnings("unchecked") // ArrayList::toArray doesn't preserve type
      +    public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {
      +      InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());// 1. 获取所有的输入分区
      +      ArrayList<K> samples = new ArrayList<K>(numSamples);// 2. 确定需要抽样扫描的分区数目
      +      int splitsToSample = Math.min(maxSplitsSampled, splits.length);// 3. 取最小的为采样的分区数
      +      Random r = new Random();
      +      long seed = r.nextLong();
      +      r.setSeed(seed);
      +      LOG.debug("seed: " + seed);
      +      // shuffle splits 4. 对输入分区数组shuffle排序
      +      for (int i = 0; i < splits.length; ++i) {
      +        InputSplit tmp = splits[i];
      +        int j = r.nextInt(splits.length);// 5. 打乱其原始顺序
      +        splits[i] = splits[j];
      +        splits[j] = tmp;
      +      }
      +      // our target rate is in terms of the maximum number of sample splits,
      +      // but we accept the possibility of sampling additional splits to hit
      +      // the target sample keyset
      +// 5. 然后循环逐 个扫描每个分区中的记录进行采样,
      +      for (int i = 0; i < splitsToSample ||
      +                     (i < splits.length && samples.size() < numSamples); ++i) {
      +        RecordReader<K,V> reader = inf.getRecordReader(splits[i], job,
      +            Reporter.NULL);
      +       // 6. 取出一条记录
      +        K key = reader.createKey();
      +        V value = reader.createValue();
      +        while (reader.next(key, value)) {
      +          if (r.nextDouble() <= freq) {
      +            if (samples.size() < numSamples) {// 7. 判断当前的采样数是否小于最大采样数
      +              samples.add(key); //8. 小于则这条记录被选中,放进采样集合中,
      +            } else {
      +              // When exceeding the maximum number of samples, replace a
      +              // random element with this one, then adjust the frequency
      +              // to reflect the possibility of existing elements being
      +              // pushed out
      +              int ind = r.nextInt(numSamples);// 9. 从[0,numSamples]中选择一个随机数
      +              if (ind != numSamples) {
      +                samples.set(ind, key);// 10. 替换掉采样集合随机数对应位置的记录,
      +              }
      +              freq *= (numSamples - 1) / (double) numSamples;// 11. 调小频率
      +            }
      +            key = reader.createKey();// 12. 下一条纪录的key
      +          }
      +        }
      +        reader.close();
      +      }
      +      return (K[])samples.toArray();// 13. 返回
      +    }
      +  }
      +… … 
      +}
      +

      SplitSampler 从 s 个分区中采样前 n 个记录,是采样随机数据的一种简便方式。

      +
        public static class SplitSampler<K,V> implements Sampler<K,V> {
      +    private final int numSamples;// 最大采样数
      +    private final int maxSplitsSampled;// 最大分区数
      +    … … 
      +    /**
      +     * From each split sampled, take the first numSamples / numSplits records.
      +     */
      +    @SuppressWarnings("unchecked") // ArrayList::toArray doesn't preserve type
      +    public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {
      +      InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());
      +      ArrayList<K> samples = new ArrayList<K>(numSamples);
      +      int splitsToSample = Math.min(maxSplitsSampled, splits.length);// 1. 采样的分区数
      +      int splitStep = splits.length / splitsToSample; // 2. 分区采样时的间隔 = 分片的长度 与 输入分片的总数的 商
      +      int samplesPerSplit = numSamples / splitsToSample; // 3. 每个分区的采样数 
      +      long records = 0;
      +      for (int i = 0; i < splitsToSample; ++i) {
      +        RecordReader<K,V> reader = inf.getRecordReader(splits[i * splitStep], // 4.采样下标为i * splitStep的数据
      +            job, Reporter.NULL);
      +        K key = reader.createKey();
      +        V value = reader.createValue();
      +        while (reader.next(key, value)) {
      +          samples.add(key);// 5. 将记录添加到样本集合中
      +          key = reader.createKey();
      +          ++records;
      +          if ((i+1) * samplesPerSplit <= records) { // 6. 当前样本数大于当前的采样分区所需要的样本数,则停止对当前分区的采样。
      +            break;
      +          }
      +        }
      +        reader.close();
      +      }
      +      return (K[])samples.toArray();
      +    }
      +  }
      +

      Hadoop为顺序文件提供了一个 TotalOrderPartitioner 类,可以用来实现全局排序;TotalOrderPartitioner 源代码理解。TotalOrderPartitioner 内部定义了多个字典树(内部类)。

      +
      interface Node<T> 
      +// 特里树,利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高
      +static abstract class TrieNode implements Node<BinaryComparable> 
      +static class InnerTrieNode extends TrieNode 
      +static class LeafTrieNode extends TrieNode
      +… … 
      +

      由 TotalOrderPartitioner 调用 getPartition() 方法返回分区,由 buildTrieRec() 构建特里树.

      +
       private TrieNode buildTrieRec(BinaryComparable[] splits, int lower,
      +      int upper, byte[] prefix, int maxDepth, CarriedTrieNodeRef ref) {
      +… … 
      +}
      +

      采样器使用示例

      +
        +
      1. 新建文件,名为 random.txt,里面每行存放一个数据。可由 RandomGenerator 类生成准备数据
      2. +
      3. 执行 TestTotalOrderPartitioner.java
      4. +
      +

      辅助排序

      +

      先按 key 排序,在按 相同的 key 不同的 value 再排序。可实现对值分组的效果。

      + +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/03/hadoop-pipes--streaming/index.html b/article/2014/03/hadoop-pipes--streaming/index.html new file mode 100644 index 0000000..6610f4b --- /dev/null +++ b/article/2014/03/hadoop-pipes--streaming/index.html @@ -0,0 +1,343 @@ + + + + + + + Hadoop Pipes & Streaming - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop Pipes & Streaming

      + +

      + + + + + | 评论 +

      +
      + +

      申明:本文大部分出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件 和 Apache 官方文档。

      +

      Streaming

      +
        +
      • Streaming 是 hadoop 里面提供的一个工具
      • +
      • Streaming 框架允许任何程序语言实现的程序在 Hadoop MapReduce 中使用,方便任何程序向 Hadoop 平台移植,具有很强的扩展性;
      • +
      • mapper 和 reducer 会从标准输入中读取用户数据,一行一行处理后发送给标准输出。Streaming 工具会创建 MapReduce 作业,发送给各个 tasktracker,同时监控整个作业的执行过程;
      • +
      • 如果一个文件(可执行或者脚本)作为 mapper,mapper 初始化时,每一个 mapper 任务会把该文件作为一个单独进程启动,mapper 任务运行时,它把输入切法成行并把每一行提供给可执行文件进程的标准输入。同 时,mapper 收集可执行文件进程标准输出的内容,并把收到的每一行内容转化成 key/value,作为 mapper的输出。默认情况下,一行中第一个 tab 之前的部分作为 key,之后的(不包括)作为value。如果没有 tab,整行作为 key 值,value值为null。对于reducer,类似;
      • +
      +

      Streaming 优点

      +
        +
      1. 开发效率高,便于移植。Hadoop Streaming 使用 Unix 标准流作为 Hadoop 和应用程序之间的接口。在单机上可按照 cat input | mapper | sort | reducer > output 进行测试,若单机上测试通过,集群上一般控制好内存也可以很好的执行成功。

        +
      2. +
      3. 提高运行效率。对内存要求较高,可用C/C++控制内存。比纯java实现更好。

        +
      4. +
      +

      Streaming缺点

      +
        +
      1. Hadoop Streaming 默认只能处理文本数据,(0.21.0之后可以处理二进制数据)。

        +
      2. +
      3. Steaming 中的 mapper 和 reducer 默认只能想标准输出写数据,不能方便的多路输出。

        +
      4. +
      +

      更详细内容请参考于: http://hadoop.apache.org/docs/r1.2.1/streaming.html

      +
      $HADOOP_HOME/bin/hadoop  jar $HADOOP_HOME/hadoop-streaming.jar \
      +    -input myInputDirs \
      +    -output myOutputDir \
      +    -mapper /bin/cat \
      +    -reducer /bin/wc
      +

      streaming示例

      +

      perl 语言的streaming示例 代码

      +
      -rw-rw-r--. 1 hadoop hadoop     48 2月  22 10:47 data
      +-rw-rw-r--. 1 hadoop hadoop 107399 2月  22 10:41 hadoop-streaming-1.2.1.jar
      +-rw-rw-r--. 1 hadoop hadoop    186 2月  22 10:45 mapper.pl
      +-rw-rw-r--. 1 hadoop hadoop    297 2月  22 10:55 reducer.pl
      +##
      +$ ../bin/hadoop jar hadoop-streaming-1.2.1.jar -mapper mapper.pl -reducer reducer.pl -input /test/streaming -output /test/streamingout1 -file mapper.pl -file reducer.pl 
      +

      Hadoop pipes

      +
        +
      1. Hadoop pipes 是 Hadoop MapReduce 的 C++ 的接口代称。不同于使用标准输入和输出来实现 map 代码和 reduce 代码之间的 Streaming。
      2. +
      3. Pipes 使用套接字 socket 作为 tasktracker 与 C++ 版本函数的进程间的通讯,未使用 JNI。
      4. +
      5. 与 Streaming 不同,Pipes 是 Socket 通讯,Streaming 是标准输入输出。
      6. +
      +

      编译 Hadoop Pipes

      +

      编译c++ pipes( 确保操作系统提前安装好了 openssl,zlib,glib,openssl-devel) +Hadoop更目录下执行 +ant -Dcompile.c++=yes examples

      +

      具体请参见《Hadoop Pipes 编译》

      +

      Hadoop官方示例:

      +
      hadoop/src/examples/pipes/impl
      + config.h.in
      + sort.cc
      +wordcount-nopipe.cc
      +wordcount-part.cc
      +wordcount-simple.cc
      +

      运行前需要把可执行文件和输入数据上传到 hdfs:

      +
      $ ./bin/hadoop fs -mkdir /test/pipes/input
      +$ ./bin/hadoop fs -put a.txt /test/pipes/input 
      +$ ./bin/hadoop fs -cat /test/pipes/input/a.txt 
      +hello hadoop hello hive hello hbase hello zk
      +

      上传执行文件,重新命名为/test/pipes/exec

      +
      $ ./bin/hadoop fs -put ./build/c++-examples/Linux-amd64-64/bin/wordcount-simple /test/pipes/exec
      +

      在编译好的文件夹目录下执行

      +
      $ cd hadoop/build/c++-examples/Linux-amd64-64/bin
      +$ ../../../../bin/hadoop pipes -Dhadoop.pipes.java.recordreader=true -Dhadoop.pipes.java.recordwriter=true -reduces 4 -input /test/pipes/input -output /test/pipes/input/output1 -program /test/pipes/execs
      +

      执行结果如下:

      +
      $ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00000 hbase 1 
      +$ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00001 hello 4 hive 1 
      +$ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00002 hadoop 1 zk 1 
      +$ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00003
      +

      参考博客:

      + +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/article/2014/03/hadoop-rpc/index.html b/article/2014/03/hadoop-rpc/index.html new file mode 100644 index 0000000..89fc20f --- /dev/null +++ b/article/2014/03/hadoop-rpc/index.html @@ -0,0 +1,510 @@ + + + + + + + Hadoop RPC - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      kangfoo's blog

      +

      工作学习笔记,生活掠影。

      +
      +
      + +
      +
      +
      +
      +
      +

      Hadoop RPC

      + +

      + + + + + | 评论 +

      +
      + +

      Remote Procedure Call 远程方法调用。不需要了解网络细节,某一程序即可使用该协议请求来自网络内另一台及其程序的服务。它是一个 Client/Server 的结构,提供服务的一方称为Server,消费服务的一方称为Client。

      +

      Hadoop 底层的交互都是通过 rpc 进行的。例 如:datanode 和 namenode、tasktracker 和 jobtracker、secondary namenode 和 namenode 之间的通信都是通过 rpc 实现的。

      +

      TODO: 此文未写明了。明显需要画 4张图, rpc 原理图,Hadoop rpc 时序图, 客户端 流程图,服端流程图。最好帖几个包图+ 类图(组件图)。待完善。

      +

      要实现远程过程调用,需要有3要素: +1、server 必须发布服务 +2、在 client 和 server 两端都需要有模块来处理协议和连接 +3、server 发布的服务,需要将接口给到 client

      +

      Hadoop RPC

      +
        +
      1. 序列化层。 Client 与 Server 端通讯传递的信息采用实现自 Writable 类型
      2. +
      3. 函数调用层。 Hadoop RPC 通过动态代理和 java 反射实现函数调用
      4. +
      5. 网络传输层。Hadoop RPC 采用 TCP/IP socket 机制
      6. +
      7. 服务器框架层。Hadoop RPC 采用 java NIO 事件驱动模型提高 RPC Server 吞吐量
      8. +
      +

      TODO 缺个 RPC 图

      +

      Hadoop RPC 源代码主要在org.apache.hadoop.ipc包下。org.apache.hadoop.ipc.RPC 内部包含5个内部类。

      +
        +
      • Invocation :用于封装方法名和参数,作为数据传输层,相当于VO(Value Object)。
      • +
      • ClientCache :用于存储client对象,用 socket factory 作为 hash key,存储结构为 hashMap
      • +
      • Invoker :是动态代理中的调用实现类,继承了 java.lang.reflect.InvocationHandler。
      • +
      • Server :是ipc.Server的实现类。
      • +
      • VersionMismatch : 协议版本。
      • +
      +

      从客户端开始进行通讯源代码分析

      +

      org.apache.hadoop.ipc.Client 有5个内部类

      +
        +
      • Call: A call waiting for a value.
      • +
      • Connection: Thread that reads responses and notifies callers. Each connection owns a socket connected to a remote address. Calls are multiplexed through this socket: responses may be delivered out of order.
      • +
      • ConnectionId: This class holds the address and the user ticket. The client connections to servers are uniquely identified by
      • +
      • ParallelCall: Call implementation used for parallel calls.
      • +
      • ParallelResults: Result collector for parallel calls.
      • +
      +

      客户端和服务端建立连接的大致执行过程为

      +
        +
      1. 在 Object org.apache.hadoop.ipc.RPC.Invoker.invoke(Object proxy, Method method, Object[] args) 方法中调用
        +client.call(new Invocation(method, args), remoteId);

        +
      2. +
      3. 上述的 new Invocation(method, args) 是 org.apache.hadoop.ipc.RPC 的内部类,它包含被调用的方法名称及其参数。此处主要是设置方法和参数。 client 为 org.apache.hadoop.ipc.Client 的实例对象。

        +
      4. +
      5. org.apache.hadoop.ipc.Client.call() 方法的具体源代码。在call()方法中 getConnection()内部获取一个 org.apache.hadoop.ipc.Client.Connection 对象并启动 io 流 setupIOstreams()。

        +
        Writable org.apache.hadoop.ipc.Client.call(Writable param, ConnectionId remoteId) throwsInterruptedException, IOException {
        +Call call = new Call(param); //A call waiting for a value.   
        +// Get a connection from the pool, or create a new one and add it to the
        +// pool.  Connections to a given ConnectionId are reused. 
        +Connection connection = getConnection(remoteId, call);// 主要在 org.apache.hadoop.net 包下。
        +connection.sendParam(call); //客户端发送数据过程
        +boolean interrupted = false;
        +synchronized (call) {
        +   while (!call.done) {
        +    try {
        +      call.wait();                           // wait for the result
        +    } catch (InterruptedException ie) {
        +      // save the fact that we were interrupted
        +      interrupted = true;
        +    }
        +  }
        +… …
        +}
        +}
        +// Get a connection from the pool, or create a new one and add it to the
        +// pool.  Connections to a given ConnectionId are reused. 
        +private Connection getConnection(ConnectionId remoteId,
        +                               Call call)
        +                               throws IOException, InterruptedException {
        +if (!running.get()) {
        +  // the client is stopped
        +  throw new IOException("The client is stopped");
        +}
        +Connection connection;
        +// we could avoid this allocation for each RPC by having a  
        +// connectionsId object and with set() method. We need to manage the
        +// refs for keys in HashMap properly. For now its ok.
        +do {
        +  synchronized (connections) {
        +    connection = connections.get(remoteId);
        +    if (connection == null) {
        +      connection = new Connection(remoteId);
        +      connections.put(remoteId, connection);
        +    }
        +  }
        +} while (!connection.addCall(call)); 
        +//we don't invoke the method below inside "synchronized (connections)"
        +//block above. The reason for that is if the server happens to be slow,
        +//it will take longer to establish a connection and that will slow the
        +//entire system down.
        +connection.setupIOstreams(); // 向服务段发送一个 header 并等待结果
        +return connection;
        +}
        +
      6. +
      7. setupIOstreams() 方法。

        +
        void org.apache.hadoop.ipc.Client.Connection.setupIOstreams() throws InterruptedException {
        +// Connect to the server and set up the I/O streams. It then sends
        +// a header to the server and starts
        +// the connection thread that waits for responses.
        +while (true) {
        +      setupConnection();//  建立连接
        +      InputStream inStream = NetUtils.getInputStream(socket); // 输入
        +      OutputStream outStream = NetUtils.getOutputStream(socket); // 输出
        +      writeRpcHeader(outStream);
        +      }
        +… … 
        +// update last activity time
        +  touch();
        +// start the receiver thread after the socket connection has been set up            start(); 
        +}        
        +
      8. +
      9. 启动org.apache.hadoop.ipc.Client.Connection +客户端获取服务器端放回数据过程

        +
        void org.apache.hadoop.ipc.Client.Connection.run()
        +while (waitForWork()) {//wait here for work - read or close connection
        +    receiveResponse();
        +  }
        +
      10. +
      +

      ipc.Server源码分析

      +

      ipc.Server 有6个内部类:

      +
        +
      • Call :用于存储客户端发来的请求
      • +
      • Listener : 监听类,用于监听客户端发来的请求,同时Listener内部还有一个静态类,Listener.Reader,当监听器监听到用户请求,便让Reader读取用户请求。
      • +
      • ExceptionsHandler: 异常管理
      • +
      • Responder :响应RPC请求类,请求处理完毕,由Responder发送给请求客户端。
      • +
      • Connection :连接类,真正的客户端请求读取逻辑在这个类中。
      • +
      • Handler :请求处理类,会循环阻塞读取callQueue中的call对象,并对其进行操作。
      • +
      +

      大致过程为:

      +
        +
      1. Namenode的初始化时,RPC的server对象是通过ipc.RPC类的getServer()方法获得的。

        +
        void org.apache.hadoop.hdfs.server.namenode.NameNode.initialize(Configuration conf) throwsIOException
        +// create rpc server
        +InetSocketAddress dnSocketAddr = getServiceRpcServerAddress(conf);
        +if (dnSocketAddr != null) {
        +  int serviceHandlerCount =
        +    conf.getInt(DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_KEY,
        +                DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_DEFAULT);
        +  this.serviceRpcServer = RPC.getServer(this, dnSocketAddr.getHostName(), 
        +      dnSocketAddr.getPort(), serviceHandlerCount,
        +      false, conf, namesystem.getDelegationTokenSecretManager());
        +  this.serviceRPCAddress = this.serviceRpcServer.getListenerAddress();
        +  setRpcServiceServerAddress(conf);
        +}
        +… …
        +this.server.start();  //start RPC server  
        +
      2. +
      3. 启动 server

        +
        void org.apache.hadoop.ipc.Server.start()
        +// Starts the service.  Must be called before any calls will be handled.
        +public synchronized void start() {
        +responder.start();
        +listener.start();
        +handlers = new Handler[handlerCount];
        +for (int i = 0; i < handlerCount; i++) {
        +  handlers[i] = new Handler(i);
        +  handlers[i].start(); //处理call
        +}
        +}
        +
      4. +
      5. Server处理请求, server 同样使用非阻塞 nio 以提高吞吐量

        +
        org.apache.hadoop.ipc.Server.Listener.Listener(Server) throws IOException
        +public Listener() throws IOException {
        +  address = new InetSocketAddress(bindAddress, port);
        +  // Create a new server socket and set to non blocking mode
        +  acceptChannel = ServerSocketChannel.open();
        +  acceptChannel.configureBlocking(false);
        +… … }     
        +
      6. +
      7. 真正建立连接

        +
        void org.apache.hadoop.ipc.Server.Listener.doAccept(SelectionKey key) throws IOException,OutOfMemoryError
        +

        Reader 读数据接收请求

        +
        void org.apache.hadoop.ipc.Server.Listener.doRead(SelectionKey key) throws InterruptedException
        +try {
        +    count = c.readAndProcess();
        +  } catch (InterruptedException ieo) {
        +    LOG.info(getName() + ": readAndProcess caught InterruptedException", ieo);
        +    throw ieo;
        +  }
        +
        int org.apache.hadoop.ipc.Server.Connection.readAndProcess() throws IOException,InterruptedException
        +if (!rpcHeaderRead) {
        +      //Every connection is expected to send the header.
        +      if (rpcHeaderBuffer == null) {
        +        rpcHeaderBuffer = ByteBuffer.allocate(2);
        +      }
        +      count = channelRead(channel, rpcHeaderBuffer);
        +      if (count < 0 || rpcHeaderBuffer.remaining() > 0) {
        +        return count;
        +      }
        +      int version = rpcHeaderBuffer.get(0);
        +… … 
        +processOneRpc(data.array()); // 数据处理
        +
      8. +
      9. 下面贴出Server.Connection类中的processOneRpc()方法和processData()方法的源码。

        +
        void org.apache.hadoop.ipc.Server.Connection.processOneRpc(byte[] buf) throws IOException,InterruptedException
        +private void processOneRpc(byte[] buf) throws IOException,
        +    InterruptedException {
        +  if (headerRead) {
        +    processData(buf);
        +  } else {
        +    processHeader(buf);
        +    headerRead = true;
        +    if (!authorizeConnection()) {
        +      throw new AccessControlException("Connection from " + this
        +          + " for protocol " + header.getProtocol()
        +          + " is unauthorized for user " + user);
        +    }
        +  }
        +}
        +
      10. +
      11. 处理call

        +
        void org.apache.hadoop.ipc.Server.Handler.run()
        +while (running) {
        +    try {
        +      final Call call = callQueue.take(); // pop the queue; maybe blocked here
        +      … … 
        +      CurCall.set(call);
        +      try {
        +        // Make the call as the user via Subject.doAs, thus associating
        +        // the call with the Subject
        +        if (call.connection.user == null) {
        +          value = call(call.connection.protocol, call.param, 
        +                       call.timestamp);
        +        } else {
        +… …}
        +
      12. +
      13. 返回请求

        +
      14. +
      +

      下面贴出Server.Responder类中的doRespond()方法源码:

      +
      void org.apache.hadoop.ipc.Server.Responder.doRespond(Call call) throws IOException
      +    //
      +    // Enqueue a response from the application.
      +    //
      +    void doRespond(Call call) throws IOException {
      +      synchronized (call.connection.responseQueue) {
      +        call.connection.responseQueue.addLast(call);
      +        if (call.connection.responseQueue.size() == 1) {
      +          processResponse(call.connection.responseQueue, true);
      +        }
      +      }
      +    }
      +

      补充: +notify()让因wait()进入阻塞队列里的线程(blocked状态)变为runnable,然后发出notify()动作的线程继续执行完,待其完成后,进行调度时,调用wait()的线程可能会被再次调度而进入running状态。

      +

      参考资源:

      +
      + +
      +
      +

      评论

      +
      +
      +
      + +
      +
      +

      + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

      +
      + + + + + + + + + + diff --git a/atom.xml b/atom.xml index 3ff0081..8dc6c02 100644 --- a/atom.xml +++ b/atom.xml @@ -1,503 +1,2263 @@ - <![CDATA[kangfoo's 博客]]> + <![CDATA[kangfoo's blog]]> - 2014-01-19T19:27:56+08:00 + 2014-03-05T01:02:11+08:00 http://kangfoo.u.qiniudn.com// - + OpooPress - <![CDATA[hadoop1.x wordcount分析]]> - - 2014-01-19T17:00:00+08:00 - http://kangfoo.u.qiniudn.com//article/2014/01/hadoop1.x-wordcount-fen-xi/ - hadoop mapreduce 过程粗略的分为:map, redurce(copy, sort, reduce)两个阶段。具体的工作机制还是挺复杂的,这里主要通过hadoop example jar中提供的wordcount来对hadoop mapredurce做个简单的理解。Wordcount程序输入文件类型,计算单词的频率。输出是文本文件:每行是单词和它出现的频率,用Tab键隔开。

      -
        -
      1. 首先确保Hadoop集群正常运行,并了解mapredurce工作时涉及到的基本的文件备配。vi mapred-site.xml

        - -
        <configuration>
        -<property>
        -<name>mapred.job.tracker</name> <!--JobTracker的主机(或者IP)和端口。 -->
        -<value>master11:9001</value>
        -</property>
        -<property>
        -<name>mapred.system.dir</name> <!--Map/Reduce框架存储系统文件的HDFS路径。-->
        -<value>/home/${user.name}/env/mapreduce/system</value>
        -</property>
        -<property>
        -<name>mapred.local.dir</name> <!--Map/Reduce在本地文件系统下中间结果存放路径. -->
        -<value>/home/${user.name}/env/mapreduce/local</value>
        -</property>
        -</configuration>
        -
      2. -
      3. 上传一个文件到hdfs文件系统

        - -
        $ ./bin/hadoop fs -mkdir /test/input
        -$ ./bin/hadoop fs -put ./testDir/part0 /test/input
        -$ ./bin/hadoop fs -lsr /
        -## part0 文件中的内容为:
        -hadoop zookeeper hbase hive
        -rest osgi http ftp
        -hadoop zookeeper
        -
      4. -
      5. 执行workcount -$ ./bin/hadoop jar hadoop-examples-1.2.1.jar wordcount /test/input /test/output
        日志如下

        - -
        14/01/19 18:23:25 INFO input.FileInputFormat: Total input paths to process : 1
        -## 使用 native-hadoop library
        -14/01/19 18:23:25 INFO util.NativeCodeLoader: Loaded the native-hadoop library
        -14/01/19 18:23:25 WARN snappy.LoadSnappy: Snappy native library not loaded
        -14/01/19 18:23:25 INFO mapred.JobClient: Running job: job_201401181723_0005
        -14/01/19 18:23:26 INFO mapred.JobClient:  map 0% reduce 0%
        -14/01/19 18:23:32 INFO mapred.JobClient:  map 100% reduce 0%
        -14/01/19 18:23:40 INFO mapred.JobClient:  map 100% reduce 33%
        -14/01/19 18:23:42 INFO mapred.JobClient:  map 100% reduce 100%
        -## jobid job_201401181723_0005 (job_yyyyMMddHHmm_(顺序自然数,不足4位补0,已保证磁盘文件目录顺序))
        -14/01/19 18:23:43 INFO mapred.JobClient: Job complete: job_201401181723_0005
        -## Counters 计数器
        -14/01/19 18:23:43 INFO mapred.JobClient: Counters: 29
        -## Job Counters
        -14/01/19 18:23:43 INFO mapred.JobClient:   Job Counters
        -14/01/19 18:23:43 INFO mapred.JobClient:     Launched reduce tasks=1
        -14/01/19 18:23:43 INFO mapred.JobClient:     SLOTS_MILLIS_MAPS=6925
        -14/01/19 18:23:43 INFO mapred.JobClient:     Total time spent by all reduces waiting after reserving slots (ms)=0
        -14/01/19 18:23:43 INFO mapred.JobClient:     Total time spent by all maps waiting after reserving slots (ms)=0
        -14/01/19 18:23:43 INFO mapred.JobClient:     Launched map tasks=1
        -14/01/19 18:23:43 INFO mapred.JobClient:     Data-local map tasks=1
        -14/01/19 18:23:43 INFO mapred.JobClient:     SLOTS_MILLIS_REDUCES=9688
        -## File Output Format Counters
        -14/01/19 18:23:43 INFO mapred.JobClient:   File Output Format Counters
        -14/01/19 18:23:43 INFO mapred.JobClient:     Bytes Written=63
        -## FileSystemCounters
        -14/01/19 18:23:43 INFO mapred.JobClient:   FileSystemCounters
        -14/01/19 18:23:43 INFO mapred.JobClient:     FILE_BYTES_READ=101
        -14/01/19 18:23:43 INFO mapred.JobClient:     HDFS_BYTES_READ=167
        -14/01/19 18:23:43 INFO mapred.JobClient:     FILE_BYTES_WRITTEN=112312
        -14/01/19 18:23:43 INFO mapred.JobClient:     HDFS_BYTES_WRITTEN=63
        -## File Input Format Counters
        -14/01/19 18:23:43 INFO mapred.JobClient:   File Input Format Counters
        -14/01/19 18:23:43 INFO mapred.JobClient:     Bytes Read=65
        -## Map-Reduce Framework
        -14/01/19 18:23:43 INFO mapred.JobClient:   Map-Reduce Framework
        -14/01/19 18:23:43 INFO mapred.JobClient:     Map output materialized bytes=101
        -14/01/19 18:23:43 INFO mapred.JobClient:     Map input records=3
        -14/01/19 18:23:43 INFO mapred.JobClient:     Reduce shuffle bytes=101
        -14/01/19 18:23:43 INFO mapred.JobClient:     Spilled Records=16
        -14/01/19 18:23:43 INFO mapred.JobClient:     Map output bytes=104
        -14/01/19 18:23:43 INFO mapred.JobClient:     Total committed heap usage (bytes)=176230400
        -14/01/19 18:23:43 INFO mapred.JobClient:     CPU time spent (ms)=840
        -14/01/19 18:23:43 INFO mapred.JobClient:     Combine input records=10
        -14/01/19 18:23:43 INFO mapred.JobClient:     SPLIT_RAW_BYTES=102
        -14/01/19 18:23:43 INFO mapred.JobClient:     Reduce input records=8
        -14/01/19 18:23:43 INFO mapred.JobClient:     Reduce input groups=8
        -14/01/19 18:23:43 INFO mapred.JobClient:     Combine output records=8
        -14/01/19 18:23:43 INFO mapred.JobClient:     Physical memory (bytes) snapshot=251568128
        -14/01/19 18:23:43 INFO mapred.JobClient:     Reduce output records=8
        -14/01/19 18:23:43 INFO mapred.JobClient:     Virtual memory (bytes) snapshot=1453596672
        -14/01/19 18:23:43 INFO mapred.JobClient:     Map output records=10
        -
      6. -
      7. 运行之后的文件系统结构 -
        日志如下

        - -
        drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test
        -drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/input
        --rw-r--r--   2 hadoop supergroup         65 2014-01-19 18:23 /test/input/part0
        -drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/output
        --rw-r--r--   2 hadoop supergroup          0 2014-01-19 18:23 /test/output/_SUCCESS
        -drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/output/_logs
        -drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/output/_logs/history
        -## job 执行结果的数据文件
        --rw-r--r--   2 hadoop supergroup      13647 2014-01-19 18:23 /test/output/_logs/history/job_201401181723_0005_1390127005579_hadoop_word+count
        -## job 配置文件
        --rw-r--r--   2 hadoop supergroup      48374 2014-01-19 18:23 /test/output/_logs/history/job_201401181723_0005_conf.xml
        -## 只分了1个
        --rw-r--r--   2 hadoop supergroup         63 2014-01-19 18:23 /test/output/part-r-00000
        -drwxr-xr-x   - hadoop supergroup          0 2013-12-22 14:02 /user
        -drwxr-xr-x   - hadoop supergroup          0 2014-01-18 23:16 /user/hadoop
        -
      8. -
      9. 查看结果。$ ./bin/hadoop fs -cat /test/output/part-r-00000

        - -
        ftp     1
        -hadoop  2
        -hbase   1
        -hive    1
        -http    1
        -osgi    1
        -rest    1
        -zookeeper       2
        -
      10. -
      11. 更详细的信息可web访问 http://master11:50030/jobtracker.jsp 进行查看.

        + <![CDATA[Hadoop MapReduce 学习参考博客汇总]]> + + 2014-03-05T01:01:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop--xue-xi-can-kao-bo-ke-hui-zong/ + TODO.

        +]]>
        + + + <![CDATA[Hadoop Pipes & Streaming]]> + + 2014-03-03T22:26:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-pipes--streaming/ + 申明:本文大部分出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件 和 Apache 官方文档。

        +

        Streaming

        +
          +
        • Streaming 是 hadoop 里面提供的一个工具
        • +
        • Streaming 框架允许任何程序语言实现的程序在 Hadoop MapReduce 中使用,方便任何程序向 Hadoop 平台移植,具有很强的扩展性;
        • +
        • mapper 和 reducer 会从标准输入中读取用户数据,一行一行处理后发送给标准输出。Streaming 工具会创建 MapReduce 作业,发送给各个 tasktracker,同时监控整个作业的执行过程;
        • +
        • 如果一个文件(可执行或者脚本)作为 mapper,mapper 初始化时,每一个 mapper 任务会把该文件作为一个单独进程启动,mapper 任务运行时,它把输入切法成行并把每一行提供给可执行文件进程的标准输入。同 时,mapper 收集可执行文件进程标准输出的内容,并把收到的每一行内容转化成 key/value,作为 mapper的输出。默认情况下,一行中第一个 tab 之前的部分作为 key,之后的(不包括)作为value。如果没有 tab,整行作为 key 值,value值为null。对于reducer,类似;
        • +
        +

        Streaming 优点

        +
          +
        1. 开发效率高,便于移植。Hadoop Streaming 使用 Unix 标准流作为 Hadoop 和应用程序之间的接口。在单机上可按照 cat input | mapper | sort | reducer > output 进行测试,若单机上测试通过,集群上一般控制好内存也可以很好的执行成功。

          +
        2. +
        3. 提高运行效率。对内存要求较高,可用C/C++控制内存。比纯java实现更好。

        +

        Streaming缺点

        +
          +
        1. Hadoop Streaming 默认只能处理文本数据,(0.21.0之后可以处理二进制数据)。

          +
        2. +
        3. Steaming 中的 mapper 和 reducer 默认只能想标准输出写数据,不能方便的多路输出。

          +
        4. +
        +

        更详细内容请参考于: http://hadoop.apache.org/docs/r1.2.1/streaming.html

        +
        $HADOOP_HOME/bin/hadoop  jar $HADOOP_HOME/hadoop-streaming.jar \
        +    -input myInputDirs \
        +    -output myOutputDir \
        +    -mapper /bin/cat \
        +    -reducer /bin/wc
        +

        streaming示例

        +

        perl 语言的streaming示例 代码

        +
        -rw-rw-r--. 1 hadoop hadoop     48 2月  22 10:47 data
        +-rw-rw-r--. 1 hadoop hadoop 107399 2月  22 10:41 hadoop-streaming-1.2.1.jar
        +-rw-rw-r--. 1 hadoop hadoop    186 2月  22 10:45 mapper.pl
        +-rw-rw-r--. 1 hadoop hadoop    297 2月  22 10:55 reducer.pl
        +##
        +$ ../bin/hadoop jar hadoop-streaming-1.2.1.jar -mapper mapper.pl -reducer reducer.pl -input /test/streaming -output /test/streamingout1 -file mapper.pl -file reducer.pl 
        +

        Hadoop pipes

        +
          +
        1. Hadoop pipes 是 Hadoop MapReduce 的 C++ 的接口代称。不同于使用标准输入和输出来实现 map 代码和 reduce 代码之间的 Streaming。
        2. +
        3. Pipes 使用套接字 socket 作为 tasktracker 与 C++ 版本函数的进程间的通讯,未使用 JNI。
        4. +
        5. 与 Streaming 不同,Pipes 是 Socket 通讯,Streaming 是标准输入输出。
        6. +
        +

        编译 Hadoop Pipes

        +

        编译c++ pipes( 确保操作系统提前安装好了 openssl,zlib,glib,openssl-devel) +Hadoop更目录下执行 +ant -Dcompile.c++=yes examples

        +

        具体请参见《Hadoop Pipes 编译》

        +

        Hadoop官方示例:

        +
        hadoop/src/examples/pipes/impl
        + config.h.in
        + sort.cc
        +wordcount-nopipe.cc
        +wordcount-part.cc
        +wordcount-simple.cc
        +

        运行前需要把可执行文件和输入数据上传到 hdfs:

        +
        $ ./bin/hadoop fs -mkdir /test/pipes/input
        +$ ./bin/hadoop fs -put a.txt /test/pipes/input 
        +$ ./bin/hadoop fs -cat /test/pipes/input/a.txt 
        +hello hadoop hello hive hello hbase hello zk
        +

        上传执行文件,重新命名为/test/pipes/exec

        +
        $ ./bin/hadoop fs -put ./build/c++-examples/Linux-amd64-64/bin/wordcount-simple /test/pipes/exec
        +

        在编译好的文件夹目录下执行

        +
        $ cd hadoop/build/c++-examples/Linux-amd64-64/bin
        +$ ../../../../bin/hadoop pipes -Dhadoop.pipes.java.recordreader=true -Dhadoop.pipes.java.recordwriter=true -reduces 4 -input /test/pipes/input -output /test/pipes/input/output1 -program /test/pipes/execs
        +

        执行结果如下:

        +
        $ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00000 hbase 1 
        +$ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00001 hello 4 hive 1 
        +$ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00002 hadoop 1 zk 1 
        +$ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00003
        +

        参考博客:

        + +]]>
        +
        + + <![CDATA[Hadoop MapReduce Sort]]> + + 2014-03-03T22:24:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce-sort/ + 排序是 MapReduce 的核心。排序可分为四种排序:普通排序、部分排序、全局排序、辅助排序

        +

        普通排序

        +

        Mapreduce 本身自带排序功能;Text 对象是不适合排序的;IntWritable,LongWritable 等实现了WritableComparable 类型的对象都是可以排序的。

        +

        部分排序

        +

        map 和 reduce 处理过程中包含了默认对 key 的排序,那么如果不要求全排序,可以直接把结果输出,每个输出文件中包含的就是按照key执行排序的结果。

        +

        控制排序顺序

        +

        键的排序是由 RawComparator 控制的,规则如下:

        +
          +
        1. 若属性 mapred.output.key.comparator.class 已设置,则使用该类的实例。调用 JobConf 的 setOutputKeyComparatorClass() 方法进行设置。
        2. +
        3. 否则,键必须是 WritableComparable 的子类,并使用针对该键类的已登记的 comparator.
        4. +
        5. 如果没有已登记的 comparator ,则使用 RawComparator 将字节流反序列化为一个对象,再由 WritableComparable 的 compareTo() 方法进行操作。
        6. +
        +

        全局排序(对所有数据排序)

        +

        Hadoop 没有提供全局数据排序,而全局排序是非常普遍的需求。

        +

        实现方案

        +
          +
        • 首先,创建一系列的排好序的文件;
        • +
        • 其次,串联这些文件;
        • +
        • 最后,生成一个全局排序的文件。
        • +
        +

        主要思路是使用一个partitioner来描述全局排序的输出。该方法关键在于如何划分各个分区。

        +

        例,对整数排序,[0,10000] 的在 partition 0 中,(10000,20000] 在 partition 1 中… …即第n个reduce 所分配到的数据全部大于第 n-1 个 reduce 中的数据。每个 reduce 的结果都是有序的。
        +然后再将所有的输出文件顺序合并成一个大的文件,那么就实现了全局排序。

        +

        在比较理想的数据分布均匀的情况下,每个分区内的数据量要基本相同。

        +

        但实际中数据往往分布不均匀,出现数据倾斜,这时按照此方法进行的分区划分数据就不适用,可对数据进行采样。

        +

        采样器

        +

        通过对 key 空间进行采样,可以较为均匀的划分数据集。采样的核心思想是只查看一小部分键,获取键的相似分布,并由此构建分区。采样器是在 map 阶段之前进行的, 在提交 job 的 client 端完成的。

        +

        Sampler接口

        +

        Sampler 接口是 Hadoop 的采样器,它的 getSample() 方法返回一组样本。此接口一般不由客户端调用,而是由 InputSampler 类的静态方法 writePartitionFile() 调用,以创建一个顺序文件来存储定义分区的键。

        +

        Sampler接口声明如下:

        +
          public interface Sampler<K,V> {
        +   K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException;
        +  }
        +

        继承 Sample 的类还有 IntervalSampler 间隔采样器,RandomSampler 随机采样器,SplitSampler 分词采样器。它们都是 InputSampler 的静态内部类。

        +

        getSample() 方法根据 job 的配置信息以及输入格式获得抽样结果,三个采样类各自有不同的实现。

        +

        IntervalSampler 根据一定的间隔从 s 个分区中采样数据,非常适合对排好序的数据采样。

        +
        public static class IntervalSampler<K,V> implements Sampler<K,V> {
        +    private final double freq;// 哪一条记录被选中的概率
        +    private final int maxSplitsSampled;// 采样的最大分区数
        +    /**
        +     * For each split sampled, emit when the ratio of the number of records
        +     * retained to the total record count is less than the specified
        +     * frequency.
        +     */
        +    @SuppressWarnings("unchecked") // ArrayList::toArray doesn't preserve type
        +    public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {
        +      InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());// 1. 得到输入分区数组
        +      ArrayList<K> samples = new ArrayList<K>();
        +      int splitsToSample = Math.min(maxSplitsSampled, splits.length);
        +      int splitStep = splits.length / splitsToSample; // 2. 分区采样时的间隔splitStep = 输入分区总数 除以 splitsToSample的 商;
        +      long records = 0;
        +      long kept = 0;
        +      for (int i = 0; i < splitsToSample; ++i) {
        +        RecordReader<K,V> reader = inf.getRecordReader(splits[i * splitStep], // 3. 采样下标为i * splitStep的数据
        +            job, Reporter.NULL);
        +        K key = reader.createKey();
        +        V value = reader.createValue();
        +        while (reader.next(key, value)) {// 6. 循环读取下一条记录
        +          ++records;
        +          if ((double) kept / records < freq) { // 4. 如果当前样本数与已经读取的记录数的比值小于freq,则将这条记录添加到样本集合
        +            ++kept;
        +            samples.add(key);// 5. 将记录添加到样本集合中
        +            key = reader.createKey();
        +          }
        +        }
        +        reader.close();
        +      }
        +      return (K[])samples.toArray();
        +    }
        +  }
        +… … 
        +}
        +

        RandomSampler 是常用的采样器,它随机地从输入数据中抽取 Key

        +
          public static class RandomSampler<K,V> implements Sampler<K,V> {
        +    private double freq;// 一个Key被选中的 概率
        +    private final int numSamples;// 从所有被选中的分区中获得的总共的样本数目
        +    private final int maxSplitsSampled;// 需要检查扫描的最大分区数目
        +/**
        +     * Randomize the split order, then take the specified number of keys from
        +     * each split sampled, where each key is selected with the specified
        +     * probability and possibly replaced by a subsequently selected key when
        +     * the quota of keys from that split is satisfied.
        +     */
        +    @SuppressWarnings("unchecked") // ArrayList::toArray doesn't preserve type
        +    public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {
        +      InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());// 1. 获取所有的输入分区
        +      ArrayList<K> samples = new ArrayList<K>(numSamples);// 2. 确定需要抽样扫描的分区数目
        +      int splitsToSample = Math.min(maxSplitsSampled, splits.length);// 3. 取最小的为采样的分区数
        +      Random r = new Random();
        +      long seed = r.nextLong();
        +      r.setSeed(seed);
        +      LOG.debug("seed: " + seed);
        +      // shuffle splits 4. 对输入分区数组shuffle排序
        +      for (int i = 0; i < splits.length; ++i) {
        +        InputSplit tmp = splits[i];
        +        int j = r.nextInt(splits.length);// 5. 打乱其原始顺序
        +        splits[i] = splits[j];
        +        splits[j] = tmp;
        +      }
        +      // our target rate is in terms of the maximum number of sample splits,
        +      // but we accept the possibility of sampling additional splits to hit
        +      // the target sample keyset
        +// 5. 然后循环逐 个扫描每个分区中的记录进行采样,
        +      for (int i = 0; i < splitsToSample ||
        +                     (i < splits.length && samples.size() < numSamples); ++i) {
        +        RecordReader<K,V> reader = inf.getRecordReader(splits[i], job,
        +            Reporter.NULL);
        +       // 6. 取出一条记录
        +        K key = reader.createKey();
        +        V value = reader.createValue();
        +        while (reader.next(key, value)) {
        +          if (r.nextDouble() <= freq) {
        +            if (samples.size() < numSamples) {// 7. 判断当前的采样数是否小于最大采样数
        +              samples.add(key); //8. 小于则这条记录被选中,放进采样集合中,
        +            } else {
        +              // When exceeding the maximum number of samples, replace a
        +              // random element with this one, then adjust the frequency
        +              // to reflect the possibility of existing elements being
        +              // pushed out
        +              int ind = r.nextInt(numSamples);// 9. 从[0,numSamples]中选择一个随机数
        +              if (ind != numSamples) {
        +                samples.set(ind, key);// 10. 替换掉采样集合随机数对应位置的记录,
        +              }
        +              freq *= (numSamples - 1) / (double) numSamples;// 11. 调小频率
        +            }
        +            key = reader.createKey();// 12. 下一条纪录的key
        +          }
        +        }
        +        reader.close();
        +      }
        +      return (K[])samples.toArray();// 13. 返回
        +    }
        +  }
        +… … 
        +}
        +

        SplitSampler 从 s 个分区中采样前 n 个记录,是采样随机数据的一种简便方式。

        +
          public static class SplitSampler<K,V> implements Sampler<K,V> {
        +    private final int numSamples;// 最大采样数
        +    private final int maxSplitsSampled;// 最大分区数
        +    … … 
        +    /**
        +     * From each split sampled, take the first numSamples / numSplits records.
        +     */
        +    @SuppressWarnings("unchecked") // ArrayList::toArray doesn't preserve type
        +    public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {
        +      InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());
        +      ArrayList<K> samples = new ArrayList<K>(numSamples);
        +      int splitsToSample = Math.min(maxSplitsSampled, splits.length);// 1. 采样的分区数
        +      int splitStep = splits.length / splitsToSample; // 2. 分区采样时的间隔 = 分片的长度 与 输入分片的总数的 商
        +      int samplesPerSplit = numSamples / splitsToSample; // 3. 每个分区的采样数 
        +      long records = 0;
        +      for (int i = 0; i < splitsToSample; ++i) {
        +        RecordReader<K,V> reader = inf.getRecordReader(splits[i * splitStep], // 4.采样下标为i * splitStep的数据
        +            job, Reporter.NULL);
        +        K key = reader.createKey();
        +        V value = reader.createValue();
        +        while (reader.next(key, value)) {
        +          samples.add(key);// 5. 将记录添加到样本集合中
        +          key = reader.createKey();
        +          ++records;
        +          if ((i+1) * samplesPerSplit <= records) { // 6. 当前样本数大于当前的采样分区所需要的样本数,则停止对当前分区的采样。
        +            break;
        +          }
        +        }
        +        reader.close();
        +      }
        +      return (K[])samples.toArray();
        +    }
        +  }
        +

        Hadoop为顺序文件提供了一个 TotalOrderPartitioner 类,可以用来实现全局排序;TotalOrderPartitioner 源代码理解。TotalOrderPartitioner 内部定义了多个字典树(内部类)。

        +
        interface Node<T> 
        +// 特里树,利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高
        +static abstract class TrieNode implements Node<BinaryComparable> 
        +static class InnerTrieNode extends TrieNode 
        +static class LeafTrieNode extends TrieNode
        +… … 
        +

        由 TotalOrderPartitioner 调用 getPartition() 方法返回分区,由 buildTrieRec() 构建特里树.

        +
         private TrieNode buildTrieRec(BinaryComparable[] splits, int lower,
        +      int upper, byte[] prefix, int maxDepth, CarriedTrieNodeRef ref) {
        +… … 
        +}
        +

        采样器使用示例

        +
          +
        1. 新建文件,名为 random.txt,里面每行存放一个数据。可由 RandomGenerator 类生成准备数据
        2. +
        3. 执行 TestTotalOrderPartitioner.java
        4. +
        +

        辅助排序

        +

        先按 key 排序,在按 相同的 key 不同的 value 再排序。可实现对值分组的效果。

        + +]]>
        +
        + + <![CDATA[Hadoop MapReduce Join]]> + + 2014-03-03T22:23:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce-join/ + 在 Hadoop 中可以通过 MapReduce,Pig,hive,Cascading编程进行大型数据集间的连接操作。连接操作如果由 Mapper 执行,则称为“map端连接”;如果由 Reduce 执行,则称为“Reduce端连接”。

        +

        连接操作的具体实现技术取决于数据集的规模以及分区方式。
        +若一个数据集很大而另一个数据集很小,以至于可以分发到集群中的每一个节点之中,则可以执行一个 MapReduce 作业,将各个数据集的数据放到一起,从而实现连接。
        +若两个数据规模均很大,没有哪个数据集可以完全复制到集群的每个节点,可以使用 MapReduce 作业进行连接,使用 Map 端连接还是 Reduce 端连接取决于数据的组织方式。

        +

        Map端连接将所有的工作在 map 中操作,效率高但是不通用。而 Reduce 端连接利用了 shuff 机制,进行连接,效率不高。

        +

        DistributedCache 能够在任务运行过程中及时地将文件和存档复制到任务节点进行本地缓存以供使用。各个文件通常只复制到一个节点一次。可用 api 或者命令行在需要的时候将本地文件添加到 hdfs 文件系统中。

        +

        本文中的示例 出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件。

        +

        Map端连接

        +

        Map 端联接是指数据到达 map 处理函数之前进行合并的。它要求 map 的输入数据必须先分区并以特定的方式排序。各个输入数据集被划分成相同数量的分区,并均按相同的键排序(连接键)。同一键的所有输入纪录均会放在同一个分区。以满足 MapReduce 作业的输出。

        +

        若作业的 Reduce 数量相同、键相同、输入文件是不可切分的,那么 map 端连接操作可以连接多个作业的输出。

        +

        在 Map 端连接效率比 Reduce 端连接效率高(Reduce端Shuff耗时),但是要求比较苛刻。

        +

        基本思路

        +
          +
        1. 将需要 join 的两个文件,一个存储在 HDFS 中,一个使用 DistributedCache.addCacheFile() 将需要 join 另一个文件加入到所有 Map 的缓存里(DistributedCache.addCacheFile() 需要在作业提交前设置);
        2. +
        3. 在 Map 函数里读取该文件,进行 Join;
        4. +
        5. 将结果输出到 reduce 端;
        6. +
        +

        使用步骤

        +
          +
        1. 在 HDFS 中上传文件(文本文件、压缩文件、jar包等);
        2. +
        3. 调用相关API添加文件信息;
        4. +
        5. task运行前直接调用文件读写API获取文件;
        6. +
        +

        Reduce端Join

        +

        reduce 端联接比 map 端联接更普遍,因为输入的数据不需要特定的结构;效率低(所有数据必须经过shuffle过程)。

        +

        基本思路

        +
          +
        1. Map 端读取所有文件,并在输出的内容里加上标识代表数据是从哪个文件里来的;
        2. +
        3. 在 reduce 处理函数里,对按照标识对数据进行保存;
        4. +
        5. 然后根据 Key 的 Join 来求出结果直接输出;
        6. +
        +

        示例程序

        +

        使用 MapReduce map 端join 或者 reduce 端 join 实现如下两张表 emp, dep 中的 SQL 联合查询的数据效果。

        +
        Table EMP:(新建文件EMP,第一行属性名不要)
        +----------------------------------------
        +Name      Sex      Age     DepNo
        +zhang      male     20           1     
        +li              female  25           2
        +wang       female  30           3
        +zhou        male     35           2
        +----------------------------------------
        +Table Dep:(新建文件DEP,第一行属性名不要)
        +DepNo     DepName
        +     1            Sales
        +     2            Dev
        +     3            Mgt
        +------------------------------------------------------------     
        +SQL:
        +select name,sex ,age, depName from emp inner join DEP on EMP.DepNo = Dep.DepNo
        +----------------------------------------
        +实现效果:
        +$ ./bin/hadoop fs -cat /reduceSideJoin/output11/part-r-00000
        +zhang male 20 sales
        +li female 25 dev
        +wang female 30 dev
        +zhou male 35 dev
        +

        Map 端 Join 的例子:TestMapSideJoin
        +Reduce 端 Join 的例子:TestReduceSideJoin

        ]]>
        - <![CDATA[hadoop1.x 命令手册列举]]> - - 2014-01-18T18:29:00+08:00 - http://kangfoo.u.qiniudn.com//article/2014/01/hadoop1.x--ming-ling-shou-ce-lie-ju/ - hadoop命令一般分为两类:

        -

        用户命令
        archive, distcp, fs, fsck, jar, job, pipes, version, CLASSNAME

        -

        管理命令
        balancer, daemonlog, datanode, dfsadmin, jobtracker, namenode, secondarynamenode, tasktracker

        -

        用户命令

        -
          -
        1. fs命令,可参见 file system shell

          - -
          ## 新建一个文件夹
          -$ ./hadoop fs -mkdir /test
          -## 上传一个文件到hdfs
          -$ ./hadoop fs -put ./rcc /test
          -## 查看文件
          -$ ./hadoop fs -lsr /test
          -## 文件模式  备份个数   用户   用户组   字节大小 最后修改日期 和时间 文件或者目录的绝对路径
          -##-rw-r--r--   2   hadoop supergroup 2810   2014-01-18 19:20  /test/rcc
          -## 
          -## 从hdfs文件系统中复制一个文件到本地
          -$ ./hadoop fs -copyToLocal /test/rcc rcc.copy
          -## md5对比
          -$ md5sum rcc rcc.copy
          -# b9d8a383bba2dd1b2ee0ede6c5cabeae  rcc
          -# b9d8a383bba2dd1b2ee0ede6c5cabeae  rcc.copy
          +    <![CDATA[Hadoop MapReduce 计数器]]>
          +    
          +    2014-03-03T22:22:00+08:00
          +    http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce--ji-shu-qi/
          +    计数器是一种收集系统信息有效手段,用于质量控制或应用级统计。可辅助诊断系统故障。计数器可以比日志更方便的统计事件发生次数。

          +

          内置计数器

          +

          Hadoop 为每个作业维护若干内置计数器,主要用来记录作业的执行情况。

          +

          内置计数器包括

          +
            +
          • MapReduce 框架计数器(Map-Reduce Framework)
          • +
          • 文件系统计数器(FielSystemCounters)
          • +
          • 作业计数器(Job Counters)
          • +
          • 文件输入格式计数器(File Output Format Counters)
          • +
          • 文件输出格式计数器(File Input Format Counters)
          • +
          + + +

          计数器由其关联的 task 进行维护,定期传递给 tasktracker,再由 tasktracker 传给 jobtracker。因此,计数器能够被全局地聚集。内置计数器实际由 jobtracker 维护,不必在整个网络发送。

          +

          一个任务的计数器值每次都是完整传输的,仅当一个作业执行成功之后,计数器的值才完整可靠的。

          +

          自定义Java计数器

          +

          MapReduce 允许用户自定义计数器,MapReduce 框架将跨所有 map 和 reduce 聚集这些计数器,并在作业结束的时候产生一个最终的结果。

          +

          计数器的值可以在 mapper 或者 reducer 中添加。多个计数器可以由一个 java 枚举类型来定义,以便对计数器分组。一个作业可以定义的枚举类型数量不限,个个枚举类型所包含的数量也不限。

          +

          枚举类型的名称即为组的名称,枚举类型的字段即为计数器名称。

          +

          在 TaskInputOutputContext 中的 counter

          +
           public Counter getCounter(Enum<?> counterName) {
          +    return reporter.getCounter(counterName);
          +  }
          +  public Counter getCounter(String groupName, String counterName) {
          +    return reporter.getCounter(groupName, counterName);
          +  }
          +

          计数器递增

          +

          org.apache.hadoop.mapreduce.Counter类

          +
            public synchronized void increment(long incr) {
          +    value += incr;
          +  }
          +

          计数器使用

          +
            +
          • WebUI 查看(50030);
          • +
          • 命令行方式:hadoop job [-counter ];
          • +
          • 使用Hadoop API。 +通过job.getCounters()得到Counters,而后调用counters.findCounter()方法去得到计数器对象;可参见《Hadoop权威指南》第8章 示例 8-2 MissingTemperaureFields.java
          • +
          +

          命令行方式示例

          +
          $ ./bin/hadoop job -counter  job_201402211848_0004 FileSystemCounters HDFS_BYTES_READ
          +177
          +

          自定义计数器

          +

          统计词汇行中词汇数超过2个或少于2个的行数。 源代码: TestCounter.javaTestCounter.java

          +

          输入数据文件值 counter.txt:

          +
          hello world
          +hello
          +hello world 111
          +hello world 111 222
          +

          执行参数

          +
          hdfs://master11:9000/counter/input/a.txt hdfs://master11:9000/counter/output1
          +

          计数器统计(hadoop eclipse 插件执行)结果:

          +
          2014-02-21 00:03:38,676 INFO  mapred.JobClient (Counters.java:log(587)) -   ERROR_COUNTER
          +2014-02-21 00:03:38,677 INFO  mapred.JobClient (Counters.java:log(589)) -     Above_2=2
          +2014-02-21 00:03:38,677 INFO  mapred.JobClient (Counters.java:log(589)) -     BELOW_2=1
          +
          ]]>
          + + + <![CDATA[Hadoop MapReduce RecordReader 组件]]> + + 2014-03-03T22:21:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce-recordreader-zu-jian/ + 由 RecordReader 决定每次读取以什么样的方式读取数据分片中的一条数据。Hadoop 默认的 RecordReader 是 LineRecordReader(TextInputFormat 的 getRecordReader() 方法返回即是 LineRecordReader。二进制输入 SequenceFileInputFormat 的 getRecordReader() 方法返回即是SequenceFileRecordReader。)。LineRecordReader是用每行的偏移量作为 map 的 key,每行的内容作为 map 的 value;

          +

          它可作用于,自定义读取每一条记录的方式;自定义读入 key 的类型,如希望读取的 key 是文件的路径或名字而不是该行在文件中的偏移量。

          +

          自定义RecordReader一般步骤

          +
            +
          1. 继承抽象类 RecordReader,实现 RecordReader 的实例;
          2. +
          3. 实现自定义 InputFormat 类,重写 InputFormat 中 createRecordReader() 方法,返回值是自定义的 RecordReader 实例; +(3)配置 job.setInputFormatClass() 设置自定义的 InputFormat 类型;
          4. +
          +

          TextInputFormat类源代码理解

          +

          源码见 org.apache.mapreduce.lib.input.TextInputFormat 类(新API);

          +

          Hadoop 默认 TextInputFormat 使用 LineRecordReader。具体分析见注释。

          +
            public RecordReader<LongWritable, Text> 
          +    createRecordReader(InputSplit split,
          +                       TaskAttemptContext context) {
          +    return new LineRecordReader();
          +  }
          +// --> LineRecordReader
          + public void initialize(InputSplit genericSplit,
          +                         TaskAttemptContext context) throws IOException {
          +    FileSplit split = (FileSplit) genericSplit;
          +    Configuration job = context.getConfiguration();
          +    this.maxLineLength = job.getInt("mapred.linerecordreader.maxlength",
          +                                    Integer.MAX_VALUE);
          +    start = split.getStart();  // 当前分片在整个文件中的起始位置
          +    end = start + split.getLength(); // 当前分片,在整个文件的位置
          +    final Path file = split.getPath();
          +    compressionCodecs = new CompressionCodecFactory(job);// 压缩
          +    codec = compressionCodecs.getCodec(file);
          +//
          +    // open the file and seek to the start of the split
          +    FileSystem fs = file.getFileSystem(job);
          +    FSDataInputStream fileIn = fs.open(split.getPath()); // 获取 FSDataInputStream
          +//
          +    if (isCompressedInput()) {
          +      decompressor = CodecPool.getDecompressor(codec);
          +      if (codec instanceof SplittableCompressionCodec) {
          +        final SplitCompressionInputStream cIn =
          +          ((SplittableCompressionCodec)codec).createInputStream(
          +            fileIn, decompressor, start, end,
          +            SplittableCompressionCodec.READ_MODE.BYBLOCK);
          +        in = new LineReader(cIn, job); //一行行读取
          +        start = cIn.getAdjustedStart(); // 可能跨分区读取
          +        end = cIn.getAdjustedEnd();// 可能跨分区读取
          +        filePosition = cIn;
          +      } else {
          +        in = new LineReader(codec.createInputStream(fileIn, decompressor),
          +            job);
          +        filePosition = fileIn;
          +      }
          +    } else {
          +      fileIn.seek(start);//  调整到文件起始偏移量
          +      in = new LineReader(fileIn, job); 
          +      filePosition = fileIn;
          +    }
          +    // If this is not the first split, we always throw away first record
          +    // because we always (except the last split) read one extra line in
          +    // next() method.
          +    if (start != 0) {
          +      start += in.readLine(new Text(), 0, maxBytesToConsume(start));
          +    }
          +    this.pos = start; // 在当前分片的位置
          +  }
          +//  --> getFilePosition() 指针读取到哪个位置
          +// filePosition 为 Seekable 类型
          +  private long getFilePosition() throws IOException {
          +    long retVal;
          +    if (isCompressedInput() && null != filePosition) {
          +      retVal = filePosition.getPos();
          +    } else {
          +      retVal = pos;
          +    }
          +    return retVal;
          +  }
          +//
          +// --> nextKeyValue() 
          +public boolean nextKeyValue() throws IOException {
          +    if (key == null) {
          +      key = new LongWritable();
          +    }
          +    key.set(pos);
          +    if (value == null) {
          +      value = new Text();
          +    }
          +    int newSize = 0;
          +    // We always read one extra line, which lies outside the upper
          +    // split limit i.e. (end - 1)
          +    // 预读取下一条纪录
          +    while (getFilePosition() <= end) {
          +      newSize = in.readLine(value, maxLineLength,
          +          Math.max(maxBytesToConsume(pos), maxLineLength));
          +      if (newSize == 0) {
          +        break;
          +      }
          +      pos += newSize; // 下一行的偏移量
          +      if (newSize < maxLineLength) {
          +        break;
          +      }
          +//
          +      // line too long. try again
          +      LOG.info("Skipped line of size " + newSize + " at pos " + 
          +               (pos - newSize));
          +    }
          +    if (newSize == 0) {
          +      key = null;
          +      value = null;
          +      return false;
          +    } else {
          +      return true;
          +    }
          +  }
          +

          自定义 RecordReader 演示

          +

          假设,现有如下数据 10 ~ 70 需要利用自定义 RecordReader 组件分别计算数据奇数行和偶数行的数据之和。结果为:奇数行之和等于 160,偶数和等于 120。出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件。

          +

          数据:
          +10
          +20
          +30
          +40
          +50
          +60
          +70

          +

          源代码

          +

          TestRecordReader.java

          +

          数据准备

          +
          $ ./bin/hadoop fs -mkdir /inputreader
          +$ ./bin/hadoop fs -put ./a.txt /inputreader
          +$ ./bin/hadoop fs -lsr /inputreader
          +-rw-r--r--   2 hadoop supergroup         21 2014-02-20 21:04 /inputreader/a.txt
          +

          执行

          +
          $ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestRecordReader  /inputreader /inputreaderout1
          +##
          +$ ./bin/hadoop fs -lsr /inputreaderout1
          +-rw-r--r--   2 hadoop supergroup          0 2014-02-20 21:12 /inputreaderout1/_SUCCESS
          +drwxr-xr-x   - hadoop supergroup          0 2014-02-20 21:11 /inputreaderout1/_logs
          +drwxr-xr-x   - hadoop supergroup          0 2014-02-20 21:11 /inputreaderout1/_logs/history
          +-rw-r--r--   2 hadoop supergroup      16451 2014-02-20 21:11 /inputreaderout1/_logs/history/job_201402201934_0002_1392901901142_hadoop_TestRecordReader
          +-rw-r--r--   2 hadoop supergroup      48294 2014-02-20 21:11 /inputreaderout1/_logs/history/job_201402201934_0002_conf.xml
          +-rw-r--r--   2 hadoop supergroup         23 2014-02-20 21:12 /inputreaderout1/part-r-00000
          +-rw-r--r--   2 hadoop supergroup         23 2014-02-20 21:12 /inputreaderout1/part-r-00001
          +##
          +$ ./bin/hadoop fs -cat /inputreaderout1/part-r-00000
          +偶数行之和:  120
           ##
          -## 删除
          -$ ./hadoop fs -rmr /test
          -
        2. -
        3. fsck命令。 可以参考文件系统的健康状态;查看一个文件所在的数据块;可以删除一个坏块;可以查找一个缺失的块。 可参见fsck

          - -
          ## 新建一个文件夹
          -$ ./hadoop fsck /
          -
        4. -
        5. archive命令。 创建一个hadoop档案文件。语法:archive -archiveName NAME -p <parent path> <src>* <dest> 可参考hadoop_archives

          - -
          ## 创建一个归档文件
          -$ ./hadoop archive -archiveName archive.har -p  /test rcc
          -## 查看归档文件列表
          -$ ./hadoop dfs -lsr har:///user/hadoop/rcc/archive.har
          -## 查看归档文件
          -$ ./hadoop dfs -cat har:///user/hadoop/rcc/archive.har/rcc
          -
        6. -
        -

        管理命令

        -
          -
        1. dfsadmin

          - -
          ## 报告文件系统的基本信息和统计信息
          -$ ./hadoop dfsadmin -report
          -## 安全模式维护命令
          -$ ./hadoop dfsadmin -safemode enter
          -## -safemode enter | leave | get | wait
          -## 不接受对名字空间的更改(只读), 不复制或删除块
          -## -setQuota 为每个目录 <dirname>设定配额<quota>。包括文件夹和文件名称。
          -$ ./hadoop dfsadmin -setQuota 2 /test
          -$ ./hadoop fs -put ./rcc /test
          -$ ./hadoop fs -put ./hadoop /test
          -put: org.apache.hadoop.hdfs.protocol.NSQuotaExceededException: The NameSpace quota (directories and files) of directory /test is exceeded: quota=2 file count=3
          -
        2. -
        3. balancer命令。集群平衡工具

          - -
          ##
          -$ ./hadoop balancer
          -或者
          -$ ./bin/./start-balancer.sh
          -
        4. -
        -
        其他未列举的命令可以参见官方文档。
        -

        官方文档链接: hadoop1.2.1 Commands Guide, hadoop1.0.4 命令手册

        +$ ./bin/hadoop fs -cat /inputreaderout1/part-r-00001 +奇数行之和: 160 +]]>
        +
        + + <![CDATA[Hadoop MapReduce Partitioner 组件]]> + + 2014-03-03T22:20:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce-partitioner--zu-jian/ + Partitioner 过程发生在循环缓冲区发生溢写文件之后,merge 之前。可以让 Map 对 Key 进行分区,从而可以根据不同的 key 来分发到不同的 reducer 中去处理;

        +

        Hadoop默认的提供的是HashPartitioner。

        +

        可以自定义 key 的分发规则,自定义Partitioner:

        +
          +
        • 继承抽象类Partitioner,实现自定义的getPartition()方法;
        • +
        • 通过job.setPartitionerClass()来设置自定义的Partitioner;
        • +
        +

        Partitioner 类

        +

        旧api

        +
        public interface Partitioner<K2, V2> extends JobConfigurable {
        +  int getPartition(K2 key, V2 value, int numPartitions);
        +}
        +

        新api

        +
        public abstract class Partitioner<KEY, VALUE> {
        +  public abstract int getPartition(KEY key, VALUE value, int numPartitions);  
        +}
        +

        Partitioner应用场景演示

        +

        需求:利用 Hadoop MapReduce 作业 Partitioner 组件分别统计每种商品的周销售情况。源代码 TestPartitioner.java出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件。 (可使用 PM2.5 数据代替此演示程序)

        +
          +
        • site1的周销售清单(a.txt,以空格分开):

          +
          shoes   20
          +hat 10
          +stockings   30
          +clothes 40
          +
        • +
        • site2的周销售清单(b.txt,以空格分开):

          +
          shoes   15
          +hat 1
          +stockings   90
          +clothes 80
          +
        • +
        • 汇总结果:

          +
          shoes     35
          +hat       11
          +stockings 120
          +clothes   120
          +
        • +
        • 准备测试数据

          +
          $ ./bin/hadoop fs -mkdir /testPartitioner/input
          +$ ./bin/hadoop fs -put a.txt /testPartitioner/input
          +$ ./bin/hadoop fs -put b.txt /testPartitioner/input
          +$ ./bin/hadoop fs -lsr /testPartitioner/input
          +-rw-r--r--   2 hadoop supergroup         52 2014-02-18 22:53 /testPartitioner/input/a.txt
          +-rw-r--r--   2 hadoop supergroup         50 2014-02-18 22:53 /testPartitioner/input/b.txt
          +
        • +
        • 执行 MapReduce 作业 +此处使用 hadoop jar 命令执行,eclipse 插件方式有一定的缺陷。(hadoop eclipse 执行出现java.io.IOException: Illegal partition for hat (1))

          +
          $ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestPartitioner /testPartitioner/input /testPartitioner/output10
          +
        • +
        • 结果。 四个分区,分别存储上述四种产品的总销量的统计结果值。

          +
          -rw-r--r--   2 hadoop supergroup          9 2014-02-19 00:18 /testPartitioner/output10/part-r-00000
          +-rw-r--r--   2 hadoop supergroup          7 2014-02-19 00:18 /testPartitioner/output10/part-r-00001
          +-rw-r--r--   2 hadoop supergroup         14 2014-02-19 00:18 /testPartitioner/output10/part-r-00002
          +-rw-r--r--   2 hadoop supergroup         12 2014-02-19 00:18 /testPartitioner/output10/part-r-00003
          +
        • +
        ]]>
        - <![CDATA[编译hadoop 2.x Hadoop-eclipse-plugin插件]]> - - 2013-12-15T00:19:00+08:00 - http://kangfoo.u.qiniudn.com//article/2013/12/build-hadoop2x-eclipse-plugin/ - 经过hadoop1.x的发展,编译hadoop2.x版本的eclipse插件视乎比之前要轻松的多。如果你不在意编译过程中提示的警告,那么根据how to build - hadoop2x-eclipse-plugin文档就可一步到位。若想自己设置部分变量,可参考编译hadoop 1.2.1 Hadoop-eclipse-plugin插件。当然有问题及时和开发社区联系你会收到意想不到的收获.

        -

        issuce站点.

        -

        主要步骤

        + <![CDATA[Hadoop MapReduce Combiner 组件]]> + + 2014-03-03T22:19:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce-combiner--zu-jian/ + +

        combiner 作用是把一个 map 产生的多个 合并成一个新的 ,然后再将新的 作为 reduce 的输入;

        +

        combiner 函数在 map 函数与 reduce 函数之间,目的是为了减少 map 输出的中间结果,减少 reduce 复制 map 输出的数据,减少网络传输负载;

        +

        并不是所有情况下都能使用 Combiner 组件,它适用于对记录汇总的场景(如求和,平均数不适用)

        +

        什么时候运行 Combiner

          -
        • 介质准备
        • -
        • 执行
        • -
        • 安装验证
        • +
        • 当 job 设置了 Combiner,并且 spill 的个数达到 min.num.spill.for.combine (默认是3)的时候,那么 combiner 就会 Merge 之前执行;
        • +
        • 但是有的情况下,Merge 开始执行,但 spill 文件的个数没有达到需求,这个时候 Combiner 可能会在Merge 之后执行;
        • +
        • Combiner 也有可能不运行,Combiner 会考虑当时集群的一个负载情况。
        -

        具体操作

        +

        测试 Combinner 过程

        +

        代码 TestCombiner

          -
        1. 设置语言环境

          - -
          $ export LC_ALL=en
          +
        2. 以 wordcount.txt 为输入的词频统计

          +
          $ ./bin/hadoop fs -lsr /test3/input
          +drwxr-xr-x   - hadoop supergroup          0 2014-02-18 00:28 /test3/input/test
          +-rw-r--r--   2 hadoop supergroup        983 2014-02-18 00:28 /test3/input/test/wordcount.txt
          +-rw-r--r--   2 hadoop supergroup        626 2014-02-18 00:28 /test3/input/test/wordcount2.txt
          +
        3. +
        4. 不启用 Reducer (输出,字节变大)

          +
          drwxr-xr-x   - kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1
          +-rw-r--r--   3 kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1/_SUCCESS
          +-rw-r--r--   3 kangfoo-mac supergroup       1031 2014-02-18 00:29 /test3/output1/part-m-00000 (-m 没有 reduce 过程的中间结果,每个数据文件对应一个数据分片,每个分片对应一个map任务)
          +-rw-r--r--   3 kangfoo-mac supergroup        703 2014-02-18 00:29 /test3/output1/part-m-00001
          +

          结果如下(map过程并不合并相同key的value值):

          +
          drwxr-xr-x  1
          +-  1
          +hadoop  1
          +supergroup  1
          +0   1
          +2014-02-17  1
          +21:03   1
          +/home/hadoop/env/mapreduce  1
          +drwxr-xr-x  1
          +-  1
          +hadoop  1
          +
        5. +
        6. 启用 Reducer

          +
          drwxr-xr-x   - kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1
          +-rw-r--r--   3 kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1/_SUCCESS
          +-rw-r--r--   3 kangfoo-mac supergroup       1031 2014-02-18 00:29 /test3/output1/part-m-00000
          +-rw-r--r--   3 kangfoo-mac supergroup        703 2014-02-18 00:29 /test3/output1/part-m-00001
          +drwxr-xr-x   - kangfoo-mac supergroup          0 2014-02-18 00:31 /test3/output2
          +-rw-r--r--   3 kangfoo-mac supergroup          0 2014-02-18 00:31 /test3/output2/_SUCCESS
          +-rw-r--r--   3 kangfoo-mac supergroup        705 2014-02-18 00:31 /test3/output2/part-r-00000
          +

          结果:

          +
          0:17:31,680 6
          +014-02-18   1
          +2014-02-17  11
          +2014-02-18  5
          +21:02   7
           
        7. -
        8. 检查ANT_HOME,JAVA_HOME

          +
        9. 在日志或者 http://master11:50030/jobtracker.jsp 页面查找是否执行过 Combine 过程。 +日志截取如下:

          +
          2014-02-18 00:31:29,894 INFO  SPLIT_RAW_BYTES=233
          +2014-02-18 00:31:29,894 INFO  Combine input records=140
          +2014-02-18 00:31:29,894 INFO  Reduce input records=43
          +2014-02-18 00:31:29,894 INFO  Reduce input groups=42
          +2014-02-18 00:31:29,894 INFO  Combine output records=43
          +2014-02-18 00:31:29,894 INFO  Reduce output records=42
          +2014-02-18 00:31:29,894 INFO  Map output records=140
          +
        10. +
        +]]>
        +
        + + <![CDATA[Hadoop MapReduce 类型与格式]]> + + 2014-03-03T22:18:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce--lei-xing-yu-ge-shi/ + MapReduce 的 map和reduce函数的输入和输出是键/值对(key/value pair) 形式的数据处理模型。

        +

        MapReduce 的类型

        +

        Hadoop1.x MapReduce 有2套API.旧api偏向与接口,新api偏向与抽象类,如无特殊默认列举为旧的api作讨论.

        +

        在Hadoop的MapReduce中,map和reduce函数遵循如下格式:

        +
          +
        • map(K1, V1) –> list (K2, V2) // map:对输入分片数据进行过滤数据,组织 key/value 对等操作
        • +
        • combine(K2, list(V2)) –> list(K2, V2) // 在map端对输出进行预处理,类似 reduce。combine 不一定适用任何情况,如:对总和求平均数。选用。
        • +
        • partition(K2, V2) –> integer // 将中间键值对划分到一个 reduce 分区,返回分区索引号。实际上,分区单独由键决定(值是被忽略的),分区内的键会排序,相同的键的所有值会合成一个组(list(V2))
        • +
        • reduce(K2, list(V2)) –> list(K3, V3) // 每个 reduce 会处理具有某些特性的键,每个键上都有值的序列,是通过对所有 map 输出的值进行统计得来的,reduce 根据所有map传来的结果,最后进行统计合并操作,并输出结果。
        • +
        +

        旧api类代码

        +
        public interface Mapper<K1, V1, K2, V2> extends JobConfigurable, Closeable {  
        +  void map(K1 key, V1 value, OutputCollector<K2, V2> output, Reporter reporter) throws IOException;
        +}
        +//
        +public interface Reducer<K2, V2, K3, V3> extends JobConfigurable, Closeable {
        +  void reduce(K2 key, Iterator<V2> values, OutputCollector<K3, V3> output, Reporter reporter) throws IOException;
        +}
        +//
        +public interface Partitioner<K2, V2> extends JobConfigurable {
        +   int getPartition(K2 key, V2 value, int numPartitions);
        +}
        +

        新api类代码

        +
        public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
        +… …
        +  protected void map(KEYIN key, VALUEIN value, 
        +                     Context context) throws IOException, InterruptedException {
        +    context.write((KEYOUT) key, (VALUEOUT) value);
        +  }
        +… …
        +}
        +//
        +public class Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
        +… …
        + protected void reduce(KEYIN key, Iterable<VALUEIN> values, Context context
        +                        ) throws IOException, InterruptedException {
        +    for(VALUEIN value: values) {
        +      context.write((KEYOUT) key, (VALUEOUT) value);
        +    }
        +  }
        +… …
        +}
        +//
        +public interface Partitioner<K2, V2> extends JobConfigurable {
        +  int getPartition(K2 key, V2 value, int numPartitions);
        +}
        +

        默认的 partitioner 是 HashPartitioner,对键进行哈希操作以决定该记录属于哪个分区让 reduce 处理,每个分区对应一个 reducer 任务。总槽数 solt=集群中节点数 * 每个节点的任务槽。实际值应该比理论值要小,以空闲一部分在错误容忍是备用。

        +

        HashPartitioner的实现

        +
        public class HashPartitioner<K, V> extends Partitioner<K, V> {
        +    public int getPartition(K key, V value, int numReduceTasks) {
        +        return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
        +    }
        +}
        +

        hadooop1.x 版本中

        +
          +
        • 旧的api,map 默认的 IdentityMapper, reduce 默认的是 IdentityReducer
        • +
        • 新的api,map 默认的 Mapper, reduce 默认的是 Reducer
        • +
        +

        默认MapReduce函数实例程序

        +
        public class MinimalMapReduceWithDefaults extends Configured implements Tool {
        +    @Override
        +    public int run(String[] args) throws Exception {
        +        Job job = JobBuilder.parseInputAndOutput(this, getConf(), args);
        +        if (job == null) {
        +            return -1;
        +            }
        +        //
        +        job.setInputFormatClass(TextInputFormat.class);
        +        job.setMapperClass(Mapper.class);
        +        job.setMapOutputKeyClass(LongWritable.class);
        +        job.setMapOutputValueClass(Text.class);
        +        job.setPartitionerClass(HashPartitioner.class);
        +        job.setNumReduceTasks(1);
        +        job.setReducerClass(Reducer.class);
        +        job.setOutputKeyClass(LongWritable.class);
        +        job.setOutputValueClass(Text.class);
        +        job.setOutputFormatClass(TextOutputFormat.class);
        +        return job.waitForCompletion(true) ? 0 : 1;
        +        }
        +    //
        +    public static void main(String[] args) throws Exception {
        +        int exitCode = ToolRunner.run(new MinimalMapReduceWithDefaults(), args);
        +        System.exit(exitCode);
        +        }
        +}
        +

        输入格式

        +

        输入分片与记录

        +

        一个输入分片(input split)是由单个 map 处理的输入块,即每一个 map 只处理一个输入分片,每个分片被划分为若干个记录( records ),每条记录就是一个 key/value 对,map 一个接一个的处理每条记录,输入分片和记录都是逻辑的,不必将他们对应到文件上。数据分片由数据块大小决定的。

        +

        注意,一个分片不包含数据本身,而是指向数据的引用( reference )。

        +

        输入分片在Java中被表示为InputSplit抽象类

        +
        public interface InputSplit extends Writable {
        +  long getLength() throws IOException;
        +  String[] getLocations() throws IOException;
        +}
        +

        InputFormat负责创建输入分片并将它们分割成记录,抽象类如下:

        +
        public interface InputFormat<K, V> {
        +  InputSplit[] getSplits(JobConf job, int numSplits) throws IOException;
        +  RecordReader<K, V> getRecordReader(InputSplit split,
        +                                     JobConf job, 
        +                                     Reporter reporter) throws IOException;
        +}
        +

        客户端通过调用 getSpilts() 方法获得分片数目(怎么调到的?),在 TaskTracker 或 NodeManager上,MapTask 会将分片信息传给 InputFormat 的 +createRecordReader() 方法,进而这个方法来获得这个分片的 RecordReader,RecordReader 基本就是记录上的迭代器,MapTask 用一个 RecordReader 来生成记录的 key/value 对,然后再传递给 map 函数,如下步骤:

        +
          +
        1. jobClient调用getSpilts()方法获得分片数目,将numSplits作为参数传入,以参考。InputFomat实现有自己的getSplits()方法。
        2. +
        3. 客户端将他们发送到jobtracker
        4. +
        5. jobtracker使用其存储位置信息来调度map任务从而在tasktracker上处理分片数据
        6. +
        7. 在tasktracker上,map任务把输入分片传给InputFormat上的getRecordReader()方法,来获取分片的RecordReader。
        8. +
        9. map 用一个RecordReader来生成纪录的键值对。
        10. +
        11. RecordReader的next()方法被调用,知道返回false。map任务结束。
        12. +
        +

        MapRunner 类部分代码(旧api)

        +
        public class MapRunner<K1, V1, K2, V2>
        +    implements MapRunnable<K1, V1, K2, V2> {
        +… … 
        + public void run(RecordReader<K1, V1> input, OutputCollector<K2, V2> output,
        +                  Reporter reporter)
        +    throws IOException {
        +    try {
        +      // allocate key & value instances that are re-used for all entries
        +      K1 key = input.createKey();
        +      V1 value = input.createValue();
        +      //
        +      while (input.next(key, value)) {
        +        // map pair to output
        +        mapper.map(key, value, output, reporter);
        +        if(incrProcCount) {
        +          reporter.incrCounter(SkipBadRecords.COUNTER_GROUP, 
        +              SkipBadRecords.COUNTER_MAP_PROCESSED_RECORDS, 1);
        +        }
        +      }
        +    } finally {
        +      mapper.close();
        +    }
        +  }
        +……
        +}
        +

        FileInputFormat类

        +

        FileInputFormat是所有使用文件为数据源的InputFormat实现的基类,它提供了两个功能:一个定义哪些文件包含在一个作业的输入中;一个为输入文件生成分片的实现,把分片割成记录的作业由其子类来完成。

        +

        下图为InputFormat类的层次结构: +image

        +

        FileInputFormat 类输入路径

        +

        FileInputFormat 提供四种静态方法来设定 Job 的输入路径,其中下面的 addInputPath() 方法 addInputPaths() 方法可以将一个或多个路径加入路径列表,setInputPaths() 方法一次设定完整的路径列表(可以替换前面所设路 径)

        +
        public static void addInputPath(Job job, Path path);
        +public static void addInputPaths(Job job, String commaSeparatedPaths);
        +public static void setInputPaths(Job job, Path... inputPaths);
        +public static void setInputPaths(Job job, String commaSeparatedPaths);
        +

        如果需要排除特定文件,可以使用 FileInputFormat 的 setInputPathFilter() 设置一个过滤器: +public static void setInputPathFilter(Job job, Class<? extends PathFilter> filter); +它默认过滤隐藏文件中以”_“和”.“开头的文件

        +
          private static final PathFilter hiddenFileFilter = new PathFilter(){
        +      public boolean accept(Path p){
        +        String name = p.getName(); 
        +        return !name.startsWith("_") && !name.startsWith("."); 
        +      }
        +    }; 
        +

        FileInputFormat 类的输入分片

        +

        FileInputFormat 类一般分割超过 HDFS 块大小的文件。通常分片与 HDFS 块大小一样,然后分片大小也可以改变的,下面展示了控制分片大小的属性:

        +

        待补。 TODO

        +
        FileInputFormat computeSplitSize(long goalSize, long minSize,long blockSize) {
        +    return Math.max(minSize, Math.min(goalSize, blockSize));
        +}
        +

        即: +minimumSize < blockSize < maximumSize 分片的大小即为块大小。

        +

        重载 FileInputFormat 的 isSplitable() =false 可以避免 mapreduce 输入文件被分割。

        +

        小文件与CombineFileInputFormat

        +
          +
        1. CombineFileInputFormat 是针对小文件设计的,CombineFileInputFormat 会把多个文件打包到一个分片中,以便每个 mapper 可以处理更多的数据;减少大量小文件的另一种方法可以使用 SequenceFile 将这些小文件合并成一个或者多个大文件。

          +
        2. +
        3. CombineFileInputFormat 不仅对于处理小文件实际上对于处理大文件也有好处,本质上,CombineFileInputFormat 使 map 操作中处理的数据量与 HDFS 中文件的块大小之间的耦合度降低了

        4. -
        5. 下载hadoop2x-eclipse-plugin
          -目前hadoop2的eclipse-plugins源代码由github脱管,下载地址how to build - hadoop2x-eclipse-plugin 右侧的 “Download ZIP” 或者 克隆到桌面. 当然你也可以fork到你自己的帐户下,在使用git clone。

          +
        6. CombineFileInputFormat 是一个抽象类,没有提供实体类,所以需要实现一个CombineFileInputFormat 具体 +类和 getRecordReader() 方法(旧的接口是这个方法,新的接口InputFormat中则是createRecordReader())

        7. -
        8. 执行

          - -
          $ cd src/contrib/eclipse-plugin
          -$ ant jar -Dversion=2.2.0 -Declipse.home=/opt/eclipse -Dhadoop.home=/usr/share/hadoop
          -

          将上述java system property eclipse.home 和 hadoop.home 设置成你自己的环境路径。 执行上述命令可能很快或者很慢。请耐心等待。主要慢的target:ivy-download,ivy-resolve-common。最后jar生成在 -$root/build/contrib/eclipse-plugin/hadoop-eclipse-plugin-2.2.0.jar路径下。

          +
        +

        把整个文件作为一条记录处理

        +

        有时,mapper 需要访问问一个文件中的全部内容。即使不分割文件,仍然需要一个 RecordReader 来读取文件内容为 record 的值,下面给出实现这个功能的完整程序,详细解释见《Hadoop权威指南》。

        +

        文本处理

        +
          +
        1. TextInputFileFormat 是默认的 InputFormat,每一行就是一个纪录

        2. -
        3. 安装验证
          -将生成好的jar,复制到${eclipse.home}/plugins目录下。启动eclipse,新建Map/Reduce Project,配置hadoop location.验证插件完全分布式的插件配置截图和core-site.xml端口配置。

          +
        4. TextInputFileFormat 的 key 是 LongWritable 类型,存储该行在整个文件的偏移量,value 是每行的数据内容,不包括任何终止符(换行符和回车符),它是Text类型. +如下例 +On the top of the Crumpetty Tree
          +
          +The Quangle Wangle sat,
          +But his face you could not see,
          +On account of his Beaver Hat.
          +每条记录表示以下key/value对
          +(0, On the top of the Crumpetty Tree)
          +(33, The Quangle Wangle sat,)
          +(57, But his face you could not see,)
          +(89, On account of his Beaver Hat.

        5. -
        6. 效果图
          -使用插件访问本地的伪分布式hadoop环境。查看文件texst1.txt和test2.txt同使用命令 hadoop dfs -ls /in 效果相同。 -image

          +
        7. 输入分片与 HDFS 块之间的关系:TextInputFormat 每一条纪录就是一行,很可能某一行跨数据库存放。

        8. -
        9. 已编译的插件 -hadoop-eclipse-plugin-2.2.0.jar

          +
        +

        image

        +
          +
        1. KeyValueTextInputFormat。对下面的文本,KeyValueTextInputFormat 比较适合处理,其中可以通过 +mapreduce.input.keyvaluelinerecordreader.key.value.separator 属性设置指定分隔符,默认 +值为制表符,以下指定”→“为分隔符 +
          +line1→On the top of the Crumpetty Tree
          +line2→The Quangle Wangle sat,
          +line3→But his face you could not see,
          +line4→On account of his Beaver Hat.

        2. -
        3. 备注
          -目前我在使用这个版本的插件时发现还时挺不稳定的。发现了两个缺陷。我的环境为:Java HotSpot™ 64-Bit Server VM、 eclipse-standard-kepler-SR1-macosx-cocoa、 hadoop2.2.0。

          +
        4. NLineInputFormat。如果希望 mapper 收到固定行数的输入,需要使用 NLineInputFormat 作为 InputFormat 。与 TextInputFormat 一样,key是文件中行的字节偏移量,值是行本身。

        -

        缺陷:

        +

        N 是每个 mapper 收到的输入行数,默认时 N=1,每个 mapper 会正好收到一行输入,mapreduce.input.lineinputformat.linespermap 属性控制 N 的值。以刚才的文本为例。 +如果N=2,则每个输入分片包括两行。第一个 mapper 会收到前两行 key/value 对:

        +

        (0, On the top of the Crumpetty Tree)
        +(33, The Quangle Wangle sat,)
        +另一个mapper则收到:
        +(57, But his face you could not see,)
        +(89, On account of his Beaver Hat.)

        +

        二进制输入

        +

        SequenceFileInputFormat +如果要用顺序文件数据作为 MapReduce 的输入,应用 SequenceFileInputFormat。key 和 value 顺序文件,所以要保证map输入的类型匹配

        +

        SequenceFileInputFormat 可以读 MapFile 和 SequenceFile,如果在处理顺序文件时遇到目录,SequenceFileInputFormat 类会认为值正在读 MapFile 数据文件。

        +

        SequenceFileAsTextInputFormat 是 SequenceFileInputFormat 的变体。将顺序文件(其实就是SequenceFile)的 key 和 value 转成 Text 对象

        +

        SequenceFileAsBinaryInputFormat是 SequenceFileInputFormat 的变体。将顺序文件的key和value作为二进制对象

        +

        多种输入

        +

        对于不同格式,不同表示的文本文件输出的处理,可以用 MultipleInputs 类里处理,它允许为每条输入路径指定 InputFormat 和 Mapper。

        +

        MultipleInputs 类有一个重载版本的 addInputPath()方法:

        +
          +
        • 旧api列举
          public static void addInputPath(JobConf conf, Path path, Class<? extends InputFormat> inputFormatClass) 
          +
        • +
        • 新api列举
          public static void addInputPath(Job job, Path path, Class<? extends InputFormat> inputFormatClass) 
          +
          在有多种输入格式只有一个mapper时候(调用Job的setMapperClass()方法),这个方法会很有用。
        • +
        +

        DBInputFormat

        +

        JDBC从关系数据库中读取数据的输入格式(参见权威指南)

        +

        输出格式

        +

        OutputFormat类的层次结构

        +

        image

        +

        文本输出

        +

        默认输出格式是 TextOutputFormat,它本每条记录写成文本行,key/value 任意,这里 key和value 可以用制表符分割,用 mapreduce.output.textoutputformat.separator 书信可以改变制表符,与TextOutputFormat 对应的输入格式是 KeyValueTextInputFormat。

        +

        可以使用 NullWritable 来省略输出的 key 和 value。

        +

        二进制输出

          -
        • Editor could not be initalized.
        • -
        • NullPointException
        • +
        • SequenceFileOutputFormat 将它的输出写为一个顺序文件,因为它的格式紧凑,很容易被压缩,所以易于作为 MapReduce 的输入
        • +
        • 把key/value对作为二进制格式写到一个 SequenceFile 容器中
        • +
        • MapFileOutputFormat 把 MapFile 作为输出,MapFile 中的 key 必需顺序添加,所以必须确保 reducer 输出的 key 已经排好序。
        -

        截图如下: -image

        +

        多个输出

        +
          +
        • MultipleOutputFormat 类可以将数据写到多个文件中,这些文件名称源于输出的键和值。MultipleOutputFormat是个抽象类,它有两个子类:MultipleTextOutputFormatMultipleSequenceFileOutputFormat 。它们是 TextOutputFormat 的和 SequenceOutputFormat 的多版本。

          +
        • +
        • MultipleOutputs 类 +用于生成多个输出的库,可以为不同的输出产生不同的类型,无法控制输出的命名。它用于在原有输出基础上附加输出。输出是制定名称的。

          +
        • +
        +

        MultipleOutputFormat和MultipleOutputs的区别

        +

        这两个类库的功能几乎相同。MultipleOutputs 功能更齐全,但 MultipleOutputFormat 对 目录结构和文件命令更多de控制。

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        特征 MultipleOutputFormat MultipleOutputs
        完全控制文件名和目录名
        不同输出有不同的键和值类型
        从同一作业的map和reduce使用
        每个纪录多个输出
        与任意OutputFormat一起使用 否,需要子类
        +

        延时输出

        +

        有些文件应用倾向于不创建空文件,此时就可以利用 LazyOutputFormat (Hadoop 0.21.0版本之后开始提供),它是一个封装输出格式,可以保证指定分区第一条记录输出时才真正的创建文件,要使用它,用JobConf和相关输出格式作为参数来调用 setOutputFormatClass() 方法.

        +

        Streaming 和 Pigs 支持 -LazyOutput 选项来启用 LazyOutputFormat功能。

        +

        数据库输出

        +

        参见 关系数据和 HBase的输出格式。

        +

        练习代码

        +

        代码路径 +https://github.com/kangfoo/hadoop1.study/blob/master/kangfoo/study.hdfs/src/main/java/com/kangfoo/study/hadoop1/mp/typeformat
        +使用 maven 打包之后用 hadoop jar 命令执行
        +步骤同 Hadoop example jar 类

        +
          +
        1. 使用 TextInputFormat 类型测试 wordcount +TestMapreduceInputFormat +上传一个文件

          +
          $ ./bin/hadoop fs -mkdir /test/input1
          +$ ./bin/hadoop fs -put ./wordcount.txt /test/input1
          +

          使用maven 打包 或者用eclipse hadoop 插件, 执行主函数时设置如下参数

          +
          hdfs://master11:9000/test/input1/wordcount.txt hdfs://master11:9000/numbers.seq hdfs://master11:9000/test/output5
          +

          没改过端口默认 namenode RPC 交互端口 8020 将上述的 9000 改成你自己的端口即可。
          +部分日志

          +
          ## 准备运行程序和测试数据
          +lrwxrwxrwx.  1 hadoop hadoop      86 2月  17 21:02 study.hdfs-0.0.1-SNAPSHOT.jar -> /home/hadoop/env/kangfoo.study/kangfoo/study.hdfs/target/study.hdfs-0.0.1-SNAPSHOT.jar
          +-rw-rw-r--.  1 hadoop hadoop    1983 2月  17 20:18 wordcount.txt
          +##执行
          +$ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestMapreduceInputFormat /test/input1/wordcount.txt /test/output1
          +
        2. +
        3. 使用SequenceInputFormat类型测试wordcound +使用Hadoop权威指南中的示例创建 /numbers.seq 文件

          +
          $ ./bin/hadoop fs -text /numbers.seq
          +$ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestMapreduceSequenceInputFormat /numbers.seq /test/output2
          +
        4. +
        5. 多文件输入

          +
          $  ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestMapreduceMultipleInputs /test/input1/wordcount.txt /numbers.seq /test/output3
          +
        6. +
        +

        博客参考

        +

        淘宝博客

        ]]>
        - <![CDATA[在oracle virtual box 虚拟机中搭建hadoop1.2.1完全分布式环境]]> - - 2013-12-10T17:32:00+08:00 - http://kangfoo.u.qiniudn.com//article/2013/12/xu-ni-ji-an-zhuang-hadoop-wan-quan-fen-bu-shi/ - 一、初衷 -

        对于学习Hadoop的我来将,没有足够的硬件设备,但又想安装完全分布式的Hadoop,一个master两个slave。手上就一台能联网的笔记本,那就使用oracle vitual box进行环境搭建把。环境搭建的效果为:在虚拟机中虚拟3台centos6.4 64位系统,每台都配置双网卡NAT,host-only模式。在宿主机器上安装eclipse进行Hadoop开发。

        -

        Hadoop环境搭建很大部分是在准备操作系统。具体如何搭建Hadoop其实就像解压缩普通的tar类似。然后再适当的配置Hadoop的dfs,mapredurce相关的配置,调整操作系统就可以开始着手学习Hadoop了。

        -

        二、拓补图

        -

        图片制作中… …

        -
          -
        • master11:192.168.56.11
        • -
        • slave12:192.168.56.12
        • -
        • slave14:192.168.56.14
        • -
        -

        三、介质准备

        - -

        其他版本可到相关官方网站根据需要自行下载

        -

        四、虚拟机和基础环境搭建

        -

        这里主要操作有:安装一个新的oracle virtual box,并先安装一个centos6.4 的64位的操作系统。配置操作系统双网卡、修改机器名为master11、新建hadoop用户组和hadoop用户、配置sudo权限、安装配置java环境、同步系统时间、关闭防火墙。其中有些步骤需要重启操作系统后成效,建议一切都配置后再重启并再次验证是否生效,并开始克隆两个DataNode节点服务器slave12\slave14。

        -

        4.1 安装虚拟机

        -

        可参见官方文档安装oracle virtual box

        -

        4.2 在虚拟机里安装centos6.4

        -

        此处使用的是mini版的centos6.4 64位网易提供的镜像。在安装中内存调整大于等于1g,默认为视图安装界面,小于1g则为命令行终端安装方式。可更具实际情况调整虚拟机资源分配。此处为内存1g,存储20g.网络NAT模式。

        -

        具体可参见centos安装

        -

        4.3 配置双网卡

        -

        使用自己的笔记本经常遇到的问题就是在不同的网络下ip是不一样的。那么我们在学习hadoop的时候岂不是要经常修改这些ip呢。索性就直接弄个host-only模式的让oracle virtual box提供一个虚拟的网关。具体步骤:

        -
          -
        1. 先关闭计算机 sudo poweroff

          -
        2. -
        3. 打开virtualbox主界面,依次点击屏幕左上角virtualbox->偏好设置->网络->点击右侧添加图标添加一个Host-Only网络vboxnet0,再设置参数值

          -主机虚拟网络界面(A)
          -IPv4地址(I):192.168.56.1
          -IPv4网络掩码(M):255.255.255.0
          -IPv6地址(P):空
          -IPv6网络掩码长度(L):0
          -DHCP服务器(D)
          -选择启动服务器
          -服务器地址(r):192.168.56.100
          -服务器网络掩码(M):255.255.255.0
          -最小地址(L):192.168.56.254
          -最大地址(U):192.168.56.254
          -
          -截图:image

          -
        4. -
        5. 配置网卡1。选中你刚新建的虚拟机,右键设置->网络->网卡1->点击启动网络连接(E)

          -连接方式(A):仅主机(Host-Only)适配器
          -界面名称(N):vboxnet0(此处需要注意,如果没有进行步骤1那么这里可能无法选择,整个设置流程会提示有错误而无法继续)
          -高级
          -控制芯片(T):xxx
          -混杂模式(P):拒绝
          -MAC地址(M):系统随机即可
          -接入网线(C):选中
          -

          -
        6. -
        7. 配置网卡2。点击网卡2->点击启动网络连接(E)

          -连接方式(A):网络地址装换(NAT)
          -界面名称(N):
          -高级(d)
          -控制芯片(T):xxx
          -混杂模式(P):拒绝
          -MAC地址(M):系统随机即可
          -接入网线(C):选中
          -

          -
        8. -
        9. 配置网络。启动操作系统,使用root用户进行网络配置。

          - -
          cd /etc/sysconfig/network-scripts/
          -cp ifcfg-eth0 ifcfg-eth1
          -

          配置ifcfg-eth0,ifcfg-eth1两个文件与virtual box 中的网卡mac值一一对应, ONBOOT=yes开机启动。并将ifcfg-eth1中的网卡名称eth0改为eth1,再重启网路服务。

          - -
          service network start
          -

          其中eth0的网关信息比较多,需要根据情况具体配置。如,这里使用eth0为host-only模式,eth1为nat模式,eth0为固定ip,eth1为开机自动获取ip。可参考如下:

          -
          -[root@master11 network-scripts]# cat ifcfg-eth0
          -DEVICE=eth0
          -HWADDR=08:00:27:55:99:EA(必须和virtual box 中的mac地址一致)
          -TYPE=Ethernet
          -UUID=dc6511c2-b5bb-4ccc-9775-84679a726db3(没有可不填)
          -ONBOOT=yes
          -NM_CONTROLLED=yes
          -BOOTPROTO=static
          -NETMASK=255.255.255.0
          -BROADCAST=192.168.56.255
          -IPADDR=192.168.56.11
          -

          [root@master11 network-scripts]# cat ifcfg-eth1 -DEVICE=eth1 -HWADDR=08:00:27:13:36:C3 -TYPE=Ethernet -UUID=b8f8485e-b731-4b64-8363-418dbe34880d -ONBOOT=yes -NM_CONTROLLED=yes -BOOTPROTO=dhcp -

          -
        10. -
        -

        4.4 检查机器名称

        -

        检查机器名称。修改后,重启生效。

        - -
           cat /etc/sysconfig/network
        -

        这里期望得机器名称信息是:

        -
        - NETWORKING=yes
        - HOSTNAME=master11
        +    <![CDATA[Hadoop MapReduce 工作机制]]>
        +    
        +    2014-03-03T22:17:00+08:00
        +    http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce--gong-zuo-ji-zhi/
        +    工作流程
        +
          +
        1. 作业配置
        2. +
        3. 作业提交
        4. +
        5. 作业初始化
        6. +
        7. 作业分配
        8. +
        9. 作业执行
        10. +
        11. 进度和状态更新
        12. +
        13. 作业完成
        14. +
        15. 错误处理
        16. +
        17. 作业调度
        18. +
        19. shule(mapreduce核心)和sort
        20. +
        +

        作业配置

        +

        相对不难理解。 具体略。

        +

        作业提交

        +

        image

        +

        首先熟悉上图,4个实例对象: client jvm、jobTracker、TaskTracker、SharedFileSystem

        +

        MapReduce 作业可以使用 JobClient.runJob(conf) 进行 job 的提交。如上图,这个执行过程主要包含了4个独立的实例。

        +
          +
        • 客户端。提交MapReduce作业。
        • +
        • jobtracker:协调作业的运行。jobtracker一个java应用程序。
        • +
        • tasktracker:运行作业划分后的任务。tasktracker一个java应用程序。
        • +
        • shared filesystem(分布式文件系统,如:HDFS)
        • +
        +

        以下是Hadoop1.x 中旧版本的 MapReduce JobClient API. org.apache.hadoop.mapred.JobClient

        +
        /** JobClient is the primary interface for the user-job to interact with the JobTracker. JobClient provides facilities to submit jobs, track their progress, access component-tasks' reports/logs, get the Map-Reduce cluster status information etc.
        +The job submission process involves:
        +Checking the input and output specifications of the job.
        +Computing the InputSplits for the job.
        +Setup the requisite accounting information for the DistributedCache of the job, if necessary.
        +Copying the job's jar and configuration to the map-reduce system directory on the distributed file-system.
        +Submitting the job to the JobTracker and optionally monitoring it's status.
        +Normally the user creates the application, describes various facets of the job via JobConf and then uses the JobClient to submit the job and monitor its progress. */ 
        +Here is an example on how to use JobClient:
        +     // Create a new JobConf
        +     JobConf job = new JobConf(new Configuration(), MyJob.class);
        +     // Specify various job-specific parameters     
        +     job.setJobName("myjob");
        +     job.setInputPath(new Path("in"));
        +     job.setOutputPath(new Path("out"));
        +     job.setMapperClass(MyJob.MyMapper.class);
        +     job.setReducerClass(MyJob.MyReducer.class);
        +     // Submit the job, then poll for progress until the job is complete
        +     JobClient.runJob(job);   
        +// JobClient.runJob(job) --> JobClient. submitJob(job) -->  submitJobInternal(job) 
        +

        新API放在 org.apache.hadoop.mapreduce.* 包下. 使用 Job 类代替 JobClient。又由job.waitForCompletion(true) 内部进行 JobClient.submitJobInternal() 封装。

        +

        新旧API请参考博文 Hadoop编程笔记(二):Hadoop新旧编程API的区别

        +

        hadoop1.x 旧 API JobClient.runJob(job) 调用submitJob() 之后,便每秒轮询作业进度monitorAndPrintJob。并将其进度、执行结果信息打印到控制台上。

        +

        接着再看看 JobClient 的 submitJob() 方法的实现基本过程。上图步骤 2,3,4.

        +
          +
        1. 向 jobtracker 请求一个新的 jobId. (JobID jobId = jobSubmitClient.getNewJobId(); void org.apache.hadoop.mapred.JobClient.init(JobConf conf) throws IOException , 集群环境下是 RPC JobSubmissionProtocol 代理。本地环境使用 LocalJobRunner。

          +
        2. +
        3. 检查作业的相关的输出路径并提交 job 以及相关的 jar 到 job tracker, 相关的 libjar 通过distributedCache 传递给 jobtracker.

          +
          submitJobInternal(… …); 
          +// -->
          +copyAndConfigureFiles(jobCopy, submitJobDir); 
          +// --> 
          +copyAndConfigureFiles(job, jobSubmitDir, replication); 
          +… 
          +// --> 
          +output.checkOutputSpecs(context);
          +
        4. +
        5. 计算作业的分片。将 SplitMetaInfo 信息写入 JobSplit。 Maptask 的个数 = 输入的文件大小除以块的大小。

          +
          int maps = writeSplits(context, submitJobDir);
          +(JobConf)jobCopy.setNumMapTasks(maps);
          +// --> 
          +maps = writeNewSplits(job, jobSubmitDir); 
          +// --> (重写,要详细)
          +JobSplitWriter.createSplitFiles(jobSubmitDir, conf,
          +    jobSubmitDir.getFileSystem(conf), array); // List<InputSplit> splits = input.getSplits(job); 
          +// -->
          +SplitMetaInfo[] info = writeNewSplits(conf, splits, out);
          +
        6. +
        7. 写JobConf信息到配置文件 job.xml。 jobCopy.writeXml(out);

          +
        8. +
        9. 准备提交job。 RPC 通讯到 JobTracker 或者 LocalJobRunner.

          +
          jobSubmitClient.submitJob(jobId, submitJobDir.toString(), jobCopy.getCredentials());
          +
        10. +
        +

        作业初始化

        +
          +
        1. 当 JobTracker 接收到了 submitJob() 方法的调用后,会把此调用放入一个内部队列中,交由作业调度器(job scheduler)进行调度。

          +
          submitJob(jobId, jobSubmitDir, null, ts, false);
          +// -->
          +jobInfo = new JobInfo(jobId, new Text(ugi.getShortUserName()),
          +      new Path(jobSubmitDir));
          +
        2. +
        3. 作业调度器并对job进行初始化。初始化包括创建一个表示正在运行作业的对象——封装任务和纪录信息,以便跟踪任务的状态和进程(步骤5)。

          +
          job = new JobInProgress(this, this.conf, jobInfo, 0, ts);
          +// -->
          +status = addJob(jobId, job);
          +// -->
          +synchronized (jobs) {
          +  synchronized (taskScheduler) {
          +    jobs.put(job.getProfile().getJobID(), job);
          +    for (JobInProgressListener listener : jobInProgressListeners) {
          +      listener.jobAdded(job);
          +    }
          +  }
          +}
          +
        4. +
        5. 创建任务列表。在 JobInProgress的 initTask()方法中

          +
        6. +
        7. 从共享文件系统中获取 JobClient 已计算好的输入分片信息(步骤6)

          +
        8. +
        9. 创建 Map 任务和 Reduce 任务,为每个 MapTask 和 ReduceTask 生成 TaskProgress 对象。

          +
        10. +
        11. 创建的 reduce 任务的数量由 JobConf 的 mapred.reduce.task 属性决定,可用 setNumReduceTasks() 方法设置,然后调度器创建相应数量的要运行的 reduce 任务。任务被分配了 id。

          +
          JobInProgress initTasks() 
          +… …
          +TaskSplitMetaInfo[] splits = createSplits(jobId); // read input splits and create a map per a split
          +// -->
          +allSplitMetaInfo[i] = new JobSplit.TaskSplitMetaInfo(splitIndex, 
          +      splitMetaInfo.getLocations(), 
          +      splitMetaInfo.getInputDataLength());
          +maps = new TaskInProgress[numMapTasks]; // 每个分片创建一个map任务
          +this.reduces = new TaskInProgress[numReduceTasks]; // 创建reduce任务
          +
        12. +
        +

        任务分配

        +

        Tasktracker 和 JobTracker 通过心跳通信分配一个任务

        +
          +
        1. TaskTracker 定期发送心跳,告知 JobTracker, tasktracker 是否还存活,并充当两者之间的消息通道。

          +
        2. +
        3. TaskTracker 主动向 JobTracker 询问是否有作业。若自己有空闲的 solt,就可在心跳阶段得到 JobTracker 发送过来的 Map 任务或 Reduce 任务。对于 map 任务和 task 任务,TaskTracker 有固定数量的任务槽,准确数量由 tasktracker 核的个数核内存的大小来确定。默认调度器在处理 reduce 任务槽之前,会填充满空闲的 map 任务槽,因此,如果 tasktracker 至少有一个空闲的 map 任务槽,tasktracker 会为它选择一个 map 任务,否则选择一个 reduce 任务。选择 map 任务时,jobTracker 会考虑数据本地化(任务运行在输入分片所在的节点),而 reduce 任务不考虑数据本地化。任务还可能是机架本地化。

          +
        4. +
        5. TaskTracker 和 JobTracker heartbeat代码

          +
          TaskTracker.transmitHeartBeat()
          +// -->
          +//
          +// Check if we should ask for a new Task
          +//
          +if (askForNewTask) {
          +  askForNewTask = enoughFreeSpace(localMinSpaceStart);
          +  long freeDiskSpace = getFreeSpace();
          +  long totVmem = getTotalVirtualMemoryOnTT();
          +  long totPmem = getTotalPhysicalMemoryOnTT();
          +  long availableVmem = getAvailableVirtualMemoryOnTT();
          +  long availablePmem = getAvailablePhysicalMemoryOnTT();
          +  long cumuCpuTime = getCumulativeCpuTimeOnTT();
          +  long cpuFreq = getCpuFrequencyOnTT();
          +  int numCpu = getNumProcessorsOnTT();
          +  float cpuUsage = getCpuUsageOnTT();
          +// -->
          +// Xmit the heartbeat
          +HeartbeatResponse heartbeatResponse = jobClient.heartbeat(status, 
          +                                                          justStarted,
          +                                                          justInited,
          +                                                          askForNewTask, 
          +                                                          heartbeatResponseId);
          +注: InterTrackerProtocol jobClient RPC 到 JobTracker.heartbeat() 
          +JobTracker.heartbeat()
          +// -->
          +// Process this heartbeat 
          +short newResponseId = (short)(responseId + 1);
          +status.setLastSeen(now);
          +if (!processHeartbeat(status, initialContact, now)) {
          +  if (prevHeartbeatResponse != null) {
          +    trackerToHeartbeatResponseMap.remove(trackerName);
          +  }
          +  return new HeartbeatResponse(newResponseId, 
          +               new TaskTrackerAction[] {new ReinitTrackerAction()});
          +}
          +
        6. +
        +

        任务执行

        +

        tasktracker 执行任务大致步骤:

        +
          +
        1. 被分配到一个任务后,从共享文件中把作业的jar复制到本地,并将程序执行需要的全部文件(配置信息、数据分片)复制到本地
        2. +
        3. 为任务新建一个本地工作目录
        4. +
        5. 内部类TaskRunner实例启动一个新的jvm运行任务
        6. +
        +

        Tasktracker.TaskRunner.startNewTask()代码

        +
        // -->
        +RunningJob rjob = localizeJob(tip);
        +// -->
        +launchTaskForJob(tip, new JobConf(rjob.getJobConf()), rjob); 
        +// -->
        +tip.launchTask(rjob);
        +// -->
        +setTaskRunner(task.createRunner(TaskTracker.this, this, rjob));
        +this.runner.start(); // MapTaskRunner 或者 ReduceTaskRunner
        +//
        +//startNewTask 方法完整代码:
        +void startNewTask(final TaskInProgress tip) throws InterruptedException {
        +    Thread launchThread = new Thread(new Runnable() {
        +      @Override
        +      public void run() {
        +        try {
        +          RunningJob rjob = localizeJob(tip);//初始化job工作目录
        +          tip.getTask().setJobFile(rjob.getLocalizedJobConf().toString());
        +          // Localization is done. Neither rjob.jobConf nor rjob.ugi can be null
        +          launchTaskForJob(tip, new JobConf(rjob.getJobConf()), rjob); // 启动taskrunner执行task
        +        } catch (Throwable e) {
        +          String msg = ("Error initializing " + tip.getTask().getTaskID() + 
        +                        ":\n" + StringUtils.stringifyException(e));
        +          LOG.warn(msg);
        +          tip.reportDiagnosticInfo(msg);
        +          try {
        +            tip.kill(true);
        +            tip.cleanup(false, true);
        +          } catch (IOException ie2) {
        +            LOG.info("Error cleaning up " + tip.getTask().getTaskID(), ie2);
        +          } catch (InterruptedException ie2) {
        +            LOG.info("Error cleaning up " + tip.getTask().getTaskID(), ie2);
        +          }
        +          if (e instanceof Error) {
        +            LOG.error("TaskLauncher error " + 
        +                StringUtils.stringifyException(e));
        +          }
        +        }
        +      }
        +    });
        +    launchThread.start();
        +  }
        +

        进度和状态更新

        +
          +
        1. 状态包括:作业或认为的状态(成功,失败,运行中)、map 和 reduce 的进度、作业计数器的值、状态消息或描述
        2. +
        3. task 运行时,将自己的状态发送给 TaskTracker,由 TaskTracker 心跳机制向 JobTracker 汇报
        4. +
        5. 状态进度由计数器实现
        6. +
        +

        如图: +image

        +

        作业完成

        +
          +
        1. jobtracker收到最后一个任务完成通知后,便把作业任务状态置为成功
        2. +
        3. 同时jobtracker,tasktracker清理作业的工作状态
        4. +
        +

        错误处理

        +

        task 失败

        +
          +
        1. map 或者 reduce 任务中的用户代码运行异常,子 jvm 在进程退出之前向其父 tasktracker 发送报告, 并打印日志。tasktracker 会将此 task attempt 标记为 failed,释放一个任务槽 slot,以运行另一个任务。streaming 任务以非零退出代码,则标记为 failed.
        2. +
        3. 子进程jvm突然退出(jvm bug)。tasktracker 注意到会将其标记为 failed。
        4. +
        5. 任务挂起。tasktracker 注意到一段时间没有收到进度的更新,便将任务标记为 failed。此 jvm 子进程将被自动杀死。任务超时时间间隔通常为10分钟,使用 mapred.task.timeout 属性进行配置。以毫秒为单位。超时设置为0表示将关闭超时判定,长时间运行不会被标记为 failed,也不会释放任务槽。
        6. +
        7. tasktracker 通过心跳将子任务标记为失败后,自身计数器减一,以便向 jobtracker 申请新的任务
        8. +
        9. jobtracker 通过心跳知道一个 task attempt 失败之后,便重新调度该任务的执行(避开将失败的任务分配给执行失败的tasktracker)。默认执行失败尝试4次,若仍没有执行成功,整个作业就执行失败。
        10. +
        +

        tasktracker 失败

        +
          +
        1. 一个 tasktracker 由于崩溃或者运行过于缓慢而失败,就会停止将 jobtracker 心跳。默认间隔可由 mapred.tasktracker.expriy.interval 设置,毫秒为单位。
        2. +
        3. 同时 jobtracker 将从等待任务调度的 tasktracker 池将此 tasktracker 移除。jobtracker 重新安排此 tasktracker 上已运行并成功完成的 map 任务重新运行。
        4. +
        5. 若 tasktracker 上面的失败任务数远远高于集群的平均失败数,tasktracker 将被列入黑名单。重启后失效。
        6. +
        +

        jobtracker失败

        +

        Hadoop jobtracker 失败是一个单点故障。作业失败。可在后续版本中启动多个 jobtracker,使用zookeeper协调控制(YARN)。

        +

        作业调度

        +
          +
        1. hadoop默认使用先进先出调度器(FIFO) +先遵循优先级优先,在按作业到来顺序调度。缺点:高优先级别的长时间运行的task占用资源,低级优先级,短作业得不到调度。
        2. +
        3. 公平调度器(FairScheduler) +目标:让每个用户公平的共享集群的能力.默认情况下,每个用户都有自己的池。支持抢占,若一个池在特定的时间内未得到公平的资源分配共享,调度器将终止运行池中得到过多资源的任务,以便将任务槽让给资源不足的池。 +详细文档参见:http://hadoop.apache.org/docs/r1.2.1/fair_scheduler.html
        4. +
        5. 容量调度器(CapacityScheduler) +支持多队列,每个队列配置一定的资源,采用FIFO调度策略。对每个用户提交的作业所占的资源进行限定。 +详细文档参见:http://hadoop.apache.org/docs/r1.2.1/capacity_scheduler.html
        6. +
        +

        shuffle和sort

        +

        mapreduce 执行排序,将 map 输出作为输入传递给 reduce 称为 shuffle。其确保每个 reduce 的输入都时按键排序。shuffle 是调优 mapreduce 重要的阶段。

        +

        mapreduce 的 shuffle 和排序如下图: +image

        +

        map端

        +
          +
        1. map端并不是简单的将中间结果输出到磁盘。而是先用缓冲的方式写到内存,并预排序。
        2. +
        3. 每个map任务都有一个环形缓冲区,用于存储任务的输出。默认100mb,由 io.sort.mb 设置。 io.sort.spill.percent 设置阀值,默认80%。
        4. +
        5. 一旦内存缓冲区到达阀值,由一个后台线程将内存中内容 spill 到磁盘中。在写磁盘前,线程会根据数据最终要传送的 reducer 数目划分成相应的分区。每一个分区中,后台线程按键进行内排序,如果有一个 combiner 它会在排序后的输出上运行。
        6. +
        7. 在任务完成之前,多个溢出写文件会被合并成一个已分区已排序的输出文件。最终成为 reduce 的输入文件。属性 io.sort.factor 控制一次最多能合并多少流(分区),默认10.
        8. +
        9. 如果已指定 combiner,并且溢出写文件次数至少为3(min.num.spills.for.combiner 属性),则 combiner 就会在输出文件写到磁盘之前运行。目的时 map 输出更紧凑,写到磁盘上的数据更少。combiner 在输入上反复运行并不影响最终结果。
        10. +
        11. 压缩 map 输出。写磁盘速度更快、节省磁盘空间、减少传给 reduce 数据量。默认不压缩。可使 mapred.compress.map.output=true 启用压缩,并指定压缩库, mapred.map.output.compression.codec。
        12. +
        13. reducer 通过HTTP方式获取输出文件的分区。由于文件分区的工作线程数量任务的 tracker.http.threads 属性控制。
        14. +
        +

        MapTask代码,内部类MapOutputBuffer.collect()方法在收集key/value到容器中,一旦满足预值,则开始溢出写文件由sortAndSpill() 执行。

        +
        // sufficient acct space
        +          kvfull = kvnext == kvstart;
        +          final boolean kvsoftlimit = ((kvnext > kvend)
        +              ? kvnext - kvend > softRecordLimit
        +              : kvend - kvnext <= kvoffsets.length - softRecordLimit);
        +          if (kvstart == kvend && kvsoftlimit) {
        +            LOG.info("Spilling map output: record full = " + kvsoftlimit);
        +            startSpill();
        +          }
        +// --> startSpill();
        + spillReady.signal(); //    private final Condition spillReady = spillLock.newCondition();
        +// --> 溢出写文件主要由内部类 SpillThread(Thread) 执行
        +    try {
        +              spillLock.unlock();
        +              sortAndSpill(); // 排序并溢出
        +            } 
        +// --> sortAndSpill()
        + // create spill file
        +        final SpillRecord spillRec = new SpillRecord(partitions);
        + // sorter = ReflectionUtils.newInstance(job.getClass("map.sort.class", QuickSort.class, IndexedSorter.class), job);
        +… …
        + sorter.sort(MapOutputBuffer.this, kvstart, endPosition, reporter);
        +// -->
        + if (combinerRunner == null) {
        +… …
        + // Note: we would like to avoid the combiner if we've fewer
        +              // than some threshold of records for a partition
        +              if (spstart != spindex) {
        +                combineCollector.setWriter(writer);
        +                RawKeyValueIterator kvIter =
        +                  new MRResultIterator(spstart, spindex);
        +                combinerRunner.combine(kvIter, combineCollector);
        +              }
        +}
        +

        reduce 端

        +
          +
        1. reduce 端 shuffle 过程分为三个阶段:复制 map 输出、排序合并、reduce 处理
        2. +
        3. reduce 可以接收多个 map 的输出。若 map 相当小,则会复制到 reduce tasktracker 的内存中(mapred.job.shuffle.input.buffer.pecent控制百分比)。一旦内存缓冲区达到阀值大小(由 mapped.iob.shuffle.merge.percent 决定)或者达到map输出阀值( mapred.inmem.merge.threshold 控制),则合并后溢出写到磁盘
        4. +
        5. map任务在不同时间完成,tasktracker 通过心跳从 jobtracker 获取 map 输出位置。并开始复制 map 输出文件。
        6. +
        7. reduce 任务由少量复制线程,可并行复制 map 输出文件。由属性 mapred.reduce.parallel.copies 控制。
        8. +
        9. reduce 阶段不会等待所有输入合并成一个大文件后在进行处理,而是把部分合并的结果直接进行处理。
        10. +
        +

        ReduceTask源代码,run()方法

        +
        // --> 3个阶段
        + if (isMapOrReduce()) {
        +      copyPhase = getProgress().addPhase("copy");
        +      sortPhase  = getProgress().addPhase("sort");
        +      reducePhase = getProgress().addPhase("reduce");
        +    }
        +// --> copy 阶段
        +if (!isLocal) {
        +      reduceCopier = new ReduceCopier(umbilical, job, reporter);
        +      if (!reduceCopier.fetchOutputs()) {
        +        if(reduceCopier.mergeThrowable instanceof FSError) {
        +          throw (FSError)reduceCopier.mergeThrowable;
        +        }
        +        throw new IOException("Task: " + getTaskID() + 
        +            " - The reduce copier failed", reduceCopier.mergeThrowable);
        +      }
        +    }
        +    copyPhase.complete();                         // copy is already complete
        +// --> sort 阶段
        +setPhase(TaskStatus.Phase.SORT);
        +    statusUpdate(umbilical);
        +    final FileSystem rfs = FileSystem.getLocal(job).getRaw();
        +    RawKeyValueIterator rIter = isLocal
        +      ? Merger.merge(job, rfs, job.getMapOutputKeyClass(),
        +          job.getMapOutputValueClass(), codec, getMapFiles(rfs, true),
        +          !conf.getKeepFailedTaskFiles(), job.getInt("io.sort.factor", 100),
        +          new Path(getTaskID().toString()), job.getOutputKeyComparator(),
        +          reporter, spilledRecordsCounter, null)
        +      : reduceCopier.createKVIterator(job, rfs, reporter);
        +    // free up the data structures
        +    mapOutputFilesOnDisk.clear();
        +    sortPhase.complete();                         // sort is complete
        +// --> reduce 阶段
        +setPhase(TaskStatus.Phase.REDUCE); 
        +    statusUpdate(umbilical);
        +    Class keyClass = job.getMapOutputKeyClass();
        +    Class valueClass = job.getMapOutputValueClass();
        +    RawComparator comparator = job.getOutputValueGroupingComparator();
        +    if (useNewApi) {
        +      runNewReducer(job, umbilical, reporter, rIter, comparator, 
        +                    keyClass, valueClass);
        +    } else {
        +      runOldReducer(job, umbilical, reporter, rIter, comparator, 
        +                    keyClass, valueClass);
        +    }
        +// --> done 执行结果
        +    done(umbilical, reporter);
        +

        有关mapreduce shuffle和sort 原理、过程和调优

        +

        hadoop作业调优参数整理及原理, MapReduce:详解Shuffle过程 介绍的非常详尽。

        +]]>
        + + + <![CDATA[Hadoop RPC]]> + + 2014-03-02T23:33:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-rpc/ + Remote Procedure Call 远程方法调用。不需要了解网络细节,某一程序即可使用该协议请求来自网络内另一台及其程序的服务。它是一个 Client/Server 的结构,提供服务的一方称为Server,消费服务的一方称为Client。

        +

        Hadoop 底层的交互都是通过 rpc 进行的。例 如:datanode 和 namenode、tasktracker 和 jobtracker、secondary namenode 和 namenode 之间的通信都是通过 rpc 实现的。

        +

        TODO: 此文未写明了。明显需要画 4张图, rpc 原理图,Hadoop rpc 时序图, 客户端 流程图,服端流程图。最好帖几个包图+ 类图(组件图)。待完善。

        +

        要实现远程过程调用,需要有3要素: +1、server 必须发布服务 +2、在 client 和 server 两端都需要有模块来处理协议和连接 +3、server 发布的服务,需要将接口给到 client

        +

        Hadoop RPC

        +
          +
        1. 序列化层。 Client 与 Server 端通讯传递的信息采用实现自 Writable 类型
        2. +
        3. 函数调用层。 Hadoop RPC 通过动态代理和 java 反射实现函数调用
        4. +
        5. 网络传输层。Hadoop RPC 采用 TCP/IP socket 机制
        6. +
        7. 服务器框架层。Hadoop RPC 采用 java NIO 事件驱动模型提高 RPC Server 吞吐量
        8. +
        +

        TODO 缺个 RPC 图

        +

        Hadoop RPC 源代码主要在org.apache.hadoop.ipc包下。org.apache.hadoop.ipc.RPC 内部包含5个内部类。

        +
          +
        • Invocation :用于封装方法名和参数,作为数据传输层,相当于VO(Value Object)。
        • +
        • ClientCache :用于存储client对象,用 socket factory 作为 hash key,存储结构为 hashMap
        • +
        • Invoker :是动态代理中的调用实现类,继承了 java.lang.reflect.InvocationHandler。
        • +
        • Server :是ipc.Server的实现类。
        • +
        • VersionMismatch : 协议版本。
        • +
        +

        从客户端开始进行通讯源代码分析

        +

        org.apache.hadoop.ipc.Client 有5个内部类

        +
          +
        • Call: A call waiting for a value.
        • +
        • Connection: Thread that reads responses and notifies callers. Each connection owns a socket connected to a remote address. Calls are multiplexed through this socket: responses may be delivered out of order.
        • +
        • ConnectionId: This class holds the address and the user ticket. The client connections to servers are uniquely identified by
        • +
        • ParallelCall: Call implementation used for parallel calls.
        • +
        • ParallelResults: Result collector for parallel calls.
        • +
        +

        客户端和服务端建立连接的大致执行过程为

        +
          +
        1. 在 Object org.apache.hadoop.ipc.RPC.Invoker.invoke(Object proxy, Method method, Object[] args) 方法中调用
          +client.call(new Invocation(method, args), remoteId);

          +
        2. +
        3. 上述的 new Invocation(method, args) 是 org.apache.hadoop.ipc.RPC 的内部类,它包含被调用的方法名称及其参数。此处主要是设置方法和参数。 client 为 org.apache.hadoop.ipc.Client 的实例对象。

          +
        4. +
        5. org.apache.hadoop.ipc.Client.call() 方法的具体源代码。在call()方法中 getConnection()内部获取一个 org.apache.hadoop.ipc.Client.Connection 对象并启动 io 流 setupIOstreams()。

          +
          Writable org.apache.hadoop.ipc.Client.call(Writable param, ConnectionId remoteId) throwsInterruptedException, IOException {
          +Call call = new Call(param); //A call waiting for a value.   
          +// Get a connection from the pool, or create a new one and add it to the
          +// pool.  Connections to a given ConnectionId are reused. 
          +Connection connection = getConnection(remoteId, call);// 主要在 org.apache.hadoop.net 包下。
          +connection.sendParam(call); //客户端发送数据过程
          +boolean interrupted = false;
          +synchronized (call) {
          +   while (!call.done) {
          +    try {
          +      call.wait();                           // wait for the result
          +    } catch (InterruptedException ie) {
          +      // save the fact that we were interrupted
          +      interrupted = true;
          +    }
          +  }
          +… …
          +}
          +}
          +// Get a connection from the pool, or create a new one and add it to the
          +// pool.  Connections to a given ConnectionId are reused. 
          +private Connection getConnection(ConnectionId remoteId,
          +                               Call call)
          +                               throws IOException, InterruptedException {
          +if (!running.get()) {
          +  // the client is stopped
          +  throw new IOException("The client is stopped");
          +}
          +Connection connection;
          +// we could avoid this allocation for each RPC by having a  
          +// connectionsId object and with set() method. We need to manage the
          +// refs for keys in HashMap properly. For now its ok.
          +do {
          +  synchronized (connections) {
          +    connection = connections.get(remoteId);
          +    if (connection == null) {
          +      connection = new Connection(remoteId);
          +      connections.put(remoteId, connection);
          +    }
          +  }
          +} while (!connection.addCall(call)); 
          +//we don't invoke the method below inside "synchronized (connections)"
          +//block above. The reason for that is if the server happens to be slow,
          +//it will take longer to establish a connection and that will slow the
          +//entire system down.
          +connection.setupIOstreams(); // 向服务段发送一个 header 并等待结果
          +return connection;
          +}
          +
        6. +
        7. setupIOstreams() 方法。

          +
          void org.apache.hadoop.ipc.Client.Connection.setupIOstreams() throws InterruptedException {
          +// Connect to the server and set up the I/O streams. It then sends
          +// a header to the server and starts
          +// the connection thread that waits for responses.
          +while (true) {
          +      setupConnection();//  建立连接
          +      InputStream inStream = NetUtils.getInputStream(socket); // 输入
          +      OutputStream outStream = NetUtils.getOutputStream(socket); // 输出
          +      writeRpcHeader(outStream);
          +      }
          +… … 
          +// update last activity time
          +  touch();
          +// start the receiver thread after the socket connection has been set up            start(); 
          +}        
          +
        8. +
        9. 启动org.apache.hadoop.ipc.Client.Connection +客户端获取服务器端放回数据过程

          +
          void org.apache.hadoop.ipc.Client.Connection.run()
          +while (waitForWork()) {//wait here for work - read or close connection
          +    receiveResponse();
          +  }
          +
        10. +
        +

        ipc.Server源码分析

        +

        ipc.Server 有6个内部类:

        +
          +
        • Call :用于存储客户端发来的请求
        • +
        • Listener : 监听类,用于监听客户端发来的请求,同时Listener内部还有一个静态类,Listener.Reader,当监听器监听到用户请求,便让Reader读取用户请求。
        • +
        • ExceptionsHandler: 异常管理
        • +
        • Responder :响应RPC请求类,请求处理完毕,由Responder发送给请求客户端。
        • +
        • Connection :连接类,真正的客户端请求读取逻辑在这个类中。
        • +
        • Handler :请求处理类,会循环阻塞读取callQueue中的call对象,并对其进行操作。
        • +
        +

        大致过程为:

        +
          +
        1. Namenode的初始化时,RPC的server对象是通过ipc.RPC类的getServer()方法获得的。

          +
          void org.apache.hadoop.hdfs.server.namenode.NameNode.initialize(Configuration conf) throwsIOException
          +// create rpc server
          +InetSocketAddress dnSocketAddr = getServiceRpcServerAddress(conf);
          +if (dnSocketAddr != null) {
          +  int serviceHandlerCount =
          +    conf.getInt(DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_KEY,
          +                DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_DEFAULT);
          +  this.serviceRpcServer = RPC.getServer(this, dnSocketAddr.getHostName(), 
          +      dnSocketAddr.getPort(), serviceHandlerCount,
          +      false, conf, namesystem.getDelegationTokenSecretManager());
          +  this.serviceRPCAddress = this.serviceRpcServer.getListenerAddress();
          +  setRpcServiceServerAddress(conf);
          +}
          +… …
          +this.server.start();  //start RPC server  
          +
        2. +
        3. 启动 server

          +
          void org.apache.hadoop.ipc.Server.start()
          +// Starts the service.  Must be called before any calls will be handled.
          +public synchronized void start() {
          +responder.start();
          +listener.start();
          +handlers = new Handler[handlerCount];
          +for (int i = 0; i < handlerCount; i++) {
          +  handlers[i] = new Handler(i);
          +  handlers[i].start(); //处理call
          +}
          +}
          +
        4. +
        5. Server处理请求, server 同样使用非阻塞 nio 以提高吞吐量

          +
          org.apache.hadoop.ipc.Server.Listener.Listener(Server) throws IOException
          +public Listener() throws IOException {
          +  address = new InetSocketAddress(bindAddress, port);
          +  // Create a new server socket and set to non blocking mode
          +  acceptChannel = ServerSocketChannel.open();
          +  acceptChannel.configureBlocking(false);
          +… … }     
          +
        6. +
        7. 真正建立连接

          +
          void org.apache.hadoop.ipc.Server.Listener.doAccept(SelectionKey key) throws IOException,OutOfMemoryError
          +

          Reader 读数据接收请求

          +
          void org.apache.hadoop.ipc.Server.Listener.doRead(SelectionKey key) throws InterruptedException
          +try {
          +    count = c.readAndProcess();
          +  } catch (InterruptedException ieo) {
          +    LOG.info(getName() + ": readAndProcess caught InterruptedException", ieo);
          +    throw ieo;
          +  }
          +
          int org.apache.hadoop.ipc.Server.Connection.readAndProcess() throws IOException,InterruptedException
          +if (!rpcHeaderRead) {
          +      //Every connection is expected to send the header.
          +      if (rpcHeaderBuffer == null) {
          +        rpcHeaderBuffer = ByteBuffer.allocate(2);
          +      }
          +      count = channelRead(channel, rpcHeaderBuffer);
          +      if (count < 0 || rpcHeaderBuffer.remaining() > 0) {
          +        return count;
          +      }
          +      int version = rpcHeaderBuffer.get(0);
          +… … 
          +processOneRpc(data.array()); // 数据处理
          +
        8. +
        9. 下面贴出Server.Connection类中的processOneRpc()方法和processData()方法的源码。

          +
          void org.apache.hadoop.ipc.Server.Connection.processOneRpc(byte[] buf) throws IOException,InterruptedException
          +private void processOneRpc(byte[] buf) throws IOException,
          +    InterruptedException {
          +  if (headerRead) {
          +    processData(buf);
          +  } else {
          +    processHeader(buf);
          +    headerRead = true;
          +    if (!authorizeConnection()) {
          +      throw new AccessControlException("Connection from " + this
          +          + " for protocol " + header.getProtocol()
          +          + " is unauthorized for user " + user);
          +    }
          +  }
          +}
          +
        10. +
        11. 处理call

          +
          void org.apache.hadoop.ipc.Server.Handler.run()
          +while (running) {
          +    try {
          +      final Call call = callQueue.take(); // pop the queue; maybe blocked here
          +      … … 
          +      CurCall.set(call);
          +      try {
          +        // Make the call as the user via Subject.doAs, thus associating
          +        // the call with the Subject
          +        if (call.connection.user == null) {
          +          value = call(call.connection.protocol, call.param, 
          +                       call.timestamp);
          +        } else {
          +… …}
          +
        12. +
        13. 返回请求

          +
        14. +
        +

        下面贴出Server.Responder类中的doRespond()方法源码:

        +
        void org.apache.hadoop.ipc.Server.Responder.doRespond(Call call) throws IOException
        +    //
        +    // Enqueue a response from the application.
        +    //
        +    void doRespond(Call call) throws IOException {
        +      synchronized (call.connection.responseQueue) {
        +        call.connection.responseQueue.addLast(call);
        +        if (call.connection.responseQueue.size() == 1) {
        +          processResponse(call.connection.responseQueue, true);
        +        }
        +      }
        +    }
        +

        补充: +notify()让因wait()进入阻塞队列里的线程(blocked状态)变为runnable,然后发出notify()动作的线程继续执行完,待其完成后,进行调度时,调用wait()的线程可能会被再次调度而进入running状态。

        +

        参考资源:

        +]]>
        +
        + + <![CDATA[Hadoop I/O]]> + + 2014-02-26T23:50:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/02/hadoop-io-1/ + HDFS 对网络IO, 磁盘IO 的操作是比较复杂且开销还比较高的。Hadoop 在设计中使用了内部的原子操作、压缩、随机读写、流式存储、数据完整性校验、序列化、基于文件的数据结构等方面进行 IO 操作。

        +

        数据完整性

        +

        保证数据在传输过程中不损坏,常见的保证数据完整性采用的技术

        +
          +
        • 奇偶校验技术
        • +
        • ECC 内存纠错校验技术
        • +
        • CRC-32 循环冗余校验技术
        • +
        +

        HDFS的数据完整性

        +

        HDFS 会对写入的所有数据计算校验和,并在读取数据时验证校验和。它针对每个由 io.bytes.per.checksum(默认512字节,开销低于1%)指定字节数据技术校验和。

        +

        DataNode 负责在验证收到的数据后存储数据及其校验和。从客户端和其它数据节点复制过来的数据。客户端写入数据并且将它发送到一个数据节点管线中,在管线的最后一个数据节点验证校验和。

        +

        客户端读取 DataNode 上的数据时,也会验证校验和。将其与 DataNode 上存储的校验和进行对比。每个 DataNode 维护一个连续的校验和验证日志,因此它知道每个数据块最后验证的时间。

        +

        每个 DataNode 还会在后台线程运行一个 DataBlockScanner(数据块检测程序),定期验证存储在数据节点上的所有块,以解决物理存储媒介上位损坏问题。

        +

        HDFS 通过复制完整的数据复本来修复损坏的数据块,进而得到一个新的、完好无损的复本。基本思路:如果客户端读取数据块时检测到错误,就向 NameNode 汇报已损坏的数据块及它试图从名称节点中要读取的 DataNode,并抛出 ChecksumException。 NameNode 将这个已损坏的数据块复本标记为已损坏,并不直接与 datanode 联系,或尝试将这个个复本复制到另一个 datanode。之后,namennode 安排这个数据块的一个复本复制到另一个 datanode。 至此,数据块复制因子恢复到期望水平。此后,并将已损坏的数据块复本删除。

        +

        LocalFileSystem

        +

        Hadoop的 LocalFileSystem 执行客户端校验。意味着,在写一个名filename的文件时,文件系统的客户端以透明的方式创建一个隐藏.filename.crc。在同一个文件夹下,包含每个文件块的校验和。

        +

        禁用校验和,使用底层文件系统原生支持校验和。这里通过 RawLocalFileSystem 来替代 LocalFileSystem 完成。要在一个应用中全局使用,只需要设置 fs.file.impl值为 org.apache.hadoop.fs.RawLocalFileSystem 来重新 map 执行文件的 URL。或者只想对某些读取禁用校验和校验。例:

        +
        Configuration conf = ...
        +FileSystem fs = new RawLocalFileSystem();
        +fs.initialize(null, conf);
        +

        ChecksumFileSystem

        +

        LocalFileSystem 继承自 ChecksumFileSystem(校验和文件系统),ChecksumFileSystem 继承自 FileSystem。ChecksumFileSystem 可以很容易的添加校验和功能到其他文件系统中。

        +

        压缩

        +

        将文件压缩有两大好处

        +
          +
        • 减少存储文件所需要的磁盘空间
        • +
        • 加速数据在网络和磁盘上的传输
        • +
        +

        编译native-hadoop

        +

        参见 《Native-hadoop 编译》

        +

        压缩算法

        +

        所有的压缩算法都需要权衡时间/空间比.压缩和解压缩速度越快,节省空间越少。gizp压缩空间/时间性能比较适中。bzip2比gzip更高效,但数度更慢; lzo 压缩速度比gzip比较快,但是压缩效率稍微低一点。

        + +

        Hadoop支持的压缩格式

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        压缩格式 工具 算法 文件扩展名 多文件 可切分
        DEFLATE DEFLATE .deflate
        GzipgzipDEFLATE.gz
        bzip2bzip2bzip2.bz
        LZOLzopLZO.lzo
        +
        +


        +编码/解码 +用以执行压缩解压算法,是否有java/原生实现

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        压缩格式 codecjava实现原生实现
        DEFLATEorg.apache.hadoop.io.compress.DefaultCodec
        gziporg.apache.hadoop.io.compress.GzipCodec
        bzip2org.apache.hadoop.io.compress.Bzip2Codec
        LZOcom.hadoop.compression.lzo.LzopCodec
        +
        +


        +压缩算法相关的 API

        +

        使用 CompressionCodecFactory.getCodec()方法来推断 CompressionCodec 具体实现。由 CompressionCodec 接口的实现对流进行进行压缩与解压缩。CodecPool 提供了重复利用压缩和解压缩的对象的机制。

        +

        … … 画个类图。## TOTO

        +

        NativeCodeLoader 加载 native-hadoop library +若想使用 snappycode 首先加载 snappy.so,再判断加载 native hadoop–>hadoop.so。native hadoop 中包含了 java 中申明的native 方法,由 native 方法去调用第三方的 natvie library。native_libraries官方参考文档

        +

        在 Hadoop core-site.xml 配置文件中可以设置是否使用本地库,默认以启用。

        +
        <property>
        +  <name>hadoop.native.lib</name>
        +  <value>true</value>
        +  <description>Should native hadoop libraries, if present, be used.</description>
        +</property>
        +

        编写使用压缩的测试程序

        +
          +
        1. 首先下载并编译 snappy,zlib
        2. +
        3. 编写 java 代码 CompressionTest.java, DeCompressionTest.java。 程序是由 maven test 进行。
        4. +
        5. 执行
          $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/hadoop/env/hadoop/lib/native/Linux-amd64-64:/usr/local/lib
          +$ mvn test -Dtest=com.kangfoo.study.hadoop1.io.CompressionTest
          +$ mvn test -Dtest=com.kangfoo.study.hadoop1.io.DeCompressionTest
          +## 结果:
          +rw-rw-r--. 1 hadoop hadoop 531859 7月  23 2013 releasenotes.html
          +-rw-rw-r--. 1 hadoop hadoop 140903 1月  22 15:13 releasenotes.html.deflate
          +-rw-rw-r--. 1 hadoop hadoop 531859 1月  22 15:22 releasenotes.html.deflate.decp
          +-rw-rw-r--. 1 hadoop hadoop 140915 1月  22 15:13 releasenotes.html.gz
          +-rw-rw-r--. 1 hadoop hadoop 531859 1月  22 15:22 releasenotes.html.gz.decp
          +-rw-rw-r--. 1 hadoop hadoop 224661 1月  22 15:13 releasenotes.html.snappy
          +-rw-rw-r--. 1 hadoop hadoop 531859 1月  22 15:22 releasenotes.html.snappy.decp
          +## 日志:
          +Running com.kangfoo.study.hadoop1.io.CompressionTest
          +2014-01-22 15:13:31,312 WARN  snappy.LoadSnappy (LoadSnappy.java:<clinit>(36)) - Snappy native library is available
          +2014-01-22 15:13:31,357 INFO  util.NativeCodeLoader (NativeCodeLoader.java:<clinit>(43)) - Loaded the native-hadoop library
          +2014-01-22 15:13:31,357 INFO  snappy.LoadSnappy (LoadSnappy.java:<clinit>(44)) - Snappy native library loaded
          +2014-01-22 15:13:31,617 INFO  zlib.ZlibFactory (ZlibFactory.java:<clinit>(47)) - Successfully loaded & initialized native-zlib library
          +
        6. +
        +

        启用压缩

        +

        出于性能考虑,使用原生的压缩库要比同时提供 java 实现的开销更小。可以修改 Hadoop core-site.xml 配置文件 io.compression.codecs 以启用压缩,前提是必须安装好对应的原生压缩库依赖,并配置正确的 Codec。

        +
          +
        • 属性名: io.compression.codecs
        • +
        • 默认值:org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.ompress.Bzip2Codec
        • +
        +

        压缩与输入分割

        +

        考虑如何压缩将由 MapReduce 处理的数据时,是否支持分割很重要。

        +

        案例假设,一个gzip压缩的文件的为1GB。HDFS 将其分为16块(64mb 块大小),其中每一数据块最为一个 map 任务输入。那么在 map 任务中,每一个分块是无法独立工作的( gzip 是使用的 DEFLATE 算法,它将数据存储在一系列的压缩块中。无法实现从数据流的任意位置读取数据,那么这些分块必须全部读取并与整个数据流进行同步才能从任意位置进行读取数据)。这样就失去了本地化的优势。一个 map 要处理其他15个分块的数据,而大多数据并不存储在当前 map 节点上。Map的任务数越少,作业的粒度就较大,运行的时间可能会更长。

        +

        具体应该选择哪种压缩形式,还要经过测试,才可以决定。大文件选择支持分割的压缩形式,目前只有 bzip2 支持分片,但没有原生库的实现。或者使用 SequenceFile, MapFile 数据格式进行小文件的合并再存储,这样可以满足分片。

        +

        在 MapReduce 中使用压缩

        +

        如果文件是压缩过的,那么在被 MapReduce 读取时,它们会被解压,根据文件扩展名选择对应的解码器。可参考 MapReduce 块压缩相关知识。

        +

        压缩 MapReduce 的作业输出

        +
          +
        1. 在作业配置中将 mapred.output.compress 属性设置为 true
        2. +
        3. 将 mapred.output.compression.codec 属性设置为自己需要使用的压缩解码/编码器的类名。
        4. +
        +

        代码示例

        +
        conf.setBoolean(“mapred.output.compress”,true)
        +Conf.setClass(“mapred.output.compression.codec”,GizpCodec.class,CompressionCodec.class);
        +

        对 Map 任务输出结果的压缩

        +

        压缩 Map 作业的中间结果以减少网络传输。

        +

        Map输出压缩属性
        +属性名称: mapred.compress.map.output
        +类型: boolean
        +默认值: false
        +描述: 对 map 任务输出是否进行压缩
        +
        +属性名称: mapred.map.output.compression.codec
        +类型: Class
        +默认值: org.apache.hadoop.io.compress.DefaultCodec
        +描述: map 输出所用的压缩 codec

        +

        代码示例

        +
        conf.setCompressMapOutput(true);
        +conf.setMapOutputCompressorClass(GzipCodec.classs)
        +


        +

        序列化和反序列化

        +

        什么是Hadoop的序列化? 序列化,将结构化对象转换为字节流,以便于在网络传输和磁盘存储的过程。反序列化,将字节流转化为结构化的对象的逆过程。可用于进程间的通讯和永久存储,数据拷贝

        +

        序列化特点:

        +
          +
        • 紧凑:可充分利用网络带宽(哈夫曼编码)
        • +
        • 快速:尽量减少序列化和反序列化的开销
        • +
        • 可扩展:通讯协议升级向下兼容
        • +
        • 互操作:支持不同语言间的通讯
        • +
        +

        Hadoop1.x 仅满足了紧凑和快速两个特性。 +java 自身提供的序列化并不精简。Java Serializaiton 是序列化图对象的通讯机制,它有序列化和反序列化的开销。 +java 序列化比较复杂,不能很精简的控制对象的读写。连接/延迟/缓冲。java 序列化不能满足: 精简,快速,可扩展,可互操作。

        +

        Hadoop1.x 使用 Writable 实现自己的序列化格式。它结构紧凑,快速。但难以用 java 以外的语言进行扩展。

        +

        Writable 接口

        +

        Writeable 接口定义了2个方法:

        +
        void write(DataOutput out) throws IOException; // 将其状态写入二进制格式的 DataOutput 流;
        +void readFields(DataInput in) throws IOException; // 从二进制格式的 DataInput 流读取其状态
        +

        画个类图 ## TODO

        +
        writable
        +writableComparable(interface WritableComparable<T> extends Writable, Comparable<T> )
        +comparator(int compare(T o1, T o2);)
        +comparable(public int compareTo(T o);)
        +rawcomparator(public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2);)
        +writablecomparator(ReflectionUtils.newInstance(keyClass, null);)
        +

        Writable 类的层次结构 +image

        +

        部分类型列举

        +
          +
        • NullWritable 是一种特殊的Writable类型,单例的, 序列化的长度是零。可以做占位符。
        • +
        • Text 是针对UTF-8序列化的Writable类。一般可等价于 java.lang.String 的 Writable。Text是可变的。
        • +
        • BytesWritable 是一个对二进制的封装,序列化为一个格式为一个用于制定后面数据字节数的整数域(4字节),后跟字节本身。它是可变的。如:
          BytesWritable b = new BytesWritable(new byte[]{2,5,127}); // 3个长度
          +byte[] bytes = serialize(b);
          +assertThat(StringUtils.byteToHexString(bytes), is("0000000302057f"));
          +
        • +
        • ObjectWritable 适用于java基本类型(String,enum,Writable,null或者这些类型组成的数组)的一个封装。
        • +
        • Writable集合。ArrayWritable和TwoDArrayWritable针对于数组和二维数组,它们中所有的元素必须是同一个类的实例。MapWritable和SortedMapWritable是针对于 Map 和 SorMap。
        • +
        +

        自定义Writable +•实现WritableComparable +•实现

        +
        write(); // 将对象转换为字节流并写入到输出流 out 中
        +readFields(); // 从输入流 in 中读取字节流并反序列化为对象
        +compareTo()方法。 // 将 this 对像与对象 O 比较
        +

        示例程序代码

        + +

        序列化框架

        +
          +
        • apache avro 旨在解决Hadoop中Writable类型的不足:缺乏语言的可移植性。
        • +
        • apache thrift 可伸缩的跨语言, 提供了 PRC 实现层。
        • +
        • Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。
        • +
        +

        参考

        + +

        基于文件的数据结构

        +

        使用 SequenceFile, MapFile 主要解决的问题是:支持分片的数据压缩格式的比较有限,对于某些应用而言,需要处理的数据格式来存储自己的格式,MapRedurce 需要更高级的容器。

        +

        SequenceFile

        +
          +
        1. 文件是以二进制键/值对形式存储的平面文件
        2. +
        3. 可以作为小文件的容器,它将小文件包装起来,以获取更高效率的存储和处理
        4. +
        5. 存储在 SequenceFile 中的 key/valu e并不一定是 Writable 类型
        6. +
        7. 可使用 append()方法在文件末位附加 key/value 对
        8. +
        +

        好处

        +
          +
        1. 支持纪录或者块压缩
        2. +
        3. 支持splittable, 可作为mapreduce输入分片
        4. +
        5. 修改简单(har是不可以修改的)
        6. +
        +

        SequenceFile 压缩

        +

        SequenceFile 文件格式内部结构与是否启用压缩有关。启用压缩又分两类:纪录压缩;数据块压缩。

        +
          +
        1. 无压缩。 默认是不启用压缩,则每个纪录就是它的纪录长度(字节数)、键长度、键和值组成。长度字段为4字节的整数。

          +
        2. +
        3. 纪录压缩。其格式与无压缩情况相同,不同在于纪录压缩的值需要通过文件头中定义的压缩codec进行压缩。键不压缩。
          +无压缩和纪录压缩的示意图: +image

          +
        4. +
        5. 块压缩。一次压缩多条纪录,比单条纪录压缩效率高。可以不断的向数据块中压缩纪录,直到字节数不小于io.seqfile.compress.blocksize属性中设置的字节数。默认1MB.每个新的块的开始处都需要插入同步标识。数据块的格式如下:首先是一个指示数据块中字节数的字段;紧跟是4个压缩字段(键长度、键、值长度、值)。块压缩示意图如下: +image

          +
        6. +
        +

        实例程序代码

        + +

        运行结果

        +
        ## 查看 sequence file
        +$ ./bin/hadoop fs -text /numbers.seq
        +## 排序
        +$ ./bin/hadoop jar ./hadoop-examples-1.2.1.jar sort -r 1 -inFormat org.apache.hadoop.mapred.SequenceFileInputFormat -outFormat org.apache.hadoop.mapred.SequenceFileOutputFormat -outKey org.apache.hadoop.io.IntWritable -outValue org.apache.hadoop.io.Text /numbers.seq sorted
        +## 查看排序后的结果(原键降序排列为从1到100升序排列)
        +$ ./bin/hadoop fs -text /user/hadoop/sorted/part-00000
        +

        博客参考

        +

        MapFile

        +

        MapFile 是已经排序的 SequenceFile,可以视为 java.util.Map 持久化形式。它已加入了搜索键的索引,可以根据 key 进行查找。它的键必须是 WritableComparable 类型的实例,值必须是 Writable 类型的实例,而 SequenceFile 无此要求。使用 MapFile.fix() 方法进行索引重建,把 SequenceFile 转换为 MapFile。

        +

        MapFile java 源代码

        +
        org.apache.hadoop.io.MapFile.Writer{ 
        +// 类的内部结构(MapFile是已经排序的SequenceFile):
        +private SequenceFile.Writer data;
        +private SequenceFile.Writer index;
        +… … 
        +}
        +org.apache.hadoop.io.MapFile.Reader{
        +// 二分法查找。一次磁盘寻址 + 一次最多顺序128(默认值等于每128下一个索引)个条目顺序扫瞄
        +public synchronized Writable get(WritableComparable key, Writable val){
        +… … 
        +}
        +

        实例程序代码

        + +

        运行结果

        +
        $ ./bin/hadoop fs -text /numbers.map/data 
        +$ ./bin/hadoop fs -text /numbers.map/index
        +

        SequenceFile合并为MapFile

        +
          +
        1. 新建SequenceFile文件
          $ ./bin/hadoop jar ./hadoop-examples-1.2.1.jar sort -r 1 -inFormat org.apache.hadoop.mapred.SequenceFileInputFormat -outFormat org.apache.hadoop.mapred.SequenceFileOutputFormat -outKey org.apache.hadoop.io.IntWritable -outValue org.apache.hadoop.io.Text /numbers.seq /numbers2.map
          +
        2. +
        3. 重命名文件夹
          $ ./bin/hadoop fs -mv /numbers2.map/part-00000 /numbers2.map/data
          +
        4. +
        5. 运行测试用例
        6. +
        7. 验证结果
          Created MapFile hdfs://master11:9000/numbers2.map with 100 entries
          +rw-r--r--   2 hadoop      supergroup       4005 2014-02-09 20:06 /numbers2.map/data
          +-rw-r--r--   3 kangfoo-mac supergroup        136 2014-02-09 20:13 /numbers2.map/index
          +
        8. +
        + +]]>
        +
        + + <![CDATA[Hadoop pipes 编译]]> + + 2014-02-26T01:01:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/02/hadoop-c-pipes--bian-yi/ + 我在编译 Hadoop Pipes 的时候,出现了些小问题。主要是我没有安装 openssl-devel。本以为安装 openssl 就差不多了,可这个就是问题的根源, 我现在是自己动手编译 pipes, 而 Hadoop 的 pipes 编译需要 openssl 的依赖,那么在编译的时候最好还是将 openssl-devel 开发支持的依赖补上比较省事。在解决问题的时发现网上向我一样的同学还是有的。在此我就贴下我编译时的部分日志。

        +
          +
        1. 在Hadoop 根目录下执行

          +
          ant -Dcompile.c++=yes examples
          +##错误
          +[exec] checking for HMAC_Init in -lssl... no
          +BUILD FAILED
          +/home/hadoop/env/hadoop-1.2.1/build.xml:2164: exec returned: 255
          +… … 
          +./configure: line 5234: exit: please: numeric argument required
          +##具体日志:
          +… … 
          + [exec] configure: error: Cannot find libssl.so ## 没有 libssl.so
          + [exec] /home/hadoop/env/hadoop-1.2.1/src/c++/pipes/configure: line 5234: exit: please: numeric argument required
          + [exec] /home/hadoop/env/hadoop-1.2.1/src/c++/pipes/configure: line 5234: exit: please: numeric argument required
          + [exec] checking for HMAC_Init in -lssl... no 
          +
        2. +
        3. 检查 ssl

          +
          $ yum info openssl
          +$ ll /usr/lib64/libssl*
          +-rwxr-xr-x. 1 root root 221568 2月  23 2013 /usr/lib64/libssl3.so
          +lrwxrwxrwx. 1 root root     16 12月  8 18:14 /usr/lib64/libssl.so.10 -> libssl.so.1.0.1e
          +-rwxr-xr-x. 1 root root 436984 12月  4 04:21 /usr/lib64/libssl.so.1.0.1e
          +## 缺个 libssl.so 的文件, 于是添加软链接:
          +sudo ln -s /usr/lib64/libssl.so.1.0.1e /usr/lib64/libssl.so
          +
        4. +
        5. 切换目录到 pipes 下再次编译

          +
          $cd /home/hadoop/env/hadoop/src/c++/pipes
          +执行
          +$ make distclean
          +$ ./configure 
          +[hadoop@master11 pipes]$ ./configure 
          +checking for a BSD-compatible install... /usr/bin/install -c
          +… … 
          +checking whether it is safe to define __EXTENSIONS__... yes
          +checking for special C compiler options needed for large files... no
          +checking for _FILE_OFFSET_BITS value needed for large files... no
          +checking pthread.h usability... yes
          +checking pthread.h presence... yes
          +checking for pthread.h... yes
          +checking for pthread_create in -lpthread... yes
          +checking for HMAC_Init in -lssl... no
          +configure: error: Cannot find libssl.so ## 还是没找到
          +./configure: line 5234: exit: please: numeric argument required
          +./configure: line 5234: exit: please: numeric argument required
          +
        6. +
        7. 安装openssl-devel, sudo yum install openssl-devel

          +
        8. +
        9. 再切换到Hadoop根目录下执行

          +
          ant -Dcompile.c++=yes examples
          +##搞定,编译通过
          +compile-examples:
          +[javac] /home/hadoop/env/hadoop-1.2.1/build.xml:742: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
          +[javac] Compiling 24 source files to /home/hadoop/env/hadoop-1.2.1/build/examples
          +[javac] 警告: [options] 未与 -source 1.6 一起设置引导类路径
          +[javac] 注: /home/hadoop/env/hadoop-1.2.1/src/examples/org/apache/hadoop/examples/MultiFileWordCount.java使用或覆盖了已过时的 API。
          +[javac] 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
          +[javac] 1 个警告
          +examples:
          +  [jar] Building jar: /home/hadoop/env/hadoop-1.2.1/build/hadoop-examples-1.2.2-SNAPSHOT.jar
          +BUILD SUCCESSFUL
          +Total time: 1 minute 11 seconds
          +
        10. +
        +]]>
        +
        + + <![CDATA[native-hadoop 编译]]> + + 2014-02-26T00:57:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/02/nativehadoop--bian-yi/ + 对我来讲编译 native hadoop 并不是很顺利。现将问题纪录在案。

        +

        主要问题

        +
          +
        1. ivy 联网获取资源并不稳定
        2. +
        3. hadoop-1.2.1/build.xml:62: Execute failed: java.io.IOException: Cannot run program “autoreconf” (in directory “/home/userxxx/hadoop/hadoop-1.2.1/src/native”): java.io.IOException: error=2, No such file or directory
        4. +
        5. [exec] configure: error: Zlib headers were not found… native-hadoop library needs zlib to build. Please install the requisite zlib development package.
        6. +
        7. 多次编译失败之后要记得执行 make distclean 清理一下。
        8. +
        9. 编译完 ant compile-native 之后,启动 hadoop 使用 http 访问 /dfshealth.jsp /jobtracker.jsp HTTP ERROR 404
        10. +
        11. 在 Linux 平台下编译 native hadoop 是不可以的,目前。错误:/hadoop-1.2.1/build.xml:694: exec returned: 1
        12. +
        +

        解决方案

        +
          +
        1. 第一个问题只能多次尝试。
        2. +
        3. 第二,第三个问题主要是是没有安装 zlib。顺便请保证 gcc c++, autoconf, automake, libtool, openssl,openssl-devel 也安装。安装 zlib 请参考 http://www.zlib.net/ 。
        4. +
        5. 第四个问题就是 基本的 make 三部曲的步骤。
        6. +
        7. 第五个问题原因是在 build native 库的同时,生成了 webapps 目录(在当前的 target 这个目录是个基本的结构,没有任何 jsp 等资源,404找不到很正常)。那么当我们编译过build之后,hadoop启动时又指向了这个目录,就导致这个错误。我们就可以直接将这个 build 文件夹删除了或者改脚本。问题搞定了。
        8. +
        9. 第六个问题,援引官方
          +Supported Platforms
          +The native hadoop library is supported on *nix platforms only. The library does not to work with Cygwin or the Mac OS X platform.
           
          -

          4.5 建立hadoop用户组和用户

          -

          新建hadoop用户组和用户(以下步骤如无特殊说明默认皆使用hadoop)

          - -
          groupadd hadoop
          -useradd hadoop -g hadoop
          -passwd hadoop
          -

          4.6 配置sudo权限

          -

          CentOS普通用户增加sudo权限的简单配置
          -查看sudo是否安装:

          - -
          rpm -qa|grep sudo
          -

          修改/etc/sudoers文件,修改命令必须为visudo才行

          - -
          visudo -f /etc/sudoers
          -

          在root ALL=(ALL) ALL 之后增加

          - -
          hadoop ALL=(ALL) ALL
          -Defaults:hadoop timestamp_timeout=-1,runaspw
          -
          -增加普通账户hadoop的sudo权限
          -timestamp_timeout=-1 只需验证一次密码,以后系统自动记忆
          -runaspw  需要root密码,如果不加默认是要输入普通账户的密码
          -

          修改普通用户的.bash_profile文件(vi /home/hadoop/.bash_profile),在PATH变量中增加 -/sbin:/usr/sbin:/usr/local/sbin:/usr/kerberos/sbin

          -

          4.7 安装java

          -

          使用hadoop用户sudo rpm -ivh jdk-7-linux-x64.rpm进行安装jdk7。 -配置环境变量参考CentOS-6.3安装配置JDK-7

          -

          4.8 同步服务

          -

          安装时间同步服务sudo yum install -y ntp -设置同步服务器 sudo ntpdate us.pool.ntp.org

          -

          4.9 关闭防火墙

          -

          hadoop使用的端口太多了,图省事,关掉。chkconfig iptables off。需要重启。

          -

          4.10 克隆

          -

          使用虚拟机进行克隆2个datanode节点。配置网卡(参见第五步)。配置主机名(参见第六步)。配置hosts,最好也包括宿主机(sudo vi /etc/hosts

          +那就老实点用 *nix platforms,就没事了。
        10. +
        +]]>
        +
        + + <![CDATA[Java JNI练习]]> + + 2014-02-26T00:55:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/02/java-jni--lian-xi/ + 在 Hadoop 中大量使用了 JNI 技术以提高性能并利用其他语言已有的成熟算法简化开发难度。如压缩算法、pipes 等。那么在具体学习 native hadoop, hadoop io 前先简单复习下相关知识。在此我主要是参见了 Oracle 官方网站 + IBM developerworks + csdn 论坛 写了个简单的 Hello World 程序。

        +

        此处主要是将我参考的资源进行了列举,已备具体深入学习参考。

        + +

        JNI 编程主要步骤

        +
          +
        1. 编写一个.java
        2. +
        3. javac *.java
        4. +
        5. javah -jni className -> *.h
        6. +
        7. 创建一个.so/.dll 动态链接库文件
        8. +
        +

        编程注意事项

        +
          +
        1. 不要直接使用从java里面传递过来的value.(在java里面的对象在本地调用前可能被jvm析构函数了)
        2. +
        3. 一旦不使用某对象或者变量,要去ReleaseXXX()。
        4. +
        5. 不要在 native code 里面去申请内存
        6. +
        7. 使用 javap -s 查看java 签名
        8. +
        +]]>
        +
        + + <![CDATA[HDFS API 练习使用]]> + + 2014-02-26T00:53:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/02/hdfs-api--lian-xi-shi-yong/ + 经过前几页博客的知识巩固,现在开始使用 Hadoop API 不是什么难事。此处不重点讲述。参考 Hadoop API 利用 FileSystem 实例对象操作 FSDataInputStream/FSDataOutputStream 基本不是问题。

        +

        HDFS API入门级别的使用

        +
          +
        1. 获取 FileSystem 对象
          +get(Configuration conf)
          +Configuration 对象封装了客户端或者服务器端的 conf/core-site.xml 配置

          +
        2. +
        3. 通过 FileSystem 对象进行文件操作
          +读数据:open()获取FSDataInputStream(它支持随机访问),
          +写数据:create()获取FSDataOutputStream

          +
        4. +
        +

        参考代码:HDFSTest.java

        +

        代码中主要利用 FileSystem 对象进行文件的 读、写、重命名、删除、文件信息获取等操作。

        +]]>
        +
        + + <![CDATA[Hadoop 机架感知]]> + + 2014-02-26T00:52:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/02/hadoop-ji-jia-gan-zhi/ + HDFS 和 Map/Reduce 的组件是能够感知机架的。

        +

        NameNode 和 JobTracker 通过调用管理员配置模块中的 API resolve 来获取集群里每个 slave 的机架id。该 API 将 slave 的 DNS 名称(或者IP地址)转换成机架id。使用哪个模块是通过配置项 topology.node.switch.mapping.impl 来指定的。模块的默认实现会调用 topology.script.file.name 配置项指定的一个的脚本/命令。 如果 topology.script.file.name 未被设置,对于所有传入的IP地址,模块会返回 /default-rack 作为机架 id。

        +

        在 Map/Reduce 部分还有一个额外的配置项 mapred.cache.task.levels ,该参数决定 cache 的级数(在网络拓扑中)。例如,如果默认值是2,会建立两级的 cache—— 一级针对主机(主机 -> 任务的映射)另一级针对机架(机架 -> 任务的映射)。

        +

        我目前没有模拟环境先纪录个参考博客 机架感知 以备后用。

        +]]>
        +
        + + <![CDATA[模拟使用 SecondaryNameNode 恢复 NameNode]]> + + 2014-02-26T00:50:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/02/shi-yong-secondenamenode-hui-fu-namenode/ + SecondaryNameNode +

        在试验前先了解下什么是 SecondaryNameNode、它的原理、检查点等知识点。再依次从开始配置 SecondaryNameNode 检查点、准备测试环境、模拟正常的 NameNode 故障,并手动启动 NameNode 并从 SecondaryNameNode 中恢复 fsimage。

        +

        :此试验思路主要借鉴于开源力量LouisT 老师 Hadoop Development 课程中的SecondaryNameNode章节。

        +

        作用

        +

        主要是为了解决namenode单点故障。不是 namenode 的备份。它周期性的合并 fsimage ( namenode 的镜像)和 editslog(或者 edits——所有对 fsimage 镜像文件操作的步骤),并推送给 namenode 以辅助恢复namenode。

        +

        SecondaryNameNode 定期合并 fsimage 和 edits 日志,将 edits 日志文件大小控制在一个限度下。因为内存需求和 NameNode 在一个数量级上,所以通常 SecondaryNameNode 和 NameNode 运行在不同的机器上。SecondaryNameNode 通过bin/start-dfs.sh 在 conf/masters 中指定的节点上启动。

        +

        在hadoop 2.x 中它的作用可以被两个节点替换:checkpoint node(于 SecondaryNameNode 作用相同), backup node( namenode 的完全备份)

        +

        原理(具体可参见《Hadoop权威指南》第10章 管理Hadoop)

        +

        edits 文件纪录了所有对 fsimage 镜像文件的写操作的步骤。文件系统客户端执行写操作时,这些操作首先会被记录到 edits 文件中。Nodename 在内存中维护文件系统的元数据;当 edits 被修改时,相关元数据也同步更新。内存中的元数据可支持客户端的读请求。

        +

        在每次执行写操作之后,且在向客户端发送成功代码之前,edits 编辑日志都需要更新和同步。当 namedone 向多个目录写数据时,只有在所有写操作执行完毕之后方可返回成功代码,以保证任何操作都不会因为机器故障而丢失。

        +

        fsimage 文件是文件系统元数据的一个永久检查点。它包含文件系统中的所有目录和文件 inode 的序列化谢谢。每个 inode 都是一个文件或目录的元数据的内部描述方式。对于文件来说,包含的信息有“复制级别”、修改时间、访问时间、访问许可、块大小、组成一个文件的块等;对于目录来说,包含的信息有修改时间、访问许可和配额元数据等信息。

        +

        fsimage 是一个大型文件,频繁执行写操作,会使系统运行极慢。并非每一写操作都会更新到 fsimage 文件。 +SecondaryNameNode 辅助 namenode,为 namenode 内存中的文件系统元数据创建检查点,并最终合并并更新 fsimage 镜像和减小 edits 文件。

        +

        SecondaryNameNode 的检查点

        +

        SecondaryNameNode 进程启动是由两个配置参数控制的。

        +
          +
        • fs.checkpoint.period,指定连续两次检查点的最大时间间隔, 默认值是1小时。
        • +
        • fs.checkpoint.size 定义了 edits 日志文件的最大值,一旦超过这个值会导致强制执行检查点(即使没到检查点的最大时间间隔)。默认值是64MB。
        • +
        +

        SecondaryNameNode 检查点的具体步骤

        +

        image

        +
          +
        1. SecondaryNameNode 请求主 namenode 停止使用 edits 文件,暂时将新的操作记录到 edits.new 文件中;
        2. +
        3. SecondaryNameNode 以 http get 复制 主 namenode 中的 fsimage, edits 文件;
        4. +
        5. SecondaryNameNode 将 fsimage 载入到内存,并逐一执行 edits 文件中的操作,创建新的fsimage.ckpt 文件;
        6. +
        7. SecondaryNameNode 以 http post 方式将新的fsimage.ckp 复制到主namenode.
        8. +
        9. 主 namenode 将 fsimage 文件替换为 fsimage.ckpt,同时将 edits.new 文件重命名为 edits。并更新 fstime 文件来记录下次检查点时间。
        10. +
        +

        SecondaryNameNode 保存最新检查点的目录与 NameNode 的目录结构相同。 所以 NameNode 可以在需要的时候读取 SecondaryNameNode上的检查点镜像。

        +

        模拟 NameNode 故障以从 SecondaryNameNode 恢复

        +

        场景假设:如果NameNode上除了最新的检查点以外,所有的其他的历史镜像和 edits 文件都丢失了,NameNode 可以引入这个最新的检查点以恢复。具体模拟步骤如下:

        +
          +
        1. 在配置参数 dfs.name.dir 指定的位置建立一个空文件夹;
        2. +
        3. 把检查点目录的位置赋值给配置参数 fs.checkpoint.dir;
        4. +
        5. 启动NameNode,并加上-importCheckpoint。
        6. +
        +

        NameNode 会从 fs.checkpoint.dir 目录读取检查点,并把它保存在 dfs.name.dir 目录下。 如果 dfs.name.dir 目录下有合法的镜像文件,NameNode 会启动失败。 NameNode 会检查fs.checkpoint.dir 目录下镜像文件的一致性,但是不会去改动它。

        +

        试验从 SecondaryNameNode 中备份恢复 NameNode

        +

        注意:此步骤执行并不能将原的数据文件系统从物理磁盘上移除,同样也不能在新格式化的 namenode 中查看旧的文件系统文件。请确定无误再试验。

        +

        试验知识准备

        +

        命令的使用方法请参考 SecondaryNameNode 命令。在试验前,可先了解些 hadoop 的默认配置 +core-site.xml-default, +hdfs-site.xml-default, +mapred-site.xml-default

        +

        SecondarynameNode 相关属性描述:

        -192.168.56.11 master11
        -192.168.56.12 slave12
        -192.168.56.14 slave14
        +属性:fs.checkpoint.dir     
        +值:${hadoop.tmp.dir}/dfs/namesecondary
        +描述:Determines where on the local filesystem the DFS secondary name node should store the temporary images to merge. If this is a comma-delimited list of directories then the image is replicated in all of the directories for redundancy.
        +fs.checkpoint.edits.dir
        +
        +属性:${fs.checkpoint.dir}     
        +值:Determines where on the local filesystem the DFS secondary name node should 
        +描述:store the temporary edits to merge. If this is a comma-delimited list of directoires then teh edits is replicated in all of the directoires for redundancy. Default value is same as fs.checkpoint.dir
        +
        +属性:fs.checkpoint.period  
        +值:3600   
        +描述:The number of seconds between two periodic checkpoints.
        +
        +属性:fs.checkpoint.size    
        +值:67108864   
        +描述:The size of the current edit log (in bytes) that triggers a periodic checkpoint even if the fs.checkpoint.period hasn't expired.
         
        -

        同步时间

        - -
        sudo ntpdate us.pool.ntp.org
        -

        重启服务service network start。可能出现错误参见device eth0 does not seem to be present, delaying initialization

        -

        4.11 配置ssh

        -
          -
        1. 单机ssh配置并回环测试 -
          ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa   
          -chmod 700 ~/.ssh
          -cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
          -chmod 600 ~/.ssh/authorized_keys
          -
          输入ssh localhost不用输入密码直接登陆,表明ssh配置成功。(若未重新权限分配,可能无法实现ssh免密码登陆的效果。浪费了我不少时间。)
        2. -
        3. 配置master和slave间的ssh -在slave12上执行 -
          scp hadoop@master11:~/.ssh/id_dsa.pub ./master_dsa.pub
          -cat master_dsa.pub >>authorized_keys
          -
          在master上执行ssh slave12若,不输入密码直接登陆,配置即通过。
        4. -
        5. 相同的步骤需要也在slave14和master11上重复一遍。
        6. -
        -

        机器环境确认无误后可以轻松的安装hadoop。

        -

        五、hadoop1.2.1 安装与配置

        -

        经过了以上步骤准备Hadoop1.2.1的环境搭建就相对容易多了。此处就仅需要解压缩安装并配置Hadoop,再验证是否正常便可大功告成。

        -

        5.1 安装

        -
          -
        1. 重启master11,准备工作环境目录 -
          cd ~/
          -mkdir env
          -
        2. -
        3. 解压Hadoop 1.2.1 tar -
          tar -zxvf hadoop-1.2.1.tar.gz -C ~/env/
          -
        4. -
        5. 建立软链接 -
          ln -s hadoop-1.2.1/ hadoop
          -
        6. -
        7. 配置环境变量(vi ~/.bashrc -
          export JAVA_HOME=/usr/java/jdk1.7.0_45
          -source ~/.bashrc
          -
        8. -
        9. 同步.bashrc
          scp ~/.bashrc hadoop@slave12:~/
          -scp ~/.bashrc hadoop@slave14:~/
          -
        10. -
        11. 创建数据文件存放路径。主要便于管理Hadoop的数据文件。 -
          cd /home/hadoop/env
          -mkdir data
          -mkdir data/tmp
          -mkdir data/name
          -mkdir data/data
          -chmod 755 data/data/
          -mkdir mapreduce
          -mkdir mapreduce/system
          -mkdir mapreduce/local
          -
          效果如下 -
          -├── env
          -│   ├── data
          -│   │   ├── data
          -│   │   ├── name
          -│   │   └── tmp
          -│   ├── hadoop -> hadoop-1.2.1/
          -│   ├── hadoop-1.2.1
          -│   │   ├── bin
          -│   │   ├── build.xml
          -│   └── mapreduce
          -│       ├── local
          -│       └── system
        12. -
        -

        -

        5.2 配置

        -
          -
        1. 配置 conf/core-site.xml -指定hdfs协议下的存储和临时目录

          - +

          试验环境配置

          +
            +
          1. 首先修改 core-site.xml 文件中的配置,主要是调小了 checkpoint 的周期并指定 SSN 的目录。

            <property>
            -  <name>fs.default.name</name>
            -  <value>hdfs://master11:9000</value>
            +<name>fs.checkpoint.period</name>
            +<value>120</value>
             </property>
             <property>
            -  <name>hadoop.tmp.dir</name>
            -  <value>/home/${user.name}/env/data/tmp</value>
            +<name>fs.checkpoint.dir</name>
            +<value>/home/${user.name}/env/data/snn</value>
             </property>
            -
          2. -
          3. 配置 conf/hdfs-site.xml -配置关于hdfs相关的配置。这里将原有默认复制3个副本调整为2个。学习时可根据需求适当调整。

            - +

            vi hdfs-site.xml 查看 NameNode 数据文件存储路径

            <property>
              <name>dfs.name.dir</name>
              <value>/home/${user.name}/env/data/name</value>
            @@ -506,284 +2266,541 @@ mkdir mapreduce/local
              <name>dfs.data.dir</name>
              <value>/home/${user.name}/env/data/data</value>
             </property>
            -<property>
            - <name>dfs.replication</name>
            -  <value>2</value>
            -</property>
            -<property>
            - <name>dfs.web.ugi</name>
            - <value>hadoop,supergroup</value>
            - <final>true</final>
            - <description>The user account used by the web interface. Syntax: USERNAME,GROUP1,GROUP2, ……</description>
            -</property>
             
          4. -
          5. 配置 conf/mapred-site.xml

            - -
            <property>
            - <name>mapred.job.tracker</name>
            - <value>master11:9001</value>
            -</property>
            -<property>
            - <name>mapred.system.dir</name>
            - <value>/home/${user.name}/env/mapreduce/system</value>
            -</property>
            -<property>
            - <name>mapred.local.dir</name>
            - <value>/home/${user.name}/env/mapreduce/local</value>
            -</property>
            +
          6. 再次,format namenode 。 ./bin/hadoop namenode -format。查看当前的 master namenode namespaceID cat ./name/current/VERSION

            +
            #Tue Jan 21 15:14:40 CST 2014
            +namespaceID=1816120670 ## 文件系统的唯一标识符
            +cTime=0 ## namenode的创建时间,刚格式化为0,升级之后为时间戳
            +storageType=NAME_NODE ## 存储类型
            +layoutVersion=-41 ## 负的整数。描述了hdfs永久性数据结构的版本。与Hadoop的版本无关。与升级有关。
            +
          7. +
          8. 查看 datanode 下的version。cat data/current/VERSION

            +
            #Tue Jan 21 09:51:42 CST 2014
            +namespaceID=80003531
            +storageID=DS-949100596-192.168.56.12-50010-1387691685116
            +cTime=0
            +storageType=DATA_NODE
            +layoutVersion=-41
            +

            若 namespaceID 不相同,请将 datanode 中的id修改为 namenode 相同的 namespaceID。 +同样的步骤修改其他的 datanode. +若是第一次format可以跳过此步骤。此步骤要注意避免如下错误(Incompatible namespaceID):

            +
            2014-01-21 15:07:54,890 ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: java.io.IOException: Incompatible namespaceIDs in /home/hadoop/env/data/data: namenode namespaceID = 2020545490; datanode namespaceID = 80003531
            +
          9. +
          +

          查看 NameNode 试验前正常环境状况

          +
            +
          1. 启动hdfs./bin/start-dfs.sh

            +
          2. +
          3. jps 检查所有的进程(当前NameNode进程正常)

            +
            5832 SecondaryNameNode
            +6293 Jps
            +5681 NameNode
            +2212 DataNode
            +2198 DataNode
             
          4. -
          5. 配置masters

            - -
            vi masters
            -

            写为

            - -
            master11
            +
          6. 创建测试数据

            +
            $ ./bin/hadoop fs -mkdir /test
            +$ ./bin/hadoop fs -lsr /
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-21 15:21 /test
            +[hadoop@master11 hadoop]$ ./bin/hadoop fs -put ivy.xml /test
            +[hadoop@master11 hadoop]$ ./bin/hadoop fs -lsr /
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-21 15:22 /test
            +-rw-r--r--   2 hadoop supergroup      10525 2014-01-21 15:22 /test/ivy.xml
            +
          7. +
          8. 查看 SecondaryNameNode 文件目录

            +
            watch ls ./data/snn/ 
            +current
            +image
            +in_use.l
             
          9. -
          10. 配置slaves

            - -
            vi slaves
            -

            写为

            - -
            slave12
            -slave14
            +
          11. namenode 对应日志

            +
            2014-01-21 15:54:02,654 INFO org.apache.hadoop.hdfs.server.namenode.FSNamesystem: Roll Edit Log from 192.168.56.11
            +2014-01-21 15:54:02,654 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: Number of transactions: 0 Total time for transactions(ms): 0 Number of transactions batched in Syncs: 0 Number of syncs: 0 SyncTimes(ms): 0
            +2014-01-21 15:54:02,655 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: closing edit log: position=4, editlog=/home/hadoop/env/data/name/current/edits
            +2014-01-21 15:54:02,655 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: close success: truncate to 4, editlog=/home/hadoop/env/data/name/current/edits
            +2014-01-21 15:54:02,778 INFO org.apache.hadoop.hdfs.server.namenode.TransferFsImage: Opening connection to http://0.0.0.0:50090/getimage?getimage=1
            +2014-01-21 15:54:02,781 INFO org.apache.hadoop.hdfs.server.namenode.GetImageServlet: Downloaded new fsimage with checksum: 4a75545e83f108e21ef321fb0066ede4
            +2014-01-21 15:54:02,781 INFO org.apache.hadoop.hdfs.server.namenode.FSNamesystem: Roll FSImage from 192.168.56.11
            +2014-01-21 15:54:02,781 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: Number of transactions: 0 Total time for transactions(ms): 0 Number of transactions batched in Syncs: 0 Number of syncs: 1 SyncTimes(ms): 56
            +2014-01-21 15:54:02,784 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: closing edit log: position=4, editlog=/home/hadoop/env/data/name/current/edits.new
            +2014-01-21 15:54:02,784 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: close success: truncate to 4, editlog=/home/hadoop/env/data/name/current/edits.new
             
          12. -
          13. 同步hadoop到子节点

            - -
            scp -r ~/env/ hadoop@slave12:~/
            -scp -r ~/env/ hadoop@slave14:~/
            +
          14. namenode 文件目录

            +
            $ cd name/
            +$ tree
            +.
            +├── current
            +│   ├── edits
            +│   ├── fsimage
            +│   ├── fstime
            +│   └── VERSION
            +├── image
            +│   └── fsimage
            +├── in_use.lock
            +└── previous.checkpoint
            +├── edits
            +├── fsimage
            +├── fstime
            +└── VERSION
             
          -

          5.3 启动hadoop

          +

          模拟 NameNode 故障

            -
          1. 在主节点上格式化namenode

            - -
            ./bin/hadoop namenode -format
            -
          2. -
          3. 在主节点上启动Hadoop

            - -
            ./bin/start-all.sh
            +
          4. 人为的杀掉 namenode 进程
            kill -9 6690 ## 6690 NameNode
            +删除 namenode 元数据
            +$ rm -rf ./data/name/*
            +删除 Secondary NameNode in_use.lock 文件 
            +$ rm -rf ./snn/in_use.lock
             
          -

          5.4 检查运行状态

          +

          从 SecondaryNameNode 中恢复 NameNode

            -
          1. 通过web查看Hadoop状态

            -
            -http://192.168.56.11:50030/jobtracker.jsp
            -http://192.168.56.11:50070/dfshealth.jsp
            -
            -
          2. -
          3. 验证Hadoop mapredurce -执行hadoop jar hadoop-xx-examples.jar 验证jobtracker和tasktracker

            - -
            ./bin/hadoop jar hadoop-0.16.0-examples.jar wordcount input output
            -

            可wordcount参考Hadoop集群(第6期)_WordCount运行详解

            +
          4. 启动以 importCheckpoint 方式启动 NameNode。$ ./bin/hadoop namenode -importCheckpoint

          5. +
          6. 验证是否恢复成功

            +
            ## HDFS 文件系统正常
            +$ ./bin/hadoop fsck /
            +The filesystem under path '/' is HEALTHY
            +$ ./bin/hadoop fs -lsr /
            +## 元文件信息已恢复
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-21 15:22 /test
            +-rw-r--r--   2 hadoop supergroup      10525 2014-01-21 15:22 /test/ivy.xml
            +$ tree
            +.
            +├── data
            +├── name(已恢复)
            +│   ├── current
            +│   │   ├── edits
            +│   │   ├── fsimage
            +│   │   ├── fstime
            +│   │   └── VERSION
            +│   ├── image
            +│   │   └── fsimage
            +│   ├── in_use.lock
            +│   └── previous.checkpoint
            +│       ├── edits
            +│       ├── fsimage
            +│       ├── fstime
            +│       └── VERSION
            +├── snn
            +│   ├── current
            +│   │   ├── edits
            +│   │   ├── fsimage
            +│   │   ├── fstime
            +│   │   └── VERSION
            +│   ├── image
            +│   │   └── fsimage
            +│   └── in_use.lock
            +└── tmp
            +9 directories, 16 files
            +
          7. +
          8. 查看恢复日志信息(截取部分信息)

            +
            ## copy fsimage
            +14/01/21 16:57:52 INFO common.Storage: Storage directory /home/hadoop/env/data/name is not formatted.
            +14/01/21 16:57:52 INFO common.Storage: Formatting ...
            +14/01/21 16:57:52 INFO common.Storage: Start loading image file /home/hadoop/env/data/snn/current/fsimage
            +14/01/21 16:57:52 INFO common.Storage: Number of files = 3
            +14/01/21 16:57:52 INFO common.Storage: Number of files under construction = 0
            +14/01/21 16:57:52 INFO common.Storage: Image file /home/hadoop/env/data/snn/current/fsimage of size 274 bytes loaded in 0 seconds.
            +##copy edits
            +4/01/21 16:57:52 INFO namenode.FSEditLog: Start loading edits file /home/hadoop/env/data/snn/current/edits
            +14/01/21 16:57:52 INFO namenode.FSEditLog: EOF of /home/hadoop/env/data/snn/current/edits, reached end of edit log Number of transactions found: 0.  Bytes read: 4
            +14/01/21 16:57:52 INFO namenode.FSEditLog: Start checking end of edit log (/home/hadoop/env/data/snn/current/edits) ...
            +14/01/21 16:57:52 INFO namenode.FSEditLog: Checked the bytes after the end of edit log (/home/hadoop/env/data/snn/current/edits):
            +14/01/21 16:57:52 INFO namenode.FSEditLog:   Padding position  = -1 (-1 means padding not found)
            +14/01/21 16:57:52 INFO namenode.FSEditLog:   Edit log length   = 4
            +14/01/21 16:57:52 INFO namenode.FSEditLog:   Read length       = 4
            +14/01/21 16:57:52 INFO namenode.FSEditLog:   Corruption length = 0
            +14/01/21 16:57:52 INFO namenode.FSEditLog:   Toleration length = 0 (= dfs.namenode.edits.toleration.length)
            +14/01/21 16:57:52 INFO namenode.FSEditLog: Summary: |---------- Read=4 ----------|-- Corrupt=0 --|-- Pad=0 --|
            +14/01/21 16:57:52 INFO namenode.FSEditLog: Edits file /home/hadoop/env/data/snn/current/edits of size 4 edits # 0 loaded in 0 seconds.
            +14/01/21 16:57:52 INFO common.Storage: Image file /home/hadoop/env/data/name/current/fsimage of size 274 bytes saved in 0 seconds.
            +14/01/21 16:57:54 INFO namenode.FSEditLog: closing edit log: position=4, editlog=/home/hadoop/env/data/name/current/edits
            +14/01/21 16:57:54 INFO namenode.FSEditLog: close success: truncate to 4, editlog=/home/hadoop/env/data/name/current/edits
            +14/01/21 16:57:54 INFO namenode.FSEditLog: Number of transactions: 0 Total time for transactions(ms): 0 Number of transactions batched in Syncs: 0 Number of syncs: 0 SyncTimes(ms): 0 
            +## 恢复 fsimage
            +14/01/21 16:57:54 INFO namenode.FSNamesystem: Finished loading FSImage in 1971 msecs
            +14/01/21 16:57:54 INFO hdfs.StateChange: STATE* Safe mode ON
            +... ...
            +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Safe mode termination scan for invalid, over- and under-replicated blocks completed in 15 msec
            +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Leaving safe mode after 33 secs
            +## 离开安全模式 Safe mode is OFF
            +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Safe mode is OFF
            +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Network topology has 1 racks and 2 datanodes
            +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* UnderReplicatedBlocks has 0 blocks
            +
          -

          六、常见错误

          +]]> + + + <![CDATA[Hadoop 分布式文件系统]]> + + 2014-02-25T22:46:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/02/hadoop-hdfs-1/ + 管理网络跨多台计算机存储的文件系统称为分布式文件系统。当数据的大小超过单台物理计算机存储能力,就需要对它进行分区存储。Hadoop提供了一个综合性的文件系统抽象, Hadoop Distributed FileSystem 简称 HDFS或者DFS,Hadoop 分布式文件系统。它是Hadoop 3大组件之一。其他两大组件为 Hadoop-common 和 Hadoop-mapreduce。

          +

          传统以文件为基本单位的存储缺点:首先它很难实现并行化处理某个文件。单个节点一次只能处理一个文件,无法同时处理其他文件;再者,文件大小不同很难实现负载均衡。

          +

          HDFS的设计

            -
          • expected: rwxr-xr-x, while actual: rwxrwxr-x -WARN org.apache.hadoop.hdfs.server.datanode.DataNode: Invalid directory in dfs.data.dir: Incorrect permission for /home/hadoop/env/data/data, expected: rwxr-xr-x, while actual: rwxrwxr-x
            -解决方案:chmod 755 /home/hadoop/env/data/data

            -
          • -
          • 节点之间不能通信 -java.io.IOException: File xxx/jobtracker.info could only be replicated to 0 nodes, instead of 1
            -java.net.NoRouteToHostException: No route to host
            -解决方案:关闭iptables,sudo /etc/init.d/iptables stop

            -
          • -
          • got exception trying to get groups for user webuser

            -
            -org.apache.hadoop.util.Shell$ExitCodeException: id: webuser:无此用户
            -     at org.apache.hadoop.util.Shell.runCommand(Shell.java:255)
            -     at org.apache.hadoop.util.Shell.run(Shell.java:182)
            -
            -

            解决方案: -在hdfs-site.xml文件中添加

            - -
            <property>
            - <name>dfs.web.ugi</name>
            - <value>hadoop,supergroup</value>
            - <final>true</final>
            - <description>The user account used by the web interface.Syntax: USERNAME,GROUP1,GROUP2, ……
            - </description>
            -</property>
            -

            加上这个配置。可以解决了

            -
            -value  第一个为你自己搭建hadoop的用户名,第二个为用户所属组因为默认  
            -
            -

            web访问授权是webuser用户。访问的时候。我们一般用户名不是webuser所有要覆盖掉默认的webuser

            -
          • +
          • HDFS以流式数据访问模式来存储超大文件,部署运行于廉价的机器上。
          • +
          • 可存储超大文件;流式访问,一次写入,多次读取;商用廉价PC,并不需要高昂的高可用的硬件。
          • +
          • 但不适用于,低时间延迟的访问;大小文件处理(浪费namenode内存,浪费磁盘空间。);多用户写入,任意修改文件(不支持并发写入。 同一时刻只能一个进程写入,不支持随机修改。)。
          -

          七、附录

          +

          数据块

          +

          块是磁盘进行数据读写的最小单位,默认是512字节,构建单个磁盘之上的文件系统通过磁盘块来管理文件系统的来管理该文件系统中的块。HDFS的块默认是64MB,HDFS上的文件也被划分为块大小的多个分块(chunk),作为独立的存储单元。HDFS块默认64MB的好处是为了简化磁盘寻址的开销。

          +

          HDFS块的抽象好处

            -
          • 无意中Google到的一个hadoop
          • -
          • 参考博文:hadoop学习之hadoop完全分布式集群安装 图文并茂
          • -
          • 参考博文:用 Hadoop 进行分布式并行编程, 第 1 部分 理论与实践相结合
          • -
          • 参考博文:centos安装
          • +
          • 一个文件的大小,可以大于网络中任意一个硬盘的大小。文件的块并不需要存储在同一个硬盘上可以存储在分布式文件系统集群中任意一个硬盘上。
          • +
          • 大大简化系统设计。这点对于故障种类繁多的分布式系统来说尤为重要。以块为单位,不仅简化存储管理(块大小是固定的,很容易计算一个硬盘放多少个块);而且,消除了元数据的顾虑(因为Block仅仅是存储的一块数据,其文件的元数据,例如权限等就不需要跟数据块一起存储,可以交由另外的其他系来处理)。适合批处理。支持离线的批量数据处理,支持高吞吐量。
          • +
          • 块更适合于数据备份。进而提供数据容错能力和系统可用性(将每个块复制至少几个独立的机器上,可以确保在发生块、磁盘或机器故障后数据不丢失。一旦发生某个块不可用,系统将从其他地方复制一份复本。以保证复本的数量恢复到正常水平)。容错性高,容易实现负载均衡。
          -]]>
          -
          - - <![CDATA[编译hadoop 1.2.1 hadoop-eclipse-plugin插件]]> - - 2013-12-09T22:52:00+08:00 - http://kangfoo.u.qiniudn.com//article/2013/12/hadoop-eclipse-plugin-1.2.1/ - 编译hadoop1.x.x版本的eclipse插件为何如此繁琐?

          -

          个人理解,ant的初衷是打造一个本地化工具,而编译hadoop插件的资源间的依赖超出了这一目标。导致我们在使用ant编译的时候需要手工去修改配置。那么自然少不了设置环境变量、设置classpath、添加依赖、设置主函数、javac、jar清单文件编写、验证、部署等步骤。

          -

          那么我们开始动手

          -

          主要步骤如下

          -
            -
          • 设置环境变量
          • -
          • 设置ant初始参数
          • -
          • 调整java编译参数
          • -
          • 设置java classpath
          • -
          • 添加依赖
          • -
          • 修改META-INF文件
          • -
          • 编译打包、部署、验证
          • -
          -

          具体操作

          -
            -
          1. 设置语言环境

            - -
            $ export LC_ALL=en
            -
          2. -
          3. 设置ant初始参数
            -修改build-contrib.xml文件

            - -
            $ cd /hadoop-1.2.1/src/contrib
            -$ vi build-contrib.xml
            -

            编辑并修改hadoop.root值为实际hadoop解压的根目录

            - -
            <property name="hadoop.root" location="/Users/kangfoo-mac/study/hadoop-1.2.1"/>
            -

            添加eclipse依赖

            - -
            <property name="eclipse.home" location="/Users/kangfoo-mac/work/soft/eclipse-standard-kepler-SR1-macosx-cocoa" />
            -

            设置版本号

            - -
            <property name="version" value="1.2.1"/>
            -
          4. -
          5. 调整java编译设置
            -启用javac.deprecation

            - -
            $ cd /hadoop-1.2.1/src/contrib
            -$ vi build-contrib.xml
            -


            - -
            <property name="javac.deprecation" value="off"/>
            -

            改为

            - -
            <property name="javac.deprecation" value="on"/>
            -
          6. -
          7. ant 1.8+ 版本需要额外的设置javac includeantruntime=“on” 参数

            - -
            <!-- ====================================================== -->
            -<!-- Compile a Hadoop contrib's files                       -->
            -<!-- ====================================================== -->
            -<target name="compile" depends="init, ivy-retrieve-common" unless="skip.contrib">
            -<echo message="contrib: ${name}"/>
            -<javac
            - encoding="${build.encoding}"
            - srcdir="${src.dir}"
            - includes="**/*.java"
            - destdir="${build.classes}"
            - debug="${javac.debug}"
            - deprecation="${javac.deprecation}"
            - includeantruntime="on">
            - <classpath refid="contrib-classpath"/>
            -</javac>
            -</target> 
            -
          8. -
          9. 修改编译hadoop插件 classpath

            - -
            $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
            -$ vi build.xml
            -

            添加 文件路径 hadoop-jars

            - -
            <path id="hadoop-jars">
            -  <fileset dir="${hadoop.root}/">
            -    <include name="hadoop-*.jar"/>
            -  </fileset>
            -</path>
            -

            将hadoop-jars 添加到classpath

            - -
            <path id="classpath">
            -  <pathelement location="${build.classes}"/>
            -  <pathelement location="${hadoop.root}/build/classes"/>
            -  <path refid="eclipse-sdk-jars"/>
            -  <path refid="hadoop-jars"/>
            -</path> 
            -
          10. -
          11. 修改或添加额外的jar依赖
            -因为我们根本都没有直接编译过hadoop,所以就直接使用${HADOOP_HOME}/lib下的资源.需要注意,这里将依赖jar的版本后缀去掉了。
            -同样还是在hadoop-1.2.1/src/contrib/eclipse-plugin/build.xml文件中修改或添加

            - -
            $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
            -$ vi build.xml
            -

            找到 <!-- Override jar target to specify manifest --> 修改target name为 jar 中的 copy file 的路径,具体如下:

            - -
            <copy file="${hadoop.root}/hadoop-core-${version}.jar" tofile="${build.dir}/lib/hadoop-core.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/commons-cli-${commons-cli.version}.jar"  tofile="${build.dir}/lib/commons-cli.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/commons-configuration-1.6.jar"  tofile="${build.dir}/lib/commons-configuration.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/commons-httpclient-3.0.1.jar"  tofile="${build.dir}/lib/commons-httpclient.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/commons-lang-2.4.jar"  tofile="${build.dir}/lib/commons-lang.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/jackson-core-asl-1.8.8.jar"  tofile="${build.dir}/lib/jackson-core-asl.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/jackson-mapper-asl-1.8.8.jar"  tofile="${build.dir}/lib/jackson-mapper-asl.jar" verbose="true"/>
            -
          12. -
          13. 修改 jar 清单文件

            - -
            cd ./hadoop-1.2.1/src/contrib/eclipse-plugin/META-INF
            -vi MANIFEST.MF
            -

            找到这个文件的Bundle-ClassPath这一行,然后,修改成

            - -
            Bundle-ClassPath: classes/,lib/commons-cli.jar,lib/commons-httpclient.jar,lib/hadoop-core.jar,lib/jackson-mapper-asl.jar,lib/commons-configuration.jar,lib/commons-lang.jar,lib/jackson-core-asl.jar
            -

            请保证上述字符占用一行,或者满足osgi bundle 配置文件的换行标准语法也行的。省事就直接写成一行,搞定。

            -
          14. -
          15. 新建直接打包并部署jar到eclipse/plugin目录的target

            - -
            cd hadoop-1.2.1/src/contrib/eclipse-plugin
            -vi build.xml
            -

            添加target直接将编译的插件拷贝到eclipse插件目录

            - -
            <target name="deploy" depends="jar" unless="skip.contrib"> 
            -<copy file="${build.dir}/hadoop-${name}-${version}.jar" todir="${eclipse.home}/plugins" verbose="true"/> </target>
            -

            将ant默认target default=“java"改为default=“deploy”

            - -
            <project default="deploy" name="eclipse-plugin">
            -
          16. -
          17. 编译并启动eclipse验证插件

            - -
            ant -f ./hadoop-1.2.1/src/contrib/eclipse-plugin/build.xml
            -

            启动eclipse,新建Map/Reduce Project,配置hadoop location.验证插件完全分布式的插件配置截图和core-site.xml端口配置

            -
          18. -
          19. 效果图 -image

            -
          20. -
          -

          image

          -

          相关源文件

          - -

          在此非常感谢kinuxroot这位博主的的博文参考。

          -]]>
          -
          - - <![CDATA[Mac java乱码 maven OOM 异常]]> - - 2013-12-05T23:55:00+08:00 - http://kangfoo.u.qiniudn.com//article/2013/12/ping-guo-dian-nao-mavn-luan-ma-he-java-oom--yi-chang/ - 在中文环境下苹果电脑中的 java,javac 默认以GBK编码输出信息到控制台,终端terminal默认以UTF-8编码,就出现了编码错误。

          -

          可如下2种方式修改:

          +

          Namenode 和 Datanode

          +

          HDFS采用master/slave架构。一个HDFS集群是由一个Namenode和一定数目的 Datanodes 组成。Namenode是一个中心服务器,负责管理文件系统的名字空间(namespace)以及客户端对文件的访问。集群中的Datanode一般是一个节点一个,负责管理它所在节点上的存储。HDFS暴露了文件系统的名字空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组Datanode上。Namenode执行文件系统的namespace操作。比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的 映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。

          +

          NameNode上维护文件系统树及整棵树内所有的文件和目录,并永久保存在本地磁盘,fsimage和editslog。

          +

          NameNode 将对文件系统的改动追加保存到本地文件系统上的一个日志文件(edits)。当一个 NameNode 启动时,它首先从一个映像文件(fsimage)中读取 HDFS 的状态,接着应用日志文件中的 edits 操作。然后它将新的 HDFS 状态写入(fsimage)中,并使用一个空的 edits 文件开始正常操作。因为 NameNode 只有在启动阶段才合并 fsimage 和 edits,所以久而久之日志文件可能会变得非常庞大,特别是对大型的集群。日志文件太大的副作用是下一次 NameNode 启动会花很长时间。

          +

          Hadoop HDFS 架构图

          +

          image

          +

          在上图中NameNode是Master上的进程,复制控制底层文件的io操作,处理mapreduce任务等。 +DataNode运行在slave机器上,负责实际的地层文件io读写。由NameNode存储管理文件系统的命名空间。

          +

          客户端代表用户通过与NameNode和DataNode交互来访问整个文件系统。

          +

          HDFS 读过程

          +

          image

            -
          1. 更改系统语言环境
            export LC_ALL=en 或者 export LANG=zh_CN.UTF-8
            +
          2. 客户端通过调用 FileSystem 对象的 open() 方法来打开要读取的文件。(步骤 1)在HDFS中是个 DistributedFileSystem 中的一个实例对象。
          3. +
          4. (步骤 2)DistributedFileSystem 通过PRC[需要提供一个外链接介绍RPC技术]调用 namenode ,获取文件起始块的位置。对于每个块,namenode 返回存有该块复本的 datanode 地址。Datanode 根据它们于该客户端的距离排序,如果该客户端就是一个 datanode 并保存有该数据块的一个复本,该节点就直接从本地 datanode 中读取数据。反之取网路拓扑最短路径。
          5. +
          6. DistributedFileSystem 返回 FSDataInputStream 给客户端,用来读取数据。FSDataInputStream 类封装 DFSInputStream 对象。由 DFSInputStream 负责管理 DataNode 和 NameNode 的 I/O。
          7. +
          8. (步骤 3)客户端调用 stream 的 read() 函数开始读取数据。
          9. +
          10. 存储着文件起始块的 DataNode 地址的 DFSInputStream 随即连接距离最近的 DataNode。反复 read() 方法,将数据从 datanode 传输到客户端。(网络拓扑与Hadoop[机架感知] ? 连接待补。)
          11. +
          12. 到达块的末端时,DFSInputStream 关闭和此数据节点的连接,然后连接此文件下一个数据块的最佳DataNode。
          13. +
          14. 客户端读取数据时,块也是按照打开 DFSInputStream 与 datanode 建立连接的顺序读取的。以通过询问NameNode 来检索下一批所需块的 datanode。当客户端读取完毕数据的时候,调用 FSDataInputStream 的 close() 函数。
          15. +
          16. 在读取数据的过程中,如果客户端在与数据节点通信出现错误,则尝试连接包含此数据块的下一个数据节点。失败的数据节点将被记录,以后不再连接。
          17. +
          +
          源代码理解
          +
            +
          1. 在客户端执行 class DistributedFileSystem open() 方法(装饰模式),打开文件并返回 DFSInputStream。

            +
            // DistributedFileSystem extends FileSystem --> 调用 open() 方法
            +public FSDataInputStream open(Path f, int bufferSize) throws IOException {
            +    statistics.incrementReadOps(1);
            +    return new DFSClient.DFSDataInputStream(
            +        dfs.open(getPathName(f), bufferSize, verifyChecksum, statistics));
            +}
            +
            // dfs 是 DFSClient 的实例对象。
            +public DFSInputStream open(String src, int buffersize, boolean verifyChecksum,
            +               FileSystem.Statistics stats
            +) throws IOException {
            +    checkOpen(); 
            +    //    Get block info from namenode
            +    return new DFSInputStream(src, buffersize, verifyChecksum);
            +}
            +
            // DFSInputStream 是 DFSClient 的内部类,继承自 FSInputStream。
            +// 调用的构造函数(具体略)中调用了 openInfo() 方法。在 openInfo() 中 重要的是 fetchLocatedBlocks() 向 NameNode 询问所需要的数据的元信息,通过 callGetBlockLocations() 实现。 此过程若没有找到将尝试3次。 
            +//
            +// 由 callGetBlockLocations()通过 RPC 方式询问 NameNode 获取到 LocatedBlocks 信息。
            +static LocatedBlocks callGetBlockLocations(ClientProtocol namenode,
            +  String src, long start, long length) throws IOException {
            +… … 
            +    return namenode.getBlockLocations(src, start, length);
            +… … 
            +}    
            +
            // 此处的 namenode 是通过代理模式创建的。它是 namenode  ClientProtocol 的实现(interface ClientProtocol extends VersionedProtocol)。
            +private static ClientProtocol createNamenode(ClientProtocol rpcNamenode,
            +Configuration conf) throws IOException {
            +… … 
            +    final ClientProtocol cp = (ClientProtocol)RetryProxy.create(ClientProtocol.class, rpcNamenode, defaultPolicy, methodNameToPolicyMap);
            +    RPC.checkVersion(ClientProtocol.class, ClientProtocol.versionID, cp);
            +… … 
            +}  
            +
          2. +
          3. 在 class NameNode 端获取数据块位置信息并排序

            +
            public LocatedBlocks   getBlockLocations(String src, long offset, long length) throws IOException {
            +    myMetrics.incrNumGetBlockLocations();
            +    // 获取数据块信息。namenode 为 FSNamesystem 实例。
            +    // 保存的是NameNode的name space树,其属性 FSDirectory dir 关联着 FSImage fsimage 信息,
            +    // fsimage 关联 FSEditLog editLog。
            +    return namesystem.getBlockLocations(getClientMachine(), src, offset, length);
            +}
            +
            // 类 FSNamesystem.getBlockLocationsInternal() 是具体获得块信息的实现。
            +private synchronized LocatedBlocks getBlockLocationsInternal(String src,
            +long offset, long length, int nrBlocksToReturn, 
            +boolean doAccessTime,  boolean needBlockToken) throws IOException {
            +    … … 
            +}  
             
          4. -
          5. 指定输出编码方式
            javac -Dfile.encoding=UTF-8
            +
          6. 在客户端DFSClient将步骤1中打开的读文件, DFSDataInputStream 对象内部的 DFSInputStream 对象的 read(long position, byte[] buffer, int offset, int length)方法进行实际的文件读取

            +
            // class DFSInputStream
            +public int read(long position, byte[] buffer, int offset, int length)
            +  throws IOException {
            +  // sanity checks
            +  checkOpen();
            +  if (closed) {
            +    throw new IOException("Stream closed");
            +  }
            +  failures = 0;
            +  long filelen = getFileLength();
            +  if ((position < 0) || (position >= filelen)) {
            +    return -1;
            +  }
            +  int realLen = length;
            +  if ((position + length) > filelen) {
            +    realLen = (int)(filelen - position);
            +  }
            +  //
            +  // determine the block and byte range within the block
            +  // corresponding to position and realLen
            +  // 判断块内的块和字节范围,位置和实际的长度
            +  List<LocatedBlock> blockRange = getBlockRange(position, realLen);
            +  int remaining = realLen;
            +  for (LocatedBlock blk : blockRange) {
            +    long targetStart = position - blk.getStartOffset();
            +    long bytesToRead = Math.min(remaining, blk.getBlockSize() - targetStart);
            +    fetchBlockByteRange(blk, targetStart, 
            +                        targetStart + bytesToRead - 1, buffer, offset);
            +    remaining -= bytesToRead;
            +    position += bytesToRead;
            +    offset += bytesToRead;
            +  }
            +  assert remaining == 0 : "Wrong number of bytes read.";
            +  if (stats != null) {
            +    stats.incrementBytesRead(realLen);
            +  }
            +  return realLen;
            +} 
            +
            // fetchBlockByteRange() 通过 socket 连接一个最优的 DataNode 来读取数据
            +private void fetchBlockByteRange(LocatedBlock block, long start,
            +                                 long end, byte[] buf, int offset) throws IOException {
            +  //
            +  // Connect to best DataNode for desired Block, with potential offset
            +  //
            +  Socket dn = null;
            +  int refetchToken = 1; // only need to get a new access token once
            +  //      
            +  while (true) {
            +    // cached block locations may have been updated by chooseDataNode()
            +    // or fetchBlockAt(). Always get the latest list of locations at the 
            +    // start of the loop.
            +    block = getBlockAt(block.getStartOffset(), false);
            +    DNAddrPair retval = chooseDataNode(block); // 选者最DataNode
            +    DatanodeInfo chosenNode = retval.info;
            +    InetSocketAddress targetAddr = retval.addr;
            +    BlockReader reader = null;
            +    try {
            +      Token<BlockTokenIdentifier> accessToken = block.getBlockToken();
            +      int len = (int) (end - start + 1);
            +  //
            +      // first try reading the block locally.
            +      if (shouldTryShortCircuitRead(targetAddr)) {// 本地优先
            +        try {
            +          reader = getLocalBlockReader(conf, src, block.getBlock(),
            +              accessToken, chosenNode, DFSClient.this.socketTimeout, start);
            +        } catch (AccessControlException ex) {
            +          LOG.warn("Short circuit access failed ", ex);
            +          //Disable short circuit reads
            +          shortCircuitLocalReads = false;
            +          continue;
            +        }
            +      } else {
            +        // go to the datanode
            +        dn = socketFactory.createSocket(); // socke datanode
            +        LOG.debug("Connecting to " + targetAddr);
            +        NetUtils.connect(dn, targetAddr, getRandomLocalInterfaceAddr(),
            +            socketTimeout);
            +        dn.setSoTimeout(socketTimeout);
            +        reader = RemoteBlockReader.newBlockReader(dn, src, 
            +            block.getBlock().getBlockId(), accessToken,
            +            block.getBlock().getGenerationStamp(), start, len, buffersize, 
            +            verifyChecksum, clientName);
            +      }
            +      int nread = reader.readAll(buf, offset, len); // BlockReader 负责读取数据
            +      return;
            +    }
            +    … … 
            +    finally {
            +      IOUtils.closeStream(reader);
            +      IOUtils.closeSocket(dn);
            +    }
            +    // Put chosen node into dead list, continue
            +    addToDeadNodes(chosenNode); // dead datanode
            +  }
            +}
            +
          7. +
          8. NameNode 实例化启动时便监听客户端请求

            +
            DataNode(final Configuration conf,
            +       final AbstractList<File> dataDirs, SecureResources resources) throws IOException {
            +super(conf);
            +SecurityUtil.login(conf, DFSConfigKeys.DFS_DATANODE_KEYTAB_FILE_KEY, 
            +    DFSConfigKeys.DFS_DATANODE_USER_NAME_KEY);
            +//
            +datanodeObject = this;
            +durableSync = conf.getBoolean("dfs.durable.sync", true);
            +this.userWithLocalPathAccess = conf
            +    .get(DFSConfigKeys.DFS_BLOCK_LOCAL_PATH_ACCESS_USER_KEY);
            +try {
            +  startDataNode(conf, dataDirs, resources);// startDataNode
            +} catch (IOException ie) {
            +  shutdown();
            +  throw ie;
            +}   
            +}
            +
            // startDataNode
            +void startDataNode(Configuration conf, 
            +                 AbstractList<File> dataDirs, SecureResources resources
            +                 ) throws IOException {
            +… …                      
            +// find free port or use privileged port provide
            +ServerSocket ss;
            +if(secureResources == null) {
            +  ss = (socketWriteTimeout > 0) ? 
            +    ServerSocketChannel.open().socket() : new ServerSocket();
            +  Server.bind(ss, socAddr, 0);
            +} else {
            +  ss = resources.getStreamingSocket();
            +}
            +ss.setReceiveBufferSize(DEFAULT_DATA_SOCKET_SIZE); 
            +// adjust machine name with the actual port
            +tmpPort = ss.getLocalPort();
            +selfAddr = new InetSocketAddress(ss.getInetAddress().getHostAddress(),
            +//                                     tmpPort);
            +this.dnRegistration.setName(machineName + ":" + tmpPort);
            +LOG.info("Opened data transfer server at " + tmpPort);
            +//
            +this.threadGroup = new ThreadGroup("dataXceiverServer");
            +this.dataXceiverServer = new Daemon(threadGroup, 
            +    new DataXceiverServer(ss, conf, this));
            +this.threadGroup.setDaemon(true); // DataXceiverServer为守护线程监控客户端连接
            +}
            +
            // class DataXceiverServer.run()
            +public void run() {
            +while (datanode.shouldRun) {
            +  try {
            +    Socket s = ss.accept();
            +    s.setTcpNoDelay(true);
            +    new Daemon(datanode.threadGroup, 
            +        new DataXceiver(s, datanode, this)).start();
            +  } catch (SocketTimeoutException ignored) {
            +  }
            +}
            +}   
            +
            // class DataXceiver.run()
            +// Read/write data from/to the DataXceiveServer.
            +// 操作类型:OP_READ_BLOCK,OP_WRITE_BLOCK,OP_REPLACE_BLOCK,
            +// OP_COPY_BLOCK,OP_BLOCK_CHECKSUM
            +public void run() {
            +DataInputStream in=null; 
            +try {
            +  in = new DataInputStream(
            +      new BufferedInputStream(NetUtils.getInputStream(s), 
            +                              SMALL_BUFFER_SIZE));
            +… … 
            +  switch ( op ) {
            +  case DataTransferProtocol.OP_READ_BLOCK:
            +    readBlock( in );// 读数据
            +    datanode.myMetrics.addReadBlockOp(DataNode.now() - startTime);
            +    if (local)
            +      datanode.myMetrics.incrReadsFromLocalClient();
            +    else
            +      datanode.myMetrics.incrReadsFromRemoteClient();
            +    break;
            +… … 
            +  default:
            +    throw new IOException("Unknown opcode " + op + " in data stream");
            +  }
            +}  
            +
            // class DataXceiver.readBlock()
            +// Read a block from the disk.
            +private void readBlock(DataInputStream in) throws IOException {
            +//
            +// Read in the header,读指令
            +//
            +long blockId = in.readLong();          
            +Block block = new Block( blockId, 0 , in.readLong());
            +// 
            +long startOffset = in.readLong();
            +long length = in.readLong();
            +String clientName = Text.readString(in);
            +Token<BlockTokenIdentifier> accessToken = new Token<BlockTokenIdentifier>();
            +accessToken.readFields(in);
            +// 向客户端写数据
            +OutputStream baseStream = NetUtils.getOutputStream(s, 
            +    datanode.socketWriteTimeout);
            +DataOutputStream out = new DataOutputStream(
            +             new BufferedOutputStream(baseStream, SMALL_BUFFER_SIZE));
            +… … 
            +// send the block,读取本地的block的数据,并发送给客户端
            +BlockSender blockSender = null;
            +final String clientTraceFmt =
            +  clientName.length() > 0 && ClientTraceLog.isInfoEnabled()
            +    ? String.format(DN_CLIENTTRACE_FORMAT, localAddress, remoteAddress,
            +        "%d", "HDFS_READ", clientName, "%d", 
            +        datanode.dnRegistration.getStorageID(), block, "%d")
            +    : datanode.dnRegistration + " Served " + block + " to " +
            +        s.getInetAddress();
            +try {
            +  try {
            +    blockSender = new BlockSender(block, startOffset, length,
            +        true, true, false, datanode, clientTraceFmt);
            +  } catch(IOException e) {
            +    out.writeShort(DataTransferProtocol.OP_STATUS_ERROR);
            +    throw e;
            +  }
            +  out.writeShort(DataTransferProtocol.OP_STATUS_SUCCESS); // send op status
            +  long read = blockSender.sendBlock(out, baseStream, null); // send data,发送数据
            +  … … 
            +} finally {
            +  IOUtils.closeStream(out);
            +  IOUtils.closeStream(blockSender);
            +}
            +}
             
          -

          但在有maven的情况下还需要这么设置:先调整JVM大小,避免工程太大中途出现OOM,再设置文件的编码。

          -
          export MAVEN_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
          -
          ]]>
          +

          HDFS 写过程

          +

          image

          +
            +
          1. 客户端通过对 DistributedFileSystem 对象调用 create() 方法来创建文件(步骤1)。
          2. +
          3. DistributedFileSystem 通过 PRC 对 namenode 调用create() 方法,在文件系统的命名空间中创建一个新的没有数据块文件(步骤2)。
          4. +
          5. namenode 检查并确保此文件不存在,并且客户端由创建该文件的权限。通过 namenode 即为创建的新文件创建一条纪录;否则,创建失败,并向客户端抛出 IOException 异常。
          6. +
          7. DistributedFileSystem 向客户端返回一个 FSDataOutputStream 对象。客户端可以开始写数据。FSDataOutputStream 同样封装一个 DFSoutPutstream 对象。由 DFSInputStream 负责处理 DataNode 和 NameNode 的 I/O。
          8. +
          9. (步骤3)在客户端写数据时,DFSoutPutstream 将它分成一个个的数据包,并写入内部队列(数据队列)。
          10. +
          11. 由 DataStreamer(DFSClient 内部类) 处理数据队列。它根据 datanode 列表要求 namenode 分配适合的新块来存储数据备份。这组 datanode 构成一个管线。假设当前复制数为3,那么管线中将有3个节点。DataStreamer 将数据包流式传输到管线(pipeline)的第一个 datanode 节点。该 datanode 存储数据包并将它发送到管线中的第2个 datanode。 同样地,第二个 datanode 存储该数据包并发哦少年宫到管线中的第3个 datanode(步骤4)。
          12. +
          13. DFSOutputStream 内部维护一个对应的数据包队列等待 datanode 收到确认确认回执(ack queue),当 DFSOutputStream 收到所有的 datanode 确认信息之后,该数据包才从确认队列中删除。
          14. +
          15. 若在写数据时,datanode 发生故障。则先关闭管线,确认把队列中任何数据包都添加回数据队列的最前端,以确保故障节点下游的 datanode 不会漏掉任何一个数据包。并为存储在另一个 datanode 的当前数据块指定一个新的标志,并将该标志发送个 namenode,以便故障的 datanode 在恢复后可以删除存储的部分数据块。从管线中删除故障 datanode 节点并把余下的数据块写入管线中的2个 datanode。Namenode 注意到块复本量不足时,会在另一个节点上创建一个新的复本。后续数据块继续正常处理。一个块在写入期间发生多个 datanode 故障的概率不高,只要写入了最小复本数(dfs.replication.min默认为1),写入即为成功。此块由异步执行复制以达到目标复本数,默认为3。
          16. +
          17. 当客户端结束写入数据,则调用 stream 的 close()函数。此操作将剩余所有的数据包写入 datanode pipeline 中,并等待 ack queue 返回成功。最后通知元数据节点写入完毕。namenode 是通过 Datastreamer 询问的数据块的分配,它在返回成功前只需要等待数据块进行最小量的复制。
          18. +
          +
          源代码理解
          +

          TODO,原笔记已丢失,待补。 +hdfs 架构一页。且读过程和写过程各独立一页。

          +

          NadeNode 和 DataNode 实现的协议

          +

          TODO ,独立 一页

          +

          补充

          +

          详细介绍HDFS读写过程解析

          +]]> +
          + + <![CDATA[Hadoop1.x 学习准备]]> + + 2014-02-24T23:28:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/02/hadoop1.x--xue-xi-zhun-bei/ + 开始学习Hadoop之前先了解下Hadoop之父Doug Cutting 膜拜是必须的。路子是一步不走出来了。各位前辈给我们留下了宝贝的资源。不加以学习利用有些说不过去。

          +

          我个人是从2013年夏天开始拿到 《Hadoop权威指南》第二版的,但由于各种原因,也可以直接说我比较懒,从夏天到冬季中途到是翻过那书,感觉每次都没由什么实际的记忆和理解过程。在12月初,发现开源力量提供了相关的网上视频在线教学课程。选择适合自己的就是对的。 +,我毅然成为了其中的一员。 +目前我没有从事相关的工作,出于在工作之外寻找乐趣,就来了。

          +

          学习之前还是多看些相关的资源比较容易找到赶脚。

          +
          资源列举:
          + +

          部分网站可能是比较难打开的。经常遇到问题谷歌之后发现很多问题还是跳转到这些权威论坛上面了。先登记在案,以备使用。在此仅以纪录我学习《Hadoop权威指南》一书的笔记。 +笔记中图片资源大部分出自于原书英文版,章节内容来自于原书中/英文版 + 开源力量 LouisT 老师ppt/课堂示例及扩展,当然不乏在网站上找到各位博主的精品博客参考。

          +

          我自己的练习代码存放在 github。

          +]]>
          diff --git a/category/hadoop/index.html b/category/hadoop/index.html index a40b4b0..0c05f5a 100644 --- a/category/hadoop/index.html +++ b/category/hadoop/index.html @@ -4,7 +4,7 @@ - 分类: hadoop - kangfoo's 博客 + 分类: hadoop - kangfoo's blog @@ -12,12 +12,12 @@ - + - + @@ -35,7 +35,7 @@
          -

          kangfoo's 博客

          +

          kangfoo's blog

          工作学习笔记,生活掠影。

          @@ -88,6 +88,253 @@

          分类: Hadoop

          2014

          + + + + + + + + + + + + + + + + + + + @@ -109,7 +356,7 @@

          被贴了 hadoop shell, hadoop +被贴了 hadoop1 标签 @@ -123,7 +370,7 @@

          被贴了 java, ant, hadoop +被贴了 ant, hadoop2 标签 @@ -136,6 +383,8 @@

          被贴了 hadoop1 + 标签 @@ -161,19 +410,34 @@

          近期文章

        2. - Hadoop1.x Wordcount分析 + Hadoop MapReduce 学习参考博客汇总 +
        3. +
        4. + Hadoop Pipes & Streaming +
        5. +
        6. + Hadoop MapReduce Sort +
        7. +
        8. + Hadoop MapReduce Join +
        9. +
        10. + Hadoop MapReduce 计数器 +
        11. +
        12. + Hadoop MapReduce RecordReader 组件
        13. - Hadoop1.x 命令手册列举 + Hadoop MapReduce Partitioner 组件
        14. - 编译hadoop 2.x Hadoop-eclipse-plugin插件 + Hadoop MapReduce Combiner 组件
        15. - 在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境 + Hadoop MapReduce 类型与格式
        16. - 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件 + Hadoop MapReduce 工作机制
        17. diff --git a/category/index.html b/category/index.html index 8c33c9e..4d526b6 100644 --- a/category/index.html +++ b/category/index.html @@ -4,7 +4,7 @@ - Categories - kangfoo's 博客 + Categories - kangfoo's blog @@ -12,12 +12,12 @@ - + - + @@ -35,7 +35,7 @@
          -

          kangfoo's 博客

          +

          kangfoo's blog

          工作学习笔记,生活掠影。

          @@ -88,7 +88,7 @@

          Categories

          diff --git a/index.html b/index.html index 75305ca..78aff57 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - kangfoo's 博客 + kangfoo's blog @@ -12,12 +12,12 @@ - + - + - + @@ -36,7 +36,7 @@
          -

          kangfoo's 博客

          +

          kangfoo's blog

          工作学习笔记,生活掠影。

          @@ -83,840 +83,491 @@

          工作学习笔记,生活掠影。

          -

          Hadoop1.x Wordcount分析

          +

          Hadoop MapReduce 学习参考博客汇总

          - - | 评论 + + | 评论

          -

          hadoop mapreduce 过程粗略的分为:map, redurce(copy, sort, reduce)两个阶段。具体的工作机制还是挺复杂的,这里主要通过hadoop example jar中提供的wordcount来对hadoop mapredurce做个简单的理解。Wordcount程序输入文件类型,计算单词的频率。输出是文本文件:每行是单词和它出现的频率,用Tab键隔开。

          -
            -
          1. 首先确保Hadoop集群正常运行,并了解mapredurce工作时涉及到的基本的文件备配。vi mapred-site.xml

            - -
            <configuration>
            -<property>
            -<name>mapred.job.tracker</name> <!--JobTracker的主机(或者IP)和端口。 -->
            -<value>master11:9001</value>
            -</property>
            -<property>
            -<name>mapred.system.dir</name> <!--Map/Reduce框架存储系统文件的HDFS路径。-->
            -<value>/home/${user.name}/env/mapreduce/system</value>
            -</property>
            -<property>
            -<name>mapred.local.dir</name> <!--Map/Reduce在本地文件系统下中间结果存放路径. -->
            -<value>/home/${user.name}/env/mapreduce/local</value>
            -</property>
            -</configuration>
            -
          2. -
          3. 上传一个文件到hdfs文件系统

            - -
            $ ./bin/hadoop fs -mkdir /test/input
            -$ ./bin/hadoop fs -put ./testDir/part0 /test/input
            -$ ./bin/hadoop fs -lsr /
            -## part0 文件中的内容为:
            -hadoop zookeeper hbase hive
            -rest osgi http ftp
            -hadoop zookeeper
            -
          4. -
          5. 执行workcount -$ ./bin/hadoop jar hadoop-examples-1.2.1.jar wordcount /test/input /test/output
            日志如下

            - -
            14/01/19 18:23:25 INFO input.FileInputFormat: Total input paths to process : 1
            -## 使用 native-hadoop library
            -14/01/19 18:23:25 INFO util.NativeCodeLoader: Loaded the native-hadoop library
            -14/01/19 18:23:25 WARN snappy.LoadSnappy: Snappy native library not loaded
            -14/01/19 18:23:25 INFO mapred.JobClient: Running job: job_201401181723_0005
            -14/01/19 18:23:26 INFO mapred.JobClient:  map 0% reduce 0%
            -14/01/19 18:23:32 INFO mapred.JobClient:  map 100% reduce 0%
            -14/01/19 18:23:40 INFO mapred.JobClient:  map 100% reduce 33%
            -14/01/19 18:23:42 INFO mapred.JobClient:  map 100% reduce 100%
            -## jobid job_201401181723_0005 (job_yyyyMMddHHmm_(顺序自然数,不足4位补0,已保证磁盘文件目录顺序))
            -14/01/19 18:23:43 INFO mapred.JobClient: Job complete: job_201401181723_0005
            -## Counters 计数器
            -14/01/19 18:23:43 INFO mapred.JobClient: Counters: 29
            -## Job Counters
            -14/01/19 18:23:43 INFO mapred.JobClient:   Job Counters
            -14/01/19 18:23:43 INFO mapred.JobClient:     Launched reduce tasks=1
            -14/01/19 18:23:43 INFO mapred.JobClient:     SLOTS_MILLIS_MAPS=6925
            -14/01/19 18:23:43 INFO mapred.JobClient:     Total time spent by all reduces waiting after reserving slots (ms)=0
            -14/01/19 18:23:43 INFO mapred.JobClient:     Total time spent by all maps waiting after reserving slots (ms)=0
            -14/01/19 18:23:43 INFO mapred.JobClient:     Launched map tasks=1
            -14/01/19 18:23:43 INFO mapred.JobClient:     Data-local map tasks=1
            -14/01/19 18:23:43 INFO mapred.JobClient:     SLOTS_MILLIS_REDUCES=9688
            -## File Output Format Counters
            -14/01/19 18:23:43 INFO mapred.JobClient:   File Output Format Counters
            -14/01/19 18:23:43 INFO mapred.JobClient:     Bytes Written=63
            -## FileSystemCounters
            -14/01/19 18:23:43 INFO mapred.JobClient:   FileSystemCounters
            -14/01/19 18:23:43 INFO mapred.JobClient:     FILE_BYTES_READ=101
            -14/01/19 18:23:43 INFO mapred.JobClient:     HDFS_BYTES_READ=167
            -14/01/19 18:23:43 INFO mapred.JobClient:     FILE_BYTES_WRITTEN=112312
            -14/01/19 18:23:43 INFO mapred.JobClient:     HDFS_BYTES_WRITTEN=63
            -## File Input Format Counters
            -14/01/19 18:23:43 INFO mapred.JobClient:   File Input Format Counters
            -14/01/19 18:23:43 INFO mapred.JobClient:     Bytes Read=65
            -## Map-Reduce Framework
            -14/01/19 18:23:43 INFO mapred.JobClient:   Map-Reduce Framework
            -14/01/19 18:23:43 INFO mapred.JobClient:     Map output materialized bytes=101
            -14/01/19 18:23:43 INFO mapred.JobClient:     Map input records=3
            -14/01/19 18:23:43 INFO mapred.JobClient:     Reduce shuffle bytes=101
            -14/01/19 18:23:43 INFO mapred.JobClient:     Spilled Records=16
            -14/01/19 18:23:43 INFO mapred.JobClient:     Map output bytes=104
            -14/01/19 18:23:43 INFO mapred.JobClient:     Total committed heap usage (bytes)=176230400
            -14/01/19 18:23:43 INFO mapred.JobClient:     CPU time spent (ms)=840
            -14/01/19 18:23:43 INFO mapred.JobClient:     Combine input records=10
            -14/01/19 18:23:43 INFO mapred.JobClient:     SPLIT_RAW_BYTES=102
            -14/01/19 18:23:43 INFO mapred.JobClient:     Reduce input records=8
            -14/01/19 18:23:43 INFO mapred.JobClient:     Reduce input groups=8
            -14/01/19 18:23:43 INFO mapred.JobClient:     Combine output records=8
            -14/01/19 18:23:43 INFO mapred.JobClient:     Physical memory (bytes) snapshot=251568128
            -14/01/19 18:23:43 INFO mapred.JobClient:     Reduce output records=8
            -14/01/19 18:23:43 INFO mapred.JobClient:     Virtual memory (bytes) snapshot=1453596672
            -14/01/19 18:23:43 INFO mapred.JobClient:     Map output records=10
            -
          6. -
          7. 运行之后的文件系统结构 -
            日志如下

            - -
            drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test
            -drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/input
            --rw-r--r--   2 hadoop supergroup         65 2014-01-19 18:23 /test/input/part0
            -drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/output
            --rw-r--r--   2 hadoop supergroup          0 2014-01-19 18:23 /test/output/_SUCCESS
            -drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/output/_logs
            -drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/output/_logs/history
            -## job 执行结果的数据文件
            --rw-r--r--   2 hadoop supergroup      13647 2014-01-19 18:23 /test/output/_logs/history/job_201401181723_0005_1390127005579_hadoop_word+count
            -## job 配置文件
            --rw-r--r--   2 hadoop supergroup      48374 2014-01-19 18:23 /test/output/_logs/history/job_201401181723_0005_conf.xml
            -## 只分了1个
            --rw-r--r--   2 hadoop supergroup         63 2014-01-19 18:23 /test/output/part-r-00000
            -drwxr-xr-x   - hadoop supergroup          0 2013-12-22 14:02 /user
            -drwxr-xr-x   - hadoop supergroup          0 2014-01-18 23:16 /user/hadoop
            -
          8. -
          9. 查看结果。$ ./bin/hadoop fs -cat /test/output/part-r-00000

            - -
            ftp     1
            -hadoop  2
            -hbase   1
            -hive    1
            -http    1
            -osgi    1
            -rest    1
            -zookeeper       2
            -
          10. -
          11. 更详细的信息可web访问 http://master11:50030/jobtracker.jsp 进行查看.

            -
          12. -
          +

          TODO.

          -

          Hadoop1.x 命令手册列举

          +

          Hadoop Pipes & Streaming

          - - | 评论 + + | 评论

          -

          hadoop命令一般分为两类:

          -

          用户命令
          archive, distcp, fs, fsck, jar, job, pipes, version, CLASSNAME

          -

          管理命令
          balancer, daemonlog, datanode, dfsadmin, jobtracker, namenode, secondarynamenode, tasktracker

          -

          用户命令

          +

          申明:本文大部分出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件 和 Apache 官方文档。

          +

          Streaming

          +
            +
          • Streaming 是 hadoop 里面提供的一个工具
          • +
          • Streaming 框架允许任何程序语言实现的程序在 Hadoop MapReduce 中使用,方便任何程序向 Hadoop 平台移植,具有很强的扩展性;
          • +
          • mapper 和 reducer 会从标准输入中读取用户数据,一行一行处理后发送给标准输出。Streaming 工具会创建 MapReduce 作业,发送给各个 tasktracker,同时监控整个作业的执行过程;
          • +
          • 如果一个文件(可执行或者脚本)作为 mapper,mapper 初始化时,每一个 mapper 任务会把该文件作为一个单独进程启动,mapper 任务运行时,它把输入切法成行并把每一行提供给可执行文件进程的标准输入。同 时,mapper 收集可执行文件进程标准输出的内容,并把收到的每一行内容转化成 key/value,作为 mapper的输出。默认情况下,一行中第一个 tab 之前的部分作为 key,之后的(不包括)作为value。如果没有 tab,整行作为 key 值,value值为null。对于reducer,类似;
          • +
          +

          Streaming 优点

            -
          1. fs命令,可参见 file system shell

            - -
            ## 新建一个文件夹
            -$ ./hadoop fs -mkdir /test
            -## 上传一个文件到hdfs
            -$ ./hadoop fs -put ./rcc /test
            -## 查看文件
            -$ ./hadoop fs -lsr /test
            -## 文件模式  备份个数   用户   用户组   字节大小 最后修改日期 和时间 文件或者目录的绝对路径
            -##-rw-r--r--   2   hadoop supergroup 2810   2014-01-18 19:20  /test/rcc
            -## 
            -## 从hdfs文件系统中复制一个文件到本地
            -$ ./hadoop fs -copyToLocal /test/rcc rcc.copy
            -## md5对比
            -$ md5sum rcc rcc.copy
            -# b9d8a383bba2dd1b2ee0ede6c5cabeae  rcc
            -# b9d8a383bba2dd1b2ee0ede6c5cabeae  rcc.copy
            -##
            -## 删除
            -$ ./hadoop fs -rmr /test
            -
          2. -
          3. fsck命令。 可以参考文件系统的健康状态;查看一个文件所在的数据块;可以删除一个坏块;可以查找一个缺失的块。 可参见fsck

            - -
            ## 新建一个文件夹
            -$ ./hadoop fsck /
            -
          4. -
          5. archive命令。 创建一个hadoop档案文件。语法:archive -archiveName NAME -p <parent path> <src>* <dest> 可参考hadoop_archives

            - -
            ## 创建一个归档文件
            -$ ./hadoop archive -archiveName archive.har -p  /test rcc
            -## 查看归档文件列表
            -$ ./hadoop dfs -lsr har:///user/hadoop/rcc/archive.har
            -## 查看归档文件
            -$ ./hadoop dfs -cat har:///user/hadoop/rcc/archive.har/rcc
            -
          6. +
          7. 开发效率高,便于移植。Hadoop Streaming 使用 Unix 标准流作为 Hadoop 和应用程序之间的接口。在单机上可按照 cat input | mapper | sort | reducer > output 进行测试,若单机上测试通过,集群上一般控制好内存也可以很好的执行成功。

            +
          8. +
          9. 提高运行效率。对内存要求较高,可用C/C++控制内存。比纯java实现更好。

            +
          -

          管理命令

          +

          Streaming缺点

            -
          1. dfsadmin

            - -
            ## 报告文件系统的基本信息和统计信息
            -$ ./hadoop dfsadmin -report
            -## 安全模式维护命令
            -$ ./hadoop dfsadmin -safemode enter
            -## -safemode enter | leave | get | wait
            -## 不接受对名字空间的更改(只读), 不复制或删除块
            -## -setQuota 为每个目录 <dirname>设定配额<quota>。包括文件夹和文件名称。
            -$ ./hadoop dfsadmin -setQuota 2 /test
            -$ ./hadoop fs -put ./rcc /test
            -$ ./hadoop fs -put ./hadoop /test
            -put: org.apache.hadoop.hdfs.protocol.NSQuotaExceededException: The NameSpace quota (directories and files) of directory /test is exceeded: quota=2 file count=3
            -
          2. -
          3. balancer命令。集群平衡工具

            - -
            ##
            -$ ./hadoop balancer
            -或者
            -$ ./bin/./start-balancer.sh
            -
          4. +
          5. Hadoop Streaming 默认只能处理文本数据,(0.21.0之后可以处理二进制数据)。

            +
          6. +
          7. Steaming 中的 mapper 和 reducer 默认只能想标准输出写数据,不能方便的多路输出。

            +
          -
          其他未列举的命令可以参见官方文档。
          -

          官方文档链接: hadoop1.2.1 Commands Guide, hadoop1.0.4 命令手册

          +

          更详细内容请参考于: http://hadoop.apache.org/docs/r1.2.1/streaming.html

          +
          $HADOOP_HOME/bin/hadoop  jar $HADOOP_HOME/hadoop-streaming.jar \
          +    -input myInputDirs \
          +    -output myOutputDir \
          +    -mapper /bin/cat \
          +    -reducer /bin/wc
          +

          streaming示例

          +

          perl 语言的streaming示例 代码

          +
          -rw-rw-r--. 1 hadoop hadoop     48 2月  22 10:47 data
          +-rw-rw-r--. 1 hadoop hadoop 107399 2月  22 10:41 hadoop-streaming-1.2.1.jar
          +-rw-rw-r--. 1 hadoop hadoop    186 2月  22 10:45 mapper.pl
          +-rw-rw-r--. 1 hadoop hadoop    297 2月  22 10:55 reducer.pl
          +##
          +$ ../bin/hadoop jar hadoop-streaming-1.2.1.jar -mapper mapper.pl -reducer reducer.pl -input /test/streaming -output /test/streamingout1 -file mapper.pl -file reducer.pl 
          +

          Hadoop pipes

          +
            +
          1. Hadoop pipes 是 Hadoop MapReduce 的 C++ 的接口代称。不同于使用标准输入和输出来实现 map 代码和 reduce 代码之间的 Streaming。
          2. +
          3. Pipes 使用套接字 socket 作为 tasktracker 与 C++ 版本函数的进程间的通讯,未使用 JNI。
          4. +
          5. 与 Streaming 不同,Pipes 是 Socket 通讯,Streaming 是标准输入输出。
          6. +
          +

          编译 Hadoop Pipes

          +

          编译c++ pipes( 确保操作系统提前安装好了 openssl,zlib,glib,openssl-devel) +Hadoop更目录下执行 +ant -Dcompile.c++=yes examples

          +

          具体请参见《Hadoop Pipes 编译》

          +

          Hadoop官方示例:

          +
          hadoop/src/examples/pipes/impl
          + config.h.in
          + sort.cc
          +wordcount-nopipe.cc
          +wordcount-part.cc
          +wordcount-simple.cc
          +

          运行前需要把可执行文件和输入数据上传到 hdfs:

          +
          $ ./bin/hadoop fs -mkdir /test/pipes/input
          +$ ./bin/hadoop fs -put a.txt /test/pipes/input 
          +$ ./bin/hadoop fs -cat /test/pipes/input/a.txt 
          +hello hadoop hello hive hello hbase hello zk
          +

          上传执行文件,重新命名为/test/pipes/exec

          +
          $ ./bin/hadoop fs -put ./build/c++-examples/Linux-amd64-64/bin/wordcount-simple /test/pipes/exec
          +

          在编译好的文件夹目录下执行

          +
          $ cd hadoop/build/c++-examples/Linux-amd64-64/bin
          +$ ../../../../bin/hadoop pipes -Dhadoop.pipes.java.recordreader=true -Dhadoop.pipes.java.recordwriter=true -reduces 4 -input /test/pipes/input -output /test/pipes/input/output1 -program /test/pipes/execs
          +

          执行结果如下:

          +
          $ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00000 hbase 1 
          +$ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00001 hello 4 hive 1 
          +$ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00002 hadoop 1 zk 1 
          +$ ./bin/hadoop fs -cat /test/pipes/input/output1/part-00003
          +

          参考博客:

          +
          -

          编译hadoop 2.x Hadoop-eclipse-plugin插件

          +

          Hadoop MapReduce Sort

          - - | 评论 + + | 评论

          -

          经过hadoop1.x的发展,编译hadoop2.x版本的eclipse插件视乎比之前要轻松的多。如果你不在意编译过程中提示的警告,那么根据how to build - hadoop2x-eclipse-plugin文档就可一步到位。若想自己设置部分变量,可参考编译hadoop 1.2.1 Hadoop-eclipse-plugin插件。当然有问题及时和开发社区联系你会收到意想不到的收获.

          -

          issuce站点.

          -

          主要步骤

          +

          排序是 MapReduce 的核心。排序可分为四种排序:普通排序、部分排序、全局排序、辅助排序

          +

          普通排序

          +

          Mapreduce 本身自带排序功能;Text 对象是不适合排序的;IntWritable,LongWritable 等实现了WritableComparable 类型的对象都是可以排序的。

          +

          部分排序

          +

          map 和 reduce 处理过程中包含了默认对 key 的排序,那么如果不要求全排序,可以直接把结果输出,每个输出文件中包含的就是按照key执行排序的结果。

          +

          控制排序顺序

          +

          键的排序是由 RawComparator 控制的,规则如下:

          +
            +
          1. 若属性 mapred.output.key.comparator.class 已设置,则使用该类的实例。调用 JobConf 的 setOutputKeyComparatorClass() 方法进行设置。
          2. +
          3. 否则,键必须是 WritableComparable 的子类,并使用针对该键类的已登记的 comparator.
          4. +
          5. 如果没有已登记的 comparator ,则使用 RawComparator 将字节流反序列化为一个对象,再由 WritableComparable 的 compareTo() 方法进行操作。
          6. +
          +

          全局排序(对所有数据排序)

          +

          Hadoop 没有提供全局数据排序,而全局排序是非常普遍的需求。

          +

          实现方案

            -
          • 介质准备
          • -
          • 执行
          • -
          • 安装验证
          • +
          • 首先,创建一系列的排好序的文件;
          • +
          • 其次,串联这些文件;
          • +
          • 最后,生成一个全局排序的文件。
          -

          具体操作

          +

          主要思路是使用一个partitioner来描述全局排序的输出。该方法关键在于如何划分各个分区。

          +

          例,对整数排序,[0,10000] 的在 partition 0 中,(10000,20000] 在 partition 1 中… …即第n个reduce 所分配到的数据全部大于第 n-1 个 reduce 中的数据。每个 reduce 的结果都是有序的。
          +然后再将所有的输出文件顺序合并成一个大的文件,那么就实现了全局排序。

          +

          在比较理想的数据分布均匀的情况下,每个分区内的数据量要基本相同。

          +

          但实际中数据往往分布不均匀,出现数据倾斜,这时按照此方法进行的分区划分数据就不适用,可对数据进行采样。

          +

          采样器

          +

          通过对 key 空间进行采样,可以较为均匀的划分数据集。采样的核心思想是只查看一小部分键,获取键的相似分布,并由此构建分区。采样器是在 map 阶段之前进行的, 在提交 job 的 client 端完成的。

          +

          Sampler接口

          +

          Sampler 接口是 Hadoop 的采样器,它的 getSample() 方法返回一组样本。此接口一般不由客户端调用,而是由 InputSampler 类的静态方法 writePartitionFile() 调用,以创建一个顺序文件来存储定义分区的键。

          +

          Sampler接口声明如下:

          +
            public interface Sampler<K,V> {
          +   K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException;
          +  }
          +

          继承 Sample 的类还有 IntervalSampler 间隔采样器,RandomSampler 随机采样器,SplitSampler 分词采样器。它们都是 InputSampler 的静态内部类。

          +

          getSample() 方法根据 job 的配置信息以及输入格式获得抽样结果,三个采样类各自有不同的实现。

          +

          IntervalSampler 根据一定的间隔从 s 个分区中采样数据,非常适合对排好序的数据采样。

          +
          public static class IntervalSampler<K,V> implements Sampler<K,V> {
          +    private final double freq;// 哪一条记录被选中的概率
          +    private final int maxSplitsSampled;// 采样的最大分区数
          +    /**
          +     * For each split sampled, emit when the ratio of the number of records
          +     * retained to the total record count is less than the specified
          +     * frequency.
          +     */
          +    @SuppressWarnings("unchecked") // ArrayList::toArray doesn't preserve type
          +    public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {
          +      InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());// 1. 得到输入分区数组
          +      ArrayList<K> samples = new ArrayList<K>();
          +      int splitsToSample = Math.min(maxSplitsSampled, splits.length);
          +      int splitStep = splits.length / splitsToSample; // 2. 分区采样时的间隔splitStep = 输入分区总数 除以 splitsToSample的 商;
          +      long records = 0;
          +      long kept = 0;
          +      for (int i = 0; i < splitsToSample; ++i) {
          +        RecordReader<K,V> reader = inf.getRecordReader(splits[i * splitStep], // 3. 采样下标为i * splitStep的数据
          +            job, Reporter.NULL);
          +        K key = reader.createKey();
          +        V value = reader.createValue();
          +        while (reader.next(key, value)) {// 6. 循环读取下一条记录
          +          ++records;
          +          if ((double) kept / records < freq) { // 4. 如果当前样本数与已经读取的记录数的比值小于freq,则将这条记录添加到样本集合
          +            ++kept;
          +            samples.add(key);// 5. 将记录添加到样本集合中
          +            key = reader.createKey();
          +          }
          +        }
          +        reader.close();
          +      }
          +      return (K[])samples.toArray();
          +    }
          +  }
          +… … 
          +}
          +

          RandomSampler 是常用的采样器,它随机地从输入数据中抽取 Key

          +
            public static class RandomSampler<K,V> implements Sampler<K,V> {
          +    private double freq;// 一个Key被选中的 概率
          +    private final int numSamples;// 从所有被选中的分区中获得的总共的样本数目
          +    private final int maxSplitsSampled;// 需要检查扫描的最大分区数目
          +/**
          +     * Randomize the split order, then take the specified number of keys from
          +     * each split sampled, where each key is selected with the specified
          +     * probability and possibly replaced by a subsequently selected key when
          +     * the quota of keys from that split is satisfied.
          +     */
          +    @SuppressWarnings("unchecked") // ArrayList::toArray doesn't preserve type
          +    public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {
          +      InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());// 1. 获取所有的输入分区
          +      ArrayList<K> samples = new ArrayList<K>(numSamples);// 2. 确定需要抽样扫描的分区数目
          +      int splitsToSample = Math.min(maxSplitsSampled, splits.length);// 3. 取最小的为采样的分区数
          +      Random r = new Random();
          +      long seed = r.nextLong();
          +      r.setSeed(seed);
          +      LOG.debug("seed: " + seed);
          +      // shuffle splits 4. 对输入分区数组shuffle排序
          +      for (int i = 0; i < splits.length; ++i) {
          +        InputSplit tmp = splits[i];
          +        int j = r.nextInt(splits.length);// 5. 打乱其原始顺序
          +        splits[i] = splits[j];
          +        splits[j] = tmp;
          +      }
          +      // our target rate is in terms of the maximum number of sample splits,
          +      // but we accept the possibility of sampling additional splits to hit
          +      // the target sample keyset
          +// 5. 然后循环逐 个扫描每个分区中的记录进行采样,
          +      for (int i = 0; i < splitsToSample ||
          +                     (i < splits.length && samples.size() < numSamples); ++i) {
          +        RecordReader<K,V> reader = inf.getRecordReader(splits[i], job,
          +            Reporter.NULL);
          +       // 6. 取出一条记录
          +        K key = reader.createKey();
          +        V value = reader.createValue();
          +        while (reader.next(key, value)) {
          +          if (r.nextDouble() <= freq) {
          +            if (samples.size() < numSamples) {// 7. 判断当前的采样数是否小于最大采样数
          +              samples.add(key); //8. 小于则这条记录被选中,放进采样集合中,
          +            } else {
          +              // When exceeding the maximum number of samples, replace a
          +              // random element with this one, then adjust the frequency
          +              // to reflect the possibility of existing elements being
          +              // pushed out
          +              int ind = r.nextInt(numSamples);// 9. 从[0,numSamples]中选择一个随机数
          +              if (ind != numSamples) {
          +                samples.set(ind, key);// 10. 替换掉采样集合随机数对应位置的记录,
          +              }
          +              freq *= (numSamples - 1) / (double) numSamples;// 11. 调小频率
          +            }
          +            key = reader.createKey();// 12. 下一条纪录的key
          +          }
          +        }
          +        reader.close();
          +      }
          +      return (K[])samples.toArray();// 13. 返回
          +    }
          +  }
          +… … 
          +}
          +

          SplitSampler 从 s 个分区中采样前 n 个记录,是采样随机数据的一种简便方式。

          +
            public static class SplitSampler<K,V> implements Sampler<K,V> {
          +    private final int numSamples;// 最大采样数
          +    private final int maxSplitsSampled;// 最大分区数
          +    … … 
          +    /**
          +     * From each split sampled, take the first numSamples / numSplits records.
          +     */
          +    @SuppressWarnings("unchecked") // ArrayList::toArray doesn't preserve type
          +    public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {
          +      InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());
          +      ArrayList<K> samples = new ArrayList<K>(numSamples);
          +      int splitsToSample = Math.min(maxSplitsSampled, splits.length);// 1. 采样的分区数
          +      int splitStep = splits.length / splitsToSample; // 2. 分区采样时的间隔 = 分片的长度 与 输入分片的总数的 商
          +      int samplesPerSplit = numSamples / splitsToSample; // 3. 每个分区的采样数 
          +      long records = 0;
          +      for (int i = 0; i < splitsToSample; ++i) {
          +        RecordReader<K,V> reader = inf.getRecordReader(splits[i * splitStep], // 4.采样下标为i * splitStep的数据
          +            job, Reporter.NULL);
          +        K key = reader.createKey();
          +        V value = reader.createValue();
          +        while (reader.next(key, value)) {
          +          samples.add(key);// 5. 将记录添加到样本集合中
          +          key = reader.createKey();
          +          ++records;
          +          if ((i+1) * samplesPerSplit <= records) { // 6. 当前样本数大于当前的采样分区所需要的样本数,则停止对当前分区的采样。
          +            break;
          +          }
          +        }
          +        reader.close();
          +      }
          +      return (K[])samples.toArray();
          +    }
          +  }
          +

          Hadoop为顺序文件提供了一个 TotalOrderPartitioner 类,可以用来实现全局排序;TotalOrderPartitioner 源代码理解。TotalOrderPartitioner 内部定义了多个字典树(内部类)。

          +
          interface Node<T> 
          +// 特里树,利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高
          +static abstract class TrieNode implements Node<BinaryComparable> 
          +static class InnerTrieNode extends TrieNode 
          +static class LeafTrieNode extends TrieNode
          +… … 
          +

          由 TotalOrderPartitioner 调用 getPartition() 方法返回分区,由 buildTrieRec() 构建特里树.

          +
           private TrieNode buildTrieRec(BinaryComparable[] splits, int lower,
          +      int upper, byte[] prefix, int maxDepth, CarriedTrieNodeRef ref) {
          +… … 
          +}
          +

          采样器使用示例

            -
          1. 设置语言环境

            - -
            $ export LC_ALL=en
            -
          2. -
          3. 检查ANT_HOME,JAVA_HOME

            -
          4. -
          5. 下载hadoop2x-eclipse-plugin
            -目前hadoop2的eclipse-plugins源代码由github脱管,下载地址how to build - hadoop2x-eclipse-plugin 右侧的 “Download ZIP” 或者 克隆到桌面. 当然你也可以fork到你自己的帐户下,在使用git clone。

            -
          6. -
          7. 执行

            - -
            $ cd src/contrib/eclipse-plugin
            -$ ant jar -Dversion=2.2.0 -Declipse.home=/opt/eclipse -Dhadoop.home=/usr/share/hadoop
            -

            将上述java system property eclipse.home 和 hadoop.home 设置成你自己的环境路径。 执行上述命令可能很快或者很慢。请耐心等待。主要慢的target:ivy-download,ivy-resolve-common。最后jar生成在 -$root/build/contrib/eclipse-plugin/hadoop-eclipse-plugin-2.2.0.jar路径下。

            -
          8. -
          9. 安装验证
            -将生成好的jar,复制到${eclipse.home}/plugins目录下。启动eclipse,新建Map/Reduce Project,配置hadoop location.验证插件完全分布式的插件配置截图和core-site.xml端口配置。

            -
          10. -
          11. 效果图
            -使用插件访问本地的伪分布式hadoop环境。查看文件texst1.txt和test2.txt同使用命令 hadoop dfs -ls /in 效果相同。 -image

            -
          12. -
          13. 已编译的插件 -hadoop-eclipse-plugin-2.2.0.jar

            -
          14. -
          15. 备注
            -目前我在使用这个版本的插件时发现还时挺不稳定的。发现了两个缺陷。我的环境为:Java HotSpot™ 64-Bit Server VM、 eclipse-standard-kepler-SR1-macosx-cocoa、 hadoop2.2.0。

            -
          16. +
          17. 新建文件,名为 random.txt,里面每行存放一个数据。可由 RandomGenerator 类生成准备数据
          18. +
          19. 执行 TestTotalOrderPartitioner.java
          -

          缺陷:

          +

          辅助排序

          +

          先按 key 排序,在按 相同的 key 不同的 value 再排序。可实现对值分组的效果。

          -

          截图如下: -image

          -

          在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境

          +

          Hadoop MapReduce Join

          - - | 评论 + + | 评论

          -

          一、初衷

          -

          对于学习Hadoop的我来将,没有足够的硬件设备,但又想安装完全分布式的Hadoop,一个master两个slave。手上就一台能联网的笔记本,那就使用oracle vitual box进行环境搭建把。环境搭建的效果为:在虚拟机中虚拟3台centos6.4 64位系统,每台都配置双网卡NAT,host-only模式。在宿主机器上安装eclipse进行Hadoop开发。

          -

          Hadoop环境搭建很大部分是在准备操作系统。具体如何搭建Hadoop其实就像解压缩普通的tar类似。然后再适当的配置Hadoop的dfs,mapredurce相关的配置,调整操作系统就可以开始着手学习Hadoop了。

          -

          二、拓补图

          -

          图片制作中… …

          -
            -
          • master11:192.168.56.11
          • -
          • slave12:192.168.56.12
          • -
          • slave14:192.168.56.14
          • -
          -

          三、介质准备

          - -

          其他版本可到相关官方网站根据需要自行下载

          -

          四、虚拟机和基础环境搭建

          -

          这里主要操作有:安装一个新的oracle virtual box,并先安装一个centos6.4 的64位的操作系统。配置操作系统双网卡、修改机器名为master11、新建hadoop用户组和hadoop用户、配置sudo权限、安装配置java环境、同步系统时间、关闭防火墙。其中有些步骤需要重启操作系统后成效,建议一切都配置后再重启并再次验证是否生效,并开始克隆两个DataNode节点服务器slave12\slave14。

          -

          4.1 安装虚拟机

          -

          可参见官方文档安装oracle virtual box

          -

          4.2 在虚拟机里安装centos6.4

          -

          此处使用的是mini版的centos6.4 64位网易提供的镜像。在安装中内存调整大于等于1g,默认为视图安装界面,小于1g则为命令行终端安装方式。可更具实际情况调整虚拟机资源分配。此处为内存1g,存储20g.网络NAT模式。

          -

          具体可参见centos安装

          -

          4.3 配置双网卡

          -

          使用自己的笔记本经常遇到的问题就是在不同的网络下ip是不一样的。那么我们在学习hadoop的时候岂不是要经常修改这些ip呢。索性就直接弄个host-only模式的让oracle virtual box提供一个虚拟的网关。具体步骤:

          -
            -
          1. 先关闭计算机 sudo poweroff

            -
          2. -
          3. 打开virtualbox主界面,依次点击屏幕左上角virtualbox->偏好设置->网络->点击右侧添加图标添加一个Host-Only网络vboxnet0,再设置参数值

            -主机虚拟网络界面(A)
            -IPv4地址(I):192.168.56.1
            -IPv4网络掩码(M):255.255.255.0
            -IPv6地址(P):空
            -IPv6网络掩码长度(L):0
            -DHCP服务器(D)
            -选择启动服务器
            -服务器地址(r):192.168.56.100
            -服务器网络掩码(M):255.255.255.0
            -最小地址(L):192.168.56.254
            -最大地址(U):192.168.56.254
            -
            -截图:image

            -
          4. -
          5. 配置网卡1。选中你刚新建的虚拟机,右键设置->网络->网卡1->点击启动网络连接(E)

            -连接方式(A):仅主机(Host-Only)适配器
            -界面名称(N):vboxnet0(此处需要注意,如果没有进行步骤1那么这里可能无法选择,整个设置流程会提示有错误而无法继续)
            -高级
            -控制芯片(T):xxx
            -混杂模式(P):拒绝
            -MAC地址(M):系统随机即可
            -接入网线(C):选中
            -

            -
          6. -
          7. 配置网卡2。点击网卡2->点击启动网络连接(E)

            -连接方式(A):网络地址装换(NAT)
            -界面名称(N):
            -高级(d)
            -控制芯片(T):xxx
            -混杂模式(P):拒绝
            -MAC地址(M):系统随机即可
            -接入网线(C):选中
            -

            -
          8. -
          9. 配置网络。启动操作系统,使用root用户进行网络配置。

            - -
            cd /etc/sysconfig/network-scripts/
            -cp ifcfg-eth0 ifcfg-eth1
            -

            配置ifcfg-eth0,ifcfg-eth1两个文件与virtual box 中的网卡mac值一一对应, ONBOOT=yes开机启动。并将ifcfg-eth1中的网卡名称eth0改为eth1,再重启网路服务。

            - -
            service network start
            -

            其中eth0的网关信息比较多,需要根据情况具体配置。如,这里使用eth0为host-only模式,eth1为nat模式,eth0为固定ip,eth1为开机自动获取ip。可参考如下:

            -
            -[root@master11 network-scripts]# cat ifcfg-eth0
            -DEVICE=eth0
            -HWADDR=08:00:27:55:99:EA(必须和virtual box 中的mac地址一致)
            -TYPE=Ethernet
            -UUID=dc6511c2-b5bb-4ccc-9775-84679a726db3(没有可不填)
            -ONBOOT=yes
            -NM_CONTROLLED=yes
            -BOOTPROTO=static
            -NETMASK=255.255.255.0
            -BROADCAST=192.168.56.255
            -IPADDR=192.168.56.11
            -

            [root@master11 network-scripts]# cat ifcfg-eth1 -DEVICE=eth1 -HWADDR=08:00:27:13:36:C3 -TYPE=Ethernet -UUID=b8f8485e-b731-4b64-8363-418dbe34880d -ONBOOT=yes -NM_CONTROLLED=yes -BOOTPROTO=dhcp -

            -
          10. -
          -

          4.4 检查机器名称

          -

          检查机器名称。修改后,重启生效。

          - -
             cat /etc/sysconfig/network
          -

          这里期望得机器名称信息是:

          -
          - NETWORKING=yes
          - HOSTNAME=master11
          -
          -

          4.5 建立hadoop用户组和用户

          -

          新建hadoop用户组和用户(以下步骤如无特殊说明默认皆使用hadoop)

          - -
          groupadd hadoop
          -useradd hadoop -g hadoop
          -passwd hadoop
          -

          4.6 配置sudo权限

          -

          CentOS普通用户增加sudo权限的简单配置
          -查看sudo是否安装:

          - -
          rpm -qa|grep sudo
          -

          修改/etc/sudoers文件,修改命令必须为visudo才行

          - -
          visudo -f /etc/sudoers
          -

          在root ALL=(ALL) ALL 之后增加

          - -
          hadoop ALL=(ALL) ALL
          -Defaults:hadoop timestamp_timeout=-1,runaspw
          -
          -增加普通账户hadoop的sudo权限
          -timestamp_timeout=-1 只需验证一次密码,以后系统自动记忆
          -runaspw  需要root密码,如果不加默认是要输入普通账户的密码
          -

          修改普通用户的.bash_profile文件(vi /home/hadoop/.bash_profile),在PATH变量中增加 -/sbin:/usr/sbin:/usr/local/sbin:/usr/kerberos/sbin

          -

          4.7 安装java

          -

          使用hadoop用户sudo rpm -ivh jdk-7-linux-x64.rpm进行安装jdk7。 -配置环境变量参考CentOS-6.3安装配置JDK-7

          -

          4.8 同步服务

          -

          安装时间同步服务sudo yum install -y ntp -设置同步服务器 sudo ntpdate us.pool.ntp.org

          -

          4.9 关闭防火墙

          -

          hadoop使用的端口太多了,图省事,关掉。chkconfig iptables off。需要重启。

          -

          4.10 克隆

          -

          使用虚拟机进行克隆2个datanode节点。配置网卡(参见第五步)。配置主机名(参见第六步)。配置hosts,最好也包括宿主机(sudo vi /etc/hosts

          -
          -192.168.56.11 master11
          -192.168.56.12 slave12
          -192.168.56.14 slave14
          -
          -

          同步时间

          - -
          sudo ntpdate us.pool.ntp.org
          -

          重启服务service network start。可能出现错误参见device eth0 does not seem to be present, delaying initialization

          -

          4.11 配置ssh

          +

          在 Hadoop 中可以通过 MapReduce,Pig,hive,Cascading编程进行大型数据集间的连接操作。连接操作如果由 Mapper 执行,则称为“map端连接”;如果由 Reduce 执行,则称为“Reduce端连接”。

          +

          连接操作的具体实现技术取决于数据集的规模以及分区方式。
          +若一个数据集很大而另一个数据集很小,以至于可以分发到集群中的每一个节点之中,则可以执行一个 MapReduce 作业,将各个数据集的数据放到一起,从而实现连接。
          +若两个数据规模均很大,没有哪个数据集可以完全复制到集群的每个节点,可以使用 MapReduce 作业进行连接,使用 Map 端连接还是 Reduce 端连接取决于数据的组织方式。

          +

          Map端连接将所有的工作在 map 中操作,效率高但是不通用。而 Reduce 端连接利用了 shuff 机制,进行连接,效率不高。

          +

          DistributedCache 能够在任务运行过程中及时地将文件和存档复制到任务节点进行本地缓存以供使用。各个文件通常只复制到一个节点一次。可用 api 或者命令行在需要的时候将本地文件添加到 hdfs 文件系统中。

          +

          本文中的示例 出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件。

          +

          Map端连接

          +

          Map 端联接是指数据到达 map 处理函数之前进行合并的。它要求 map 的输入数据必须先分区并以特定的方式排序。各个输入数据集被划分成相同数量的分区,并均按相同的键排序(连接键)。同一键的所有输入纪录均会放在同一个分区。以满足 MapReduce 作业的输出。

          +

          若作业的 Reduce 数量相同、键相同、输入文件是不可切分的,那么 map 端连接操作可以连接多个作业的输出。

          +

          在 Map 端连接效率比 Reduce 端连接效率高(Reduce端Shuff耗时),但是要求比较苛刻。

          +

          基本思路

            -
          1. 单机ssh配置并回环测试 -
            ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa   
            -chmod 700 ~/.ssh
            -cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
            -chmod 600 ~/.ssh/authorized_keys
            -
            输入ssh localhost不用输入密码直接登陆,表明ssh配置成功。(若未重新权限分配,可能无法实现ssh免密码登陆的效果。浪费了我不少时间。)
          2. -
          3. 配置master和slave间的ssh -在slave12上执行 -
            scp hadoop@master11:~/.ssh/id_dsa.pub ./master_dsa.pub
            -cat master_dsa.pub >>authorized_keys
            -
            在master上执行ssh slave12若,不输入密码直接登陆,配置即通过。
          4. -
          5. 相同的步骤需要也在slave14和master11上重复一遍。
          6. +
          7. 将需要 join 的两个文件,一个存储在 HDFS 中,一个使用 DistributedCache.addCacheFile() 将需要 join 另一个文件加入到所有 Map 的缓存里(DistributedCache.addCacheFile() 需要在作业提交前设置);
          8. +
          9. 在 Map 函数里读取该文件,进行 Join;
          10. +
          11. 将结果输出到 reduce 端;
          -

          机器环境确认无误后可以轻松的安装hadoop。

          -

          五、hadoop1.2.1 安装与配置

          -

          经过了以上步骤准备Hadoop1.2.1的环境搭建就相对容易多了。此处就仅需要解压缩安装并配置Hadoop,再验证是否正常便可大功告成。

          -

          5.1 安装

          +

          使用步骤

            -
          1. 重启master11,准备工作环境目录 -
            cd ~/
            -mkdir env
            -
          2. -
          3. 解压Hadoop 1.2.1 tar -
            tar -zxvf hadoop-1.2.1.tar.gz -C ~/env/
            -
          4. -
          5. 建立软链接 -
            ln -s hadoop-1.2.1/ hadoop
            -
          6. -
          7. 配置环境变量(vi ~/.bashrc -
            export JAVA_HOME=/usr/java/jdk1.7.0_45
            -source ~/.bashrc
            -
          8. -
          9. 同步.bashrc
            scp ~/.bashrc hadoop@slave12:~/
            -scp ~/.bashrc hadoop@slave14:~/
            -
          10. -
          11. 创建数据文件存放路径。主要便于管理Hadoop的数据文件。 -
            cd /home/hadoop/env
            -mkdir data
            -mkdir data/tmp
            -mkdir data/name
            -mkdir data/data
            -chmod 755 data/data/
            -mkdir mapreduce
            -mkdir mapreduce/system
            -mkdir mapreduce/local
            -
            效果如下 -
            -├── env
            -│   ├── data
            -│   │   ├── data
            -│   │   ├── name
            -│   │   └── tmp
            -│   ├── hadoop -> hadoop-1.2.1/
            -│   ├── hadoop-1.2.1
            -│   │   ├── bin
            -│   │   ├── build.xml
            -│   └── mapreduce
            -│       ├── local
            -│       └── system
          12. +
          13. 在 HDFS 中上传文件(文本文件、压缩文件、jar包等);
          14. +
          15. 调用相关API添加文件信息;
          16. +
          17. task运行前直接调用文件读写API获取文件;
          -

          -

          5.2 配置

          +

          Reduce端Join

          +

          reduce 端联接比 map 端联接更普遍,因为输入的数据不需要特定的结构;效率低(所有数据必须经过shuffle过程)。

          +

          基本思路

            -
          1. 配置 conf/core-site.xml -指定hdfs协议下的存储和临时目录

            - -
            <property>
            -  <name>fs.default.name</name>
            -  <value>hdfs://master11:9000</value>
            -</property>
            -<property>
            -  <name>hadoop.tmp.dir</name>
            -  <value>/home/${user.name}/env/data/tmp</value>
            -</property>
            -
          2. -
          3. 配置 conf/hdfs-site.xml -配置关于hdfs相关的配置。这里将原有默认复制3个副本调整为2个。学习时可根据需求适当调整。

            - -
            <property>
            - <name>dfs.name.dir</name>
            - <value>/home/${user.name}/env/data/name</value>
            -</property>
            -<property>
            - <name>dfs.data.dir</name>
            - <value>/home/${user.name}/env/data/data</value>
            -</property>
            -<property>
            - <name>dfs.replication</name>
            -  <value>2</value>
            -</property>
            -<property>
            - <name>dfs.web.ugi</name>
            - <value>hadoop,supergroup</value>
            - <final>true</final>
            - <description>The user account used by the web interface. Syntax: USERNAME,GROUP1,GROUP2, ……</description>
            -</property>
            -
          4. -
          5. 配置 conf/mapred-site.xml

            - -
            <property>
            - <name>mapred.job.tracker</name>
            - <value>master11:9001</value>
            -</property>
            -<property>
            - <name>mapred.system.dir</name>
            - <value>/home/${user.name}/env/mapreduce/system</value>
            -</property>
            -<property>
            - <name>mapred.local.dir</name>
            - <value>/home/${user.name}/env/mapreduce/local</value>
            -</property>
            -
          6. -
          7. 配置masters

            - -
            vi masters
            -

            写为

            - -
            master11
            -
          8. -
          9. 配置slaves

            - -
            vi slaves
            -

            写为

            - -
            slave12
            -slave14
            -
          10. -
          11. 同步hadoop到子节点

            - -
            scp -r ~/env/ hadoop@slave12:~/
            -scp -r ~/env/ hadoop@slave14:~/
            -
          12. -
          -

          5.3 启动hadoop

          -
            -
          1. 在主节点上格式化namenode

            - -
            ./bin/hadoop namenode -format
            -
          2. -
          3. 在主节点上启动Hadoop

            - -
            ./bin/start-all.sh
            -
          4. -
          -

          5.4 检查运行状态

          -
            -
          1. 通过web查看Hadoop状态

            -
            -http://192.168.56.11:50030/jobtracker.jsp
            -http://192.168.56.11:50070/dfshealth.jsp
            -
            -
          2. -
          3. 验证Hadoop mapredurce -执行hadoop jar hadoop-xx-examples.jar 验证jobtracker和tasktracker

            - -
            ./bin/hadoop jar hadoop-0.16.0-examples.jar wordcount input output
            -

            可wordcount参考Hadoop集群(第6期)_WordCount运行详解

            -
          4. +
          5. Map 端读取所有文件,并在输出的内容里加上标识代表数据是从哪个文件里来的;
          6. +
          7. 在 reduce 处理函数里,对按照标识对数据进行保存;
          8. +
          9. 然后根据 Key 的 Join 来求出结果直接输出;
          -

          六、常见错误

          -
            -
          • expected: rwxr-xr-x, while actual: rwxrwxr-x -WARN org.apache.hadoop.hdfs.server.datanode.DataNode: Invalid directory in dfs.data.dir: Incorrect permission for /home/hadoop/env/data/data, expected: rwxr-xr-x, while actual: rwxrwxr-x
            -解决方案:chmod 755 /home/hadoop/env/data/data

            -
          • -
          • 节点之间不能通信 -java.io.IOException: File xxx/jobtracker.info could only be replicated to 0 nodes, instead of 1
            -java.net.NoRouteToHostException: No route to host
            -解决方案:关闭iptables,sudo /etc/init.d/iptables stop

            -
          • -
          • got exception trying to get groups for user webuser

            -
            -org.apache.hadoop.util.Shell$ExitCodeException: id: webuser:无此用户
            -     at org.apache.hadoop.util.Shell.runCommand(Shell.java:255)
            -     at org.apache.hadoop.util.Shell.run(Shell.java:182)
            -
            -

            解决方案: -在hdfs-site.xml文件中添加

            - -
            <property>
            - <name>dfs.web.ugi</name>
            - <value>hadoop,supergroup</value>
            - <final>true</final>
            - <description>The user account used by the web interface.Syntax: USERNAME,GROUP1,GROUP2, ……
            - </description>
            -</property>
            -

            加上这个配置。可以解决了

            -
            -value  第一个为你自己搭建hadoop的用户名,第二个为用户所属组因为默认  
            -
            -

            web访问授权是webuser用户。访问的时候。我们一般用户名不是webuser所有要覆盖掉默认的webuser

            -
          • -
          -

          七、附录

          - +

          示例程序

          +

          使用 MapReduce map 端join 或者 reduce 端 join 实现如下两张表 emp, dep 中的 SQL 联合查询的数据效果。

          +
          Table EMP:(新建文件EMP,第一行属性名不要)
          +----------------------------------------
          +Name      Sex      Age     DepNo
          +zhang      male     20           1     
          +li              female  25           2
          +wang       female  30           3
          +zhou        male     35           2
          +----------------------------------------
          +Table Dep:(新建文件DEP,第一行属性名不要)
          +DepNo     DepName
          +     1            Sales
          +     2            Dev
          +     3            Mgt
          +------------------------------------------------------------     
          +SQL:
          +select name,sex ,age, depName from emp inner join DEP on EMP.DepNo = Dep.DepNo
          +----------------------------------------
          +实现效果:
          +$ ./bin/hadoop fs -cat /reduceSideJoin/output11/part-r-00000
          +zhang male 20 sales
          +li female 25 dev
          +wang female 30 dev
          +zhou male 35 dev
          +

          Map 端 Join 的例子:TestMapSideJoin
          +Reduce 端 Join 的例子:TestReduceSideJoin

          -

          编译hadoop 1.2.1 Hadoop-eclipse-plugin插件

          +

          Hadoop MapReduce 计数器

          - - | 评论 + + | 评论

          -

          编译hadoop1.x.x版本的eclipse插件为何如此繁琐?

          -

          个人理解,ant的初衷是打造一个本地化工具,而编译hadoop插件的资源间的依赖超出了这一目标。导致我们在使用ant编译的时候需要手工去修改配置。那么自然少不了设置环境变量、设置classpath、添加依赖、设置主函数、javac、jar清单文件编写、验证、部署等步骤。

          -

          那么我们开始动手

          -

          主要步骤如下

          +

          计数器是一种收集系统信息有效手段,用于质量控制或应用级统计。可辅助诊断系统故障。计数器可以比日志更方便的统计事件发生次数。

          +

          内置计数器

          +

          Hadoop 为每个作业维护若干内置计数器,主要用来记录作业的执行情况。

          +

          内置计数器包括

            -
          • 设置环境变量
          • -
          • 设置ant初始参数
          • -
          • 调整java编译参数
          • -
          • 设置java classpath
          • -
          • 添加依赖
          • -
          • 修改META-INF文件
          • -
          • 编译打包、部署、验证
          • +
          • MapReduce 框架计数器(Map-Reduce Framework)
          • +
          • 文件系统计数器(FielSystemCounters)
          • +
          • 作业计数器(Job Counters)
          • +
          • 文件输入格式计数器(File Output Format Counters)
          • +
          • 文件输出格式计数器(File Input Format Counters)
          -

          具体操作

          -
            -
          1. 设置语言环境

            - -
            $ export LC_ALL=en
            -
          2. -
          3. 设置ant初始参数
            -修改build-contrib.xml文件

            - -
            $ cd /hadoop-1.2.1/src/contrib
            -$ vi build-contrib.xml
            -

            编辑并修改hadoop.root值为实际hadoop解压的根目录

            - -
            <property name="hadoop.root" location="/Users/kangfoo-mac/study/hadoop-1.2.1"/>
            -

            添加eclipse依赖

            - -
            <property name="eclipse.home" location="/Users/kangfoo-mac/work/soft/eclipse-standard-kepler-SR1-macosx-cocoa" />
            -

            设置版本号

            - -
            <property name="version" value="1.2.1"/>
            -
          4. -
          5. 调整java编译设置
            -启用javac.deprecation

            - -
            $ cd /hadoop-1.2.1/src/contrib
            -$ vi build-contrib.xml
            -


            - -
            <property name="javac.deprecation" value="off"/>
            -

            改为

            - -
            <property name="javac.deprecation" value="on"/>
            -
          6. -
          7. ant 1.8+ 版本需要额外的设置javac includeantruntime=“on” 参数

            - -
            <!-- ====================================================== -->
            -<!-- Compile a Hadoop contrib's files                       -->
            -<!-- ====================================================== -->
            -<target name="compile" depends="init, ivy-retrieve-common" unless="skip.contrib">
            -<echo message="contrib: ${name}"/>
            -<javac
            - encoding="${build.encoding}"
            - srcdir="${src.dir}"
            - includes="**/*.java"
            - destdir="${build.classes}"
            - debug="${javac.debug}"
            - deprecation="${javac.deprecation}"
            - includeantruntime="on">
            - <classpath refid="contrib-classpath"/>
            -</javac>
            -</target> 
            -
          8. -
          9. 修改编译hadoop插件 classpath

            - -
            $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
            -$ vi build.xml
            -

            添加 文件路径 hadoop-jars

            - -
            <path id="hadoop-jars">
            -  <fileset dir="${hadoop.root}/">
            -    <include name="hadoop-*.jar"/>
            -  </fileset>
            -</path>
            -

            将hadoop-jars 添加到classpath

            - -
            <path id="classpath">
            -  <pathelement location="${build.classes}"/>
            -  <pathelement location="${hadoop.root}/build/classes"/>
            -  <path refid="eclipse-sdk-jars"/>
            -  <path refid="hadoop-jars"/>
            -</path> 
            -
          10. -
          11. 修改或添加额外的jar依赖
            -因为我们根本都没有直接编译过hadoop,所以就直接使用${HADOOP_HOME}/lib下的资源.需要注意,这里将依赖jar的版本后缀去掉了。
            -同样还是在hadoop-1.2.1/src/contrib/eclipse-plugin/build.xml文件中修改或添加

            - -
            $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
            -$ vi build.xml
            -

            找到 <!-- Override jar target to specify manifest --> 修改target name为 jar 中的 copy file 的路径,具体如下:

            - -
            <copy file="${hadoop.root}/hadoop-core-${version}.jar" tofile="${build.dir}/lib/hadoop-core.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/commons-cli-${commons-cli.version}.jar"  tofile="${build.dir}/lib/commons-cli.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/commons-configuration-1.6.jar"  tofile="${build.dir}/lib/commons-configuration.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/commons-httpclient-3.0.1.jar"  tofile="${build.dir}/lib/commons-httpclient.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/commons-lang-2.4.jar"  tofile="${build.dir}/lib/commons-lang.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/jackson-core-asl-1.8.8.jar"  tofile="${build.dir}/lib/jackson-core-asl.jar" verbose="true"/>
            -<copy file="${hadoop.root}/lib/jackson-mapper-asl-1.8.8.jar"  tofile="${build.dir}/lib/jackson-mapper-asl.jar" verbose="true"/>
            -
          12. -
          13. 修改 jar 清单文件

            - -
            cd ./hadoop-1.2.1/src/contrib/eclipse-plugin/META-INF
            -vi MANIFEST.MF
            -

            找到这个文件的Bundle-ClassPath这一行,然后,修改成

            - -
            Bundle-ClassPath: classes/,lib/commons-cli.jar,lib/commons-httpclient.jar,lib/hadoop-core.jar,lib/jackson-mapper-asl.jar,lib/commons-configuration.jar,lib/commons-lang.jar,lib/jackson-core-asl.jar
            -

            请保证上述字符占用一行,或者满足osgi bundle 配置文件的换行标准语法也行的。省事就直接写成一行,搞定。

            -
          14. -
          15. 新建直接打包并部署jar到eclipse/plugin目录的target

            - -
            cd hadoop-1.2.1/src/contrib/eclipse-plugin
            -vi build.xml
            -

            添加target直接将编译的插件拷贝到eclipse插件目录

            - -
            <target name="deploy" depends="jar" unless="skip.contrib"> 
            -<copy file="${build.dir}/hadoop-${name}-${version}.jar" todir="${eclipse.home}/plugins" verbose="true"/> </target>
            -

            将ant默认target default=“java"改为default=“deploy”

            - -
            <project default="deploy" name="eclipse-plugin">
            -
          16. -
          17. 编译并启动eclipse验证插件

            - -
            ant -f ./hadoop-1.2.1/src/contrib/eclipse-plugin/build.xml
            -

            启动eclipse,新建Map/Reduce Project,配置hadoop location.验证插件完全分布式的插件配置截图和core-site.xml端口配置

            -
          18. -
          19. 效果图 -image

            -
          20. -
          -

          image

          -

          相关源文件

          + + +

          计数器由其关联的 task 进行维护,定期传递给 tasktracker,再由 tasktracker 传给 jobtracker。因此,计数器能够被全局地聚集。内置计数器实际由 jobtracker 维护,不必在整个网络发送。

          +

          一个任务的计数器值每次都是完整传输的,仅当一个作业执行成功之后,计数器的值才完整可靠的。

          +

          自定义Java计数器

          +

          MapReduce 允许用户自定义计数器,MapReduce 框架将跨所有 map 和 reduce 聚集这些计数器,并在作业结束的时候产生一个最终的结果。

          +

          计数器的值可以在 mapper 或者 reducer 中添加。多个计数器可以由一个 java 枚举类型来定义,以便对计数器分组。一个作业可以定义的枚举类型数量不限,个个枚举类型所包含的数量也不限。

          +

          枚举类型的名称即为组的名称,枚举类型的字段即为计数器名称。

          +

          在 TaskInputOutputContext 中的 counter

          +
           public Counter getCounter(Enum<?> counterName) {
          +    return reporter.getCounter(counterName);
          +  }
          +  public Counter getCounter(String groupName, String counterName) {
          +    return reporter.getCounter(groupName, counterName);
          +  }
          +

          计数器递增

          +

          org.apache.hadoop.mapreduce.Counter类

          +
            public synchronized void increment(long incr) {
          +    value += incr;
          +  }
          +

          计数器使用

          -

          在此非常感谢kinuxroot这位博主的的博文参考。

          -
          -
          -
          -
          -

          Mac Java乱码 Maven OOM 异常

          - -

          - - - - - | 评论 -

          -
          - -

          在中文环境下苹果电脑中的 java,javac 默认以GBK编码输出信息到控制台,终端terminal默认以UTF-8编码,就出现了编码错误。

          -

          可如下2种方式修改:

          -
            -
          1. 更改系统语言环境
            export LC_ALL=en 或者 export LANG=zh_CN.UTF-8
            -
          2. -
          3. 指定输出编码方式
            javac -Dfile.encoding=UTF-8
            -
          4. -
          -

          但在有maven的情况下还需要这么设置:先调整JVM大小,避免工程太大中途出现OOM,再设置文件的编码。

          -
          export MAVEN_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
          +

          命令行方式示例

          +
          $ ./bin/hadoop job -counter  job_201402211848_0004 FileSystemCounters HDFS_BYTES_READ
          +177
          +

          自定义计数器

          +

          统计词汇行中词汇数超过2个或少于2个的行数。 源代码: TestCounter.javaTestCounter.java

          +

          输入数据文件值 counter.txt:

          +
          hello world
          +hello
          +hello world 111
          +hello world 111 222
          +

          执行参数

          +
          hdfs://master11:9000/counter/input/a.txt hdfs://master11:9000/counter/output1
          +

          计数器统计(hadoop eclipse 插件执行)结果:

          +
          2014-02-21 00:03:38,676 INFO  mapred.JobClient (Counters.java:log(587)) -   ERROR_COUNTER
          +2014-02-21 00:03:38,677 INFO  mapred.JobClient (Counters.java:log(589)) -     Above_2=2
          +2014-02-21 00:03:38,677 INFO  mapred.JobClient (Counters.java:log(589)) -     BELOW_2=1
           
          @@ -927,19 +578,34 @@

          近期文章

        18. - Hadoop1.x Wordcount分析 + Hadoop MapReduce 学习参考博客汇总 +
        19. +
        20. + Hadoop Pipes & Streaming +
        21. +
        22. + Hadoop MapReduce Sort +
        23. +
        24. + Hadoop MapReduce Join +
        25. +
        26. + Hadoop MapReduce 计数器 +
        27. +
        28. + Hadoop MapReduce RecordReader 组件
        29. - Hadoop1.x 命令手册列举 + Hadoop MapReduce Partitioner 组件
        30. - 编译hadoop 2.x Hadoop-eclipse-plugin插件 + Hadoop MapReduce Combiner 组件
        31. - 在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境 + Hadoop MapReduce 类型与格式
        32. - 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件 + Hadoop MapReduce 工作机制
        33. diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 0000000..efd2421 --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,1240 @@ + + + + + + + kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
          +

          kangfoo's blog

          +

          工作学习笔记,生活掠影。

          +
          +
          + +
          +
          +
          +
          +
          +

          Hadoop MapReduce RecordReader 组件

          + +

          + + + + + | 评论 +

          +
          + +

          由 RecordReader 决定每次读取以什么样的方式读取数据分片中的一条数据。Hadoop 默认的 RecordReader 是 LineRecordReader(TextInputFormat 的 getRecordReader() 方法返回即是 LineRecordReader。二进制输入 SequenceFileInputFormat 的 getRecordReader() 方法返回即是SequenceFileRecordReader。)。LineRecordReader是用每行的偏移量作为 map 的 key,每行的内容作为 map 的 value;

          +

          它可作用于,自定义读取每一条记录的方式;自定义读入 key 的类型,如希望读取的 key 是文件的路径或名字而不是该行在文件中的偏移量。

          +

          自定义RecordReader一般步骤

          +
            +
          1. 继承抽象类 RecordReader,实现 RecordReader 的实例;
          2. +
          3. 实现自定义 InputFormat 类,重写 InputFormat 中 createRecordReader() 方法,返回值是自定义的 RecordReader 实例; +(3)配置 job.setInputFormatClass() 设置自定义的 InputFormat 类型;
          4. +
          +

          TextInputFormat类源代码理解

          +

          源码见 org.apache.mapreduce.lib.input.TextInputFormat 类(新API);

          +

          Hadoop 默认 TextInputFormat 使用 LineRecordReader。具体分析见注释。

          +
            public RecordReader<LongWritable, Text> 
          +    createRecordReader(InputSplit split,
          +                       TaskAttemptContext context) {
          +    return new LineRecordReader();
          +  }
          +// --> LineRecordReader
          + public void initialize(InputSplit genericSplit,
          +                         TaskAttemptContext context) throws IOException {
          +    FileSplit split = (FileSplit) genericSplit;
          +    Configuration job = context.getConfiguration();
          +    this.maxLineLength = job.getInt("mapred.linerecordreader.maxlength",
          +                                    Integer.MAX_VALUE);
          +    start = split.getStart();  // 当前分片在整个文件中的起始位置
          +    end = start + split.getLength(); // 当前分片,在整个文件的位置
          +    final Path file = split.getPath();
          +    compressionCodecs = new CompressionCodecFactory(job);// 压缩
          +    codec = compressionCodecs.getCodec(file);
          +//
          +    // open the file and seek to the start of the split
          +    FileSystem fs = file.getFileSystem(job);
          +    FSDataInputStream fileIn = fs.open(split.getPath()); // 获取 FSDataInputStream
          +//
          +    if (isCompressedInput()) {
          +      decompressor = CodecPool.getDecompressor(codec);
          +      if (codec instanceof SplittableCompressionCodec) {
          +        final SplitCompressionInputStream cIn =
          +          ((SplittableCompressionCodec)codec).createInputStream(
          +            fileIn, decompressor, start, end,
          +            SplittableCompressionCodec.READ_MODE.BYBLOCK);
          +        in = new LineReader(cIn, job); //一行行读取
          +        start = cIn.getAdjustedStart(); // 可能跨分区读取
          +        end = cIn.getAdjustedEnd();// 可能跨分区读取
          +        filePosition = cIn;
          +      } else {
          +        in = new LineReader(codec.createInputStream(fileIn, decompressor),
          +            job);
          +        filePosition = fileIn;
          +      }
          +    } else {
          +      fileIn.seek(start);//  调整到文件起始偏移量
          +      in = new LineReader(fileIn, job); 
          +      filePosition = fileIn;
          +    }
          +    // If this is not the first split, we always throw away first record
          +    // because we always (except the last split) read one extra line in
          +    // next() method.
          +    if (start != 0) {
          +      start += in.readLine(new Text(), 0, maxBytesToConsume(start));
          +    }
          +    this.pos = start; // 在当前分片的位置
          +  }
          +//  --> getFilePosition() 指针读取到哪个位置
          +// filePosition 为 Seekable 类型
          +  private long getFilePosition() throws IOException {
          +    long retVal;
          +    if (isCompressedInput() && null != filePosition) {
          +      retVal = filePosition.getPos();
          +    } else {
          +      retVal = pos;
          +    }
          +    return retVal;
          +  }
          +//
          +// --> nextKeyValue() 
          +public boolean nextKeyValue() throws IOException {
          +    if (key == null) {
          +      key = new LongWritable();
          +    }
          +    key.set(pos);
          +    if (value == null) {
          +      value = new Text();
          +    }
          +    int newSize = 0;
          +    // We always read one extra line, which lies outside the upper
          +    // split limit i.e. (end - 1)
          +    // 预读取下一条纪录
          +    while (getFilePosition() <= end) {
          +      newSize = in.readLine(value, maxLineLength,
          +          Math.max(maxBytesToConsume(pos), maxLineLength));
          +      if (newSize == 0) {
          +        break;
          +      }
          +      pos += newSize; // 下一行的偏移量
          +      if (newSize < maxLineLength) {
          +        break;
          +      }
          +//
          +      // line too long. try again
          +      LOG.info("Skipped line of size " + newSize + " at pos " + 
          +               (pos - newSize));
          +    }
          +    if (newSize == 0) {
          +      key = null;
          +      value = null;
          +      return false;
          +    } else {
          +      return true;
          +    }
          +  }
          +

          自定义 RecordReader 演示

          +

          假设,现有如下数据 10 ~ 70 需要利用自定义 RecordReader 组件分别计算数据奇数行和偶数行的数据之和。结果为:奇数行之和等于 160,偶数和等于 120。出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件。

          +

          数据:
          +10
          +20
          +30
          +40
          +50
          +60
          +70

          +

          源代码

          +

          TestRecordReader.java

          +

          数据准备

          +
          $ ./bin/hadoop fs -mkdir /inputreader
          +$ ./bin/hadoop fs -put ./a.txt /inputreader
          +$ ./bin/hadoop fs -lsr /inputreader
          +-rw-r--r--   2 hadoop supergroup         21 2014-02-20 21:04 /inputreader/a.txt
          +

          执行

          +
          $ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestRecordReader  /inputreader /inputreaderout1
          +##
          +$ ./bin/hadoop fs -lsr /inputreaderout1
          +-rw-r--r--   2 hadoop supergroup          0 2014-02-20 21:12 /inputreaderout1/_SUCCESS
          +drwxr-xr-x   - hadoop supergroup          0 2014-02-20 21:11 /inputreaderout1/_logs
          +drwxr-xr-x   - hadoop supergroup          0 2014-02-20 21:11 /inputreaderout1/_logs/history
          +-rw-r--r--   2 hadoop supergroup      16451 2014-02-20 21:11 /inputreaderout1/_logs/history/job_201402201934_0002_1392901901142_hadoop_TestRecordReader
          +-rw-r--r--   2 hadoop supergroup      48294 2014-02-20 21:11 /inputreaderout1/_logs/history/job_201402201934_0002_conf.xml
          +-rw-r--r--   2 hadoop supergroup         23 2014-02-20 21:12 /inputreaderout1/part-r-00000
          +-rw-r--r--   2 hadoop supergroup         23 2014-02-20 21:12 /inputreaderout1/part-r-00001
          +##
          +$ ./bin/hadoop fs -cat /inputreaderout1/part-r-00000
          +偶数行之和:  120
          +##
          +$ ./bin/hadoop fs -cat /inputreaderout1/part-r-00001
          +奇数行之和:  160
          +
          +
          +
          +
          +

          Hadoop MapReduce Partitioner 组件

          + +

          + + + + + | 评论 +

          +
          + +

          Partitioner 过程发生在循环缓冲区发生溢写文件之后,merge 之前。可以让 Map 对 Key 进行分区,从而可以根据不同的 key 来分发到不同的 reducer 中去处理;

          +

          Hadoop默认的提供的是HashPartitioner。

          +

          可以自定义 key 的分发规则,自定义Partitioner:

          +
            +
          • 继承抽象类Partitioner,实现自定义的getPartition()方法;
          • +
          • 通过job.setPartitionerClass()来设置自定义的Partitioner;
          • +
          +

          Partitioner 类

          +

          旧api

          +
          public interface Partitioner<K2, V2> extends JobConfigurable {
          +  int getPartition(K2 key, V2 value, int numPartitions);
          +}
          +

          新api

          +
          public abstract class Partitioner<KEY, VALUE> {
          +  public abstract int getPartition(KEY key, VALUE value, int numPartitions);  
          +}
          +

          Partitioner应用场景演示

          +

          需求:利用 Hadoop MapReduce 作业 Partitioner 组件分别统计每种商品的周销售情况。源代码 TestPartitioner.java出自于 开源力量 LouisT 老师的开源力量培训课-Hadoop Development课件。 (可使用 PM2.5 数据代替此演示程序)

          +
            +
          • site1的周销售清单(a.txt,以空格分开):

            +
            shoes   20
            +hat 10
            +stockings   30
            +clothes 40
            +
          • +
          • site2的周销售清单(b.txt,以空格分开):

            +
            shoes   15
            +hat 1
            +stockings   90
            +clothes 80
            +
          • +
          • 汇总结果:

            +
            shoes     35
            +hat       11
            +stockings 120
            +clothes   120
            +
          • +
          • 准备测试数据

            +
            $ ./bin/hadoop fs -mkdir /testPartitioner/input
            +$ ./bin/hadoop fs -put a.txt /testPartitioner/input
            +$ ./bin/hadoop fs -put b.txt /testPartitioner/input
            +$ ./bin/hadoop fs -lsr /testPartitioner/input
            +-rw-r--r--   2 hadoop supergroup         52 2014-02-18 22:53 /testPartitioner/input/a.txt
            +-rw-r--r--   2 hadoop supergroup         50 2014-02-18 22:53 /testPartitioner/input/b.txt
            +
          • +
          • 执行 MapReduce 作业 +此处使用 hadoop jar 命令执行,eclipse 插件方式有一定的缺陷。(hadoop eclipse 执行出现java.io.IOException: Illegal partition for hat (1))

            +
            $ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestPartitioner /testPartitioner/input /testPartitioner/output10
            +
          • +
          • 结果。 四个分区,分别存储上述四种产品的总销量的统计结果值。

            +
            -rw-r--r--   2 hadoop supergroup          9 2014-02-19 00:18 /testPartitioner/output10/part-r-00000
            +-rw-r--r--   2 hadoop supergroup          7 2014-02-19 00:18 /testPartitioner/output10/part-r-00001
            +-rw-r--r--   2 hadoop supergroup         14 2014-02-19 00:18 /testPartitioner/output10/part-r-00002
            +-rw-r--r--   2 hadoop supergroup         12 2014-02-19 00:18 /testPartitioner/output10/part-r-00003
            +
          • +
          +
          +
          +
          +
          +

          Hadoop MapReduce Combiner 组件

          + +

          + + + + + | 评论 +

          +
          + +
          +

          combiner 作用是把一个 map 产生的多个 合并成一个新的 ,然后再将新的 作为 reduce 的输入;

          +

          combiner 函数在 map 函数与 reduce 函数之间,目的是为了减少 map 输出的中间结果,减少 reduce 复制 map 输出的数据,减少网络传输负载;

          +

          并不是所有情况下都能使用 Combiner 组件,它适用于对记录汇总的场景(如求和,平均数不适用)

          +

          什么时候运行 Combiner

          +
            +
          • 当 job 设置了 Combiner,并且 spill 的个数达到 min.num.spill.for.combine (默认是3)的时候,那么 combiner 就会 Merge 之前执行;
          • +
          • 但是有的情况下,Merge 开始执行,但 spill 文件的个数没有达到需求,这个时候 Combiner 可能会在Merge 之后执行;
          • +
          • Combiner 也有可能不运行,Combiner 会考虑当时集群的一个负载情况。
          • +
          +

          测试 Combinner 过程

          +

          代码 TestCombiner

          +
            +
          1. 以 wordcount.txt 为输入的词频统计

            +
            $ ./bin/hadoop fs -lsr /test3/input
            +drwxr-xr-x   - hadoop supergroup          0 2014-02-18 00:28 /test3/input/test
            +-rw-r--r--   2 hadoop supergroup        983 2014-02-18 00:28 /test3/input/test/wordcount.txt
            +-rw-r--r--   2 hadoop supergroup        626 2014-02-18 00:28 /test3/input/test/wordcount2.txt
            +
          2. +
          3. 不启用 Reducer (输出,字节变大)

            +
            drwxr-xr-x   - kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1
            +-rw-r--r--   3 kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1/_SUCCESS
            +-rw-r--r--   3 kangfoo-mac supergroup       1031 2014-02-18 00:29 /test3/output1/part-m-00000 (-m 没有 reduce 过程的中间结果,每个数据文件对应一个数据分片,每个分片对应一个map任务)
            +-rw-r--r--   3 kangfoo-mac supergroup        703 2014-02-18 00:29 /test3/output1/part-m-00001
            +

            结果如下(map过程并不合并相同key的value值):

            +
            drwxr-xr-x  1
            +-  1
            +hadoop  1
            +supergroup  1
            +0   1
            +2014-02-17  1
            +21:03   1
            +/home/hadoop/env/mapreduce  1
            +drwxr-xr-x  1
            +-  1
            +hadoop  1
            +
          4. +
          5. 启用 Reducer

            +
            drwxr-xr-x   - kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1
            +-rw-r--r--   3 kangfoo-mac supergroup          0 2014-02-18 00:29 /test3/output1/_SUCCESS
            +-rw-r--r--   3 kangfoo-mac supergroup       1031 2014-02-18 00:29 /test3/output1/part-m-00000
            +-rw-r--r--   3 kangfoo-mac supergroup        703 2014-02-18 00:29 /test3/output1/part-m-00001
            +drwxr-xr-x   - kangfoo-mac supergroup          0 2014-02-18 00:31 /test3/output2
            +-rw-r--r--   3 kangfoo-mac supergroup          0 2014-02-18 00:31 /test3/output2/_SUCCESS
            +-rw-r--r--   3 kangfoo-mac supergroup        705 2014-02-18 00:31 /test3/output2/part-r-00000
            +

            结果:

            +
            0:17:31,680 6
            +014-02-18   1
            +2014-02-17  11
            +2014-02-18  5
            +21:02   7
            +
          6. +
          7. 在日志或者 http://master11:50030/jobtracker.jsp 页面查找是否执行过 Combine 过程。 +日志截取如下:

            +
            2014-02-18 00:31:29,894 INFO  SPLIT_RAW_BYTES=233
            +2014-02-18 00:31:29,894 INFO  Combine input records=140
            +2014-02-18 00:31:29,894 INFO  Reduce input records=43
            +2014-02-18 00:31:29,894 INFO  Reduce input groups=42
            +2014-02-18 00:31:29,894 INFO  Combine output records=43
            +2014-02-18 00:31:29,894 INFO  Reduce output records=42
            +2014-02-18 00:31:29,894 INFO  Map output records=140
            +
          8. +
          +
          +
          +
          +
          +

          Hadoop MapReduce 类型与格式

          + +

          + + + + + | 评论 +

          +
          + +

          MapReduce 的 map和reduce函数的输入和输出是键/值对(key/value pair) 形式的数据处理模型。

          +

          MapReduce 的类型

          +

          Hadoop1.x MapReduce 有2套API.旧api偏向与接口,新api偏向与抽象类,如无特殊默认列举为旧的api作讨论.

          +

          在Hadoop的MapReduce中,map和reduce函数遵循如下格式:

          +
            +
          • map(K1, V1) –> list (K2, V2) // map:对输入分片数据进行过滤数据,组织 key/value 对等操作
          • +
          • combine(K2, list(V2)) –> list(K2, V2) // 在map端对输出进行预处理,类似 reduce。combine 不一定适用任何情况,如:对总和求平均数。选用。
          • +
          • partition(K2, V2) –> integer // 将中间键值对划分到一个 reduce 分区,返回分区索引号。实际上,分区单独由键决定(值是被忽略的),分区内的键会排序,相同的键的所有值会合成一个组(list(V2))
          • +
          • reduce(K2, list(V2)) –> list(K3, V3) // 每个 reduce 会处理具有某些特性的键,每个键上都有值的序列,是通过对所有 map 输出的值进行统计得来的,reduce 根据所有map传来的结果,最后进行统计合并操作,并输出结果。
          • +
          +

          旧api类代码

          +
          public interface Mapper<K1, V1, K2, V2> extends JobConfigurable, Closeable {  
          +  void map(K1 key, V1 value, OutputCollector<K2, V2> output, Reporter reporter) throws IOException;
          +}
          +//
          +public interface Reducer<K2, V2, K3, V3> extends JobConfigurable, Closeable {
          +  void reduce(K2 key, Iterator<V2> values, OutputCollector<K3, V3> output, Reporter reporter) throws IOException;
          +}
          +//
          +public interface Partitioner<K2, V2> extends JobConfigurable {
          +   int getPartition(K2 key, V2 value, int numPartitions);
          +}
          +

          新api类代码

          +
          public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
          +… …
          +  protected void map(KEYIN key, VALUEIN value, 
          +                     Context context) throws IOException, InterruptedException {
          +    context.write((KEYOUT) key, (VALUEOUT) value);
          +  }
          +… …
          +}
          +//
          +public class Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
          +… …
          + protected void reduce(KEYIN key, Iterable<VALUEIN> values, Context context
          +                        ) throws IOException, InterruptedException {
          +    for(VALUEIN value: values) {
          +      context.write((KEYOUT) key, (VALUEOUT) value);
          +    }
          +  }
          +… …
          +}
          +//
          +public interface Partitioner<K2, V2> extends JobConfigurable {
          +  int getPartition(K2 key, V2 value, int numPartitions);
          +}
          +

          默认的 partitioner 是 HashPartitioner,对键进行哈希操作以决定该记录属于哪个分区让 reduce 处理,每个分区对应一个 reducer 任务。总槽数 solt=集群中节点数 * 每个节点的任务槽。实际值应该比理论值要小,以空闲一部分在错误容忍是备用。

          +

          HashPartitioner的实现

          +
          public class HashPartitioner<K, V> extends Partitioner<K, V> {
          +    public int getPartition(K key, V value, int numReduceTasks) {
          +        return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
          +    }
          +}
          +

          hadooop1.x 版本中

          +
            +
          • 旧的api,map 默认的 IdentityMapper, reduce 默认的是 IdentityReducer
          • +
          • 新的api,map 默认的 Mapper, reduce 默认的是 Reducer
          • +
          +

          默认MapReduce函数实例程序

          +
          public class MinimalMapReduceWithDefaults extends Configured implements Tool {
          +    @Override
          +    public int run(String[] args) throws Exception {
          +        Job job = JobBuilder.parseInputAndOutput(this, getConf(), args);
          +        if (job == null) {
          +            return -1;
          +            }
          +        //
          +        job.setInputFormatClass(TextInputFormat.class);
          +        job.setMapperClass(Mapper.class);
          +        job.setMapOutputKeyClass(LongWritable.class);
          +        job.setMapOutputValueClass(Text.class);
          +        job.setPartitionerClass(HashPartitioner.class);
          +        job.setNumReduceTasks(1);
          +        job.setReducerClass(Reducer.class);
          +        job.setOutputKeyClass(LongWritable.class);
          +        job.setOutputValueClass(Text.class);
          +        job.setOutputFormatClass(TextOutputFormat.class);
          +        return job.waitForCompletion(true) ? 0 : 1;
          +        }
          +    //
          +    public static void main(String[] args) throws Exception {
          +        int exitCode = ToolRunner.run(new MinimalMapReduceWithDefaults(), args);
          +        System.exit(exitCode);
          +        }
          +}
          +

          输入格式

          +

          输入分片与记录

          +

          一个输入分片(input split)是由单个 map 处理的输入块,即每一个 map 只处理一个输入分片,每个分片被划分为若干个记录( records ),每条记录就是一个 key/value 对,map 一个接一个的处理每条记录,输入分片和记录都是逻辑的,不必将他们对应到文件上。数据分片由数据块大小决定的。

          +

          注意,一个分片不包含数据本身,而是指向数据的引用( reference )。

          +

          输入分片在Java中被表示为InputSplit抽象类

          +
          public interface InputSplit extends Writable {
          +  long getLength() throws IOException;
          +  String[] getLocations() throws IOException;
          +}
          +

          InputFormat负责创建输入分片并将它们分割成记录,抽象类如下:

          +
          public interface InputFormat<K, V> {
          +  InputSplit[] getSplits(JobConf job, int numSplits) throws IOException;
          +  RecordReader<K, V> getRecordReader(InputSplit split,
          +                                     JobConf job, 
          +                                     Reporter reporter) throws IOException;
          +}
          +

          客户端通过调用 getSpilts() 方法获得分片数目(怎么调到的?),在 TaskTracker 或 NodeManager上,MapTask 会将分片信息传给 InputFormat 的 +createRecordReader() 方法,进而这个方法来获得这个分片的 RecordReader,RecordReader 基本就是记录上的迭代器,MapTask 用一个 RecordReader 来生成记录的 key/value 对,然后再传递给 map 函数,如下步骤:

          +
            +
          1. jobClient调用getSpilts()方法获得分片数目,将numSplits作为参数传入,以参考。InputFomat实现有自己的getSplits()方法。
          2. +
          3. 客户端将他们发送到jobtracker
          4. +
          5. jobtracker使用其存储位置信息来调度map任务从而在tasktracker上处理分片数据
          6. +
          7. 在tasktracker上,map任务把输入分片传给InputFormat上的getRecordReader()方法,来获取分片的RecordReader。
          8. +
          9. map 用一个RecordReader来生成纪录的键值对。
          10. +
          11. RecordReader的next()方法被调用,知道返回false。map任务结束。
          12. +
          +

          MapRunner 类部分代码(旧api)

          +
          public class MapRunner<K1, V1, K2, V2>
          +    implements MapRunnable<K1, V1, K2, V2> {
          +… … 
          + public void run(RecordReader<K1, V1> input, OutputCollector<K2, V2> output,
          +                  Reporter reporter)
          +    throws IOException {
          +    try {
          +      // allocate key & value instances that are re-used for all entries
          +      K1 key = input.createKey();
          +      V1 value = input.createValue();
          +      //
          +      while (input.next(key, value)) {
          +        // map pair to output
          +        mapper.map(key, value, output, reporter);
          +        if(incrProcCount) {
          +          reporter.incrCounter(SkipBadRecords.COUNTER_GROUP, 
          +              SkipBadRecords.COUNTER_MAP_PROCESSED_RECORDS, 1);
          +        }
          +      }
          +    } finally {
          +      mapper.close();
          +    }
          +  }
          +……
          +}
          +

          FileInputFormat类

          +

          FileInputFormat是所有使用文件为数据源的InputFormat实现的基类,它提供了两个功能:一个定义哪些文件包含在一个作业的输入中;一个为输入文件生成分片的实现,把分片割成记录的作业由其子类来完成。

          +

          下图为InputFormat类的层次结构: +image

          +

          FileInputFormat 类输入路径

          +

          FileInputFormat 提供四种静态方法来设定 Job 的输入路径,其中下面的 addInputPath() 方法 addInputPaths() 方法可以将一个或多个路径加入路径列表,setInputPaths() 方法一次设定完整的路径列表(可以替换前面所设路 径)

          +
          public static void addInputPath(Job job, Path path);
          +public static void addInputPaths(Job job, String commaSeparatedPaths);
          +public static void setInputPaths(Job job, Path... inputPaths);
          +public static void setInputPaths(Job job, String commaSeparatedPaths);
          +

          如果需要排除特定文件,可以使用 FileInputFormat 的 setInputPathFilter() 设置一个过滤器: +public static void setInputPathFilter(Job job, Class<? extends PathFilter> filter); +它默认过滤隐藏文件中以”_“和”.“开头的文件

          +
            private static final PathFilter hiddenFileFilter = new PathFilter(){
          +      public boolean accept(Path p){
          +        String name = p.getName(); 
          +        return !name.startsWith("_") && !name.startsWith("."); 
          +      }
          +    }; 
          +

          FileInputFormat 类的输入分片

          +

          FileInputFormat 类一般分割超过 HDFS 块大小的文件。通常分片与 HDFS 块大小一样,然后分片大小也可以改变的,下面展示了控制分片大小的属性:

          +

          待补。 TODO

          +
          FileInputFormat computeSplitSize(long goalSize, long minSize,long blockSize) {
          +    return Math.max(minSize, Math.min(goalSize, blockSize));
          +}
          +

          即: +minimumSize < blockSize < maximumSize 分片的大小即为块大小。

          +

          重载 FileInputFormat 的 isSplitable() =false 可以避免 mapreduce 输入文件被分割。

          +

          小文件与CombineFileInputFormat

          +
            +
          1. CombineFileInputFormat 是针对小文件设计的,CombineFileInputFormat 会把多个文件打包到一个分片中,以便每个 mapper 可以处理更多的数据;减少大量小文件的另一种方法可以使用 SequenceFile 将这些小文件合并成一个或者多个大文件。

            +
          2. +
          3. CombineFileInputFormat 不仅对于处理小文件实际上对于处理大文件也有好处,本质上,CombineFileInputFormat 使 map 操作中处理的数据量与 HDFS 中文件的块大小之间的耦合度降低了

            +
          4. +
          5. CombineFileInputFormat 是一个抽象类,没有提供实体类,所以需要实现一个CombineFileInputFormat 具体 +类和 getRecordReader() 方法(旧的接口是这个方法,新的接口InputFormat中则是createRecordReader())

            +
          6. +
          +

          把整个文件作为一条记录处理

          +

          有时,mapper 需要访问问一个文件中的全部内容。即使不分割文件,仍然需要一个 RecordReader 来读取文件内容为 record 的值,下面给出实现这个功能的完整程序,详细解释见《Hadoop权威指南》。

          +

          文本处理

          +
            +
          1. TextInputFileFormat 是默认的 InputFormat,每一行就是一个纪录

            +
          2. +
          3. TextInputFileFormat 的 key 是 LongWritable 类型,存储该行在整个文件的偏移量,value 是每行的数据内容,不包括任何终止符(换行符和回车符),它是Text类型. +如下例 +On the top of the Crumpetty Tree
            +
            +The Quangle Wangle sat,
            +But his face you could not see,
            +On account of his Beaver Hat.
            +每条记录表示以下key/value对
            +(0, On the top of the Crumpetty Tree)
            +(33, The Quangle Wangle sat,)
            +(57, But his face you could not see,)
            +(89, On account of his Beaver Hat.

            +
          4. +
          5. 输入分片与 HDFS 块之间的关系:TextInputFormat 每一条纪录就是一行,很可能某一行跨数据库存放。

            +
          6. +
          +

          image

          +
            +
          1. KeyValueTextInputFormat。对下面的文本,KeyValueTextInputFormat 比较适合处理,其中可以通过 +mapreduce.input.keyvaluelinerecordreader.key.value.separator 属性设置指定分隔符,默认 +值为制表符,以下指定”→“为分隔符 +
            +line1→On the top of the Crumpetty Tree
            +line2→The Quangle Wangle sat,
            +line3→But his face you could not see,
            +line4→On account of his Beaver Hat.

            +
          2. +
          3. NLineInputFormat。如果希望 mapper 收到固定行数的输入,需要使用 NLineInputFormat 作为 InputFormat 。与 TextInputFormat 一样,key是文件中行的字节偏移量,值是行本身。

            +
          4. +
          +

          N 是每个 mapper 收到的输入行数,默认时 N=1,每个 mapper 会正好收到一行输入,mapreduce.input.lineinputformat.linespermap 属性控制 N 的值。以刚才的文本为例。 +如果N=2,则每个输入分片包括两行。第一个 mapper 会收到前两行 key/value 对:

          +

          (0, On the top of the Crumpetty Tree)
          +(33, The Quangle Wangle sat,)
          +另一个mapper则收到:
          +(57, But his face you could not see,)
          +(89, On account of his Beaver Hat.)

          +

          二进制输入

          +

          SequenceFileInputFormat +如果要用顺序文件数据作为 MapReduce 的输入,应用 SequenceFileInputFormat。key 和 value 顺序文件,所以要保证map输入的类型匹配

          +

          SequenceFileInputFormat 可以读 MapFile 和 SequenceFile,如果在处理顺序文件时遇到目录,SequenceFileInputFormat 类会认为值正在读 MapFile 数据文件。

          +

          SequenceFileAsTextInputFormat 是 SequenceFileInputFormat 的变体。将顺序文件(其实就是SequenceFile)的 key 和 value 转成 Text 对象

          +

          SequenceFileAsBinaryInputFormat是 SequenceFileInputFormat 的变体。将顺序文件的key和value作为二进制对象

          +

          多种输入

          +

          对于不同格式,不同表示的文本文件输出的处理,可以用 MultipleInputs 类里处理,它允许为每条输入路径指定 InputFormat 和 Mapper。

          +

          MultipleInputs 类有一个重载版本的 addInputPath()方法:

          +
            +
          • 旧api列举
            public static void addInputPath(JobConf conf, Path path, Class<? extends InputFormat> inputFormatClass) 
            +
          • +
          • 新api列举
            public static void addInputPath(Job job, Path path, Class<? extends InputFormat> inputFormatClass) 
            +
            在有多种输入格式只有一个mapper时候(调用Job的setMapperClass()方法),这个方法会很有用。
          • +
          +

          DBInputFormat

          +

          JDBC从关系数据库中读取数据的输入格式(参见权威指南)

          +

          输出格式

          +

          OutputFormat类的层次结构

          +

          image

          +

          文本输出

          +

          默认输出格式是 TextOutputFormat,它本每条记录写成文本行,key/value 任意,这里 key和value 可以用制表符分割,用 mapreduce.output.textoutputformat.separator 书信可以改变制表符,与TextOutputFormat 对应的输入格式是 KeyValueTextInputFormat。

          +

          可以使用 NullWritable 来省略输出的 key 和 value。

          +

          二进制输出

          +
            +
          • SequenceFileOutputFormat 将它的输出写为一个顺序文件,因为它的格式紧凑,很容易被压缩,所以易于作为 MapReduce 的输入
          • +
          • 把key/value对作为二进制格式写到一个 SequenceFile 容器中
          • +
          • MapFileOutputFormat 把 MapFile 作为输出,MapFile 中的 key 必需顺序添加,所以必须确保 reducer 输出的 key 已经排好序。
          • +
          +

          多个输出

          +
            +
          • MultipleOutputFormat 类可以将数据写到多个文件中,这些文件名称源于输出的键和值。MultipleOutputFormat是个抽象类,它有两个子类:MultipleTextOutputFormatMultipleSequenceFileOutputFormat 。它们是 TextOutputFormat 的和 SequenceOutputFormat 的多版本。

            +
          • +
          • MultipleOutputs 类 +用于生成多个输出的库,可以为不同的输出产生不同的类型,无法控制输出的命名。它用于在原有输出基础上附加输出。输出是制定名称的。

            +
          • +
          +

          MultipleOutputFormat和MultipleOutputs的区别

          +

          这两个类库的功能几乎相同。MultipleOutputs 功能更齐全,但 MultipleOutputFormat 对 目录结构和文件命令更多de控制。

          +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          特征 MultipleOutputFormat MultipleOutputs
          完全控制文件名和目录名
          不同输出有不同的键和值类型
          从同一作业的map和reduce使用
          每个纪录多个输出
          与任意OutputFormat一起使用 否,需要子类
          +

          延时输出

          +

          有些文件应用倾向于不创建空文件,此时就可以利用 LazyOutputFormat (Hadoop 0.21.0版本之后开始提供),它是一个封装输出格式,可以保证指定分区第一条记录输出时才真正的创建文件,要使用它,用JobConf和相关输出格式作为参数来调用 setOutputFormatClass() 方法.

          +

          Streaming 和 Pigs 支持 -LazyOutput 选项来启用 LazyOutputFormat功能。

          +

          数据库输出

          +

          参见 关系数据和 HBase的输出格式。

          +

          练习代码

          +

          代码路径 +https://github.com/kangfoo/hadoop1.study/blob/master/kangfoo/study.hdfs/src/main/java/com/kangfoo/study/hadoop1/mp/typeformat
          +使用 maven 打包之后用 hadoop jar 命令执行
          +步骤同 Hadoop example jar 类

          +
            +
          1. 使用 TextInputFormat 类型测试 wordcount +TestMapreduceInputFormat +上传一个文件

            +
            $ ./bin/hadoop fs -mkdir /test/input1
            +$ ./bin/hadoop fs -put ./wordcount.txt /test/input1
            +

            使用maven 打包 或者用eclipse hadoop 插件, 执行主函数时设置如下参数

            +
            hdfs://master11:9000/test/input1/wordcount.txt hdfs://master11:9000/numbers.seq hdfs://master11:9000/test/output5
            +

            没改过端口默认 namenode RPC 交互端口 8020 将上述的 9000 改成你自己的端口即可。
            +部分日志

            +
            ## 准备运行程序和测试数据
            +lrwxrwxrwx.  1 hadoop hadoop      86 2月  17 21:02 study.hdfs-0.0.1-SNAPSHOT.jar -> /home/hadoop/env/kangfoo.study/kangfoo/study.hdfs/target/study.hdfs-0.0.1-SNAPSHOT.jar
            +-rw-rw-r--.  1 hadoop hadoop    1983 2月  17 20:18 wordcount.txt
            +##执行
            +$ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestMapreduceInputFormat /test/input1/wordcount.txt /test/output1
            +
          2. +
          3. 使用SequenceInputFormat类型测试wordcound +使用Hadoop权威指南中的示例创建 /numbers.seq 文件

            +
            $ ./bin/hadoop fs -text /numbers.seq
            +$ ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestMapreduceSequenceInputFormat /numbers.seq /test/output2
            +
          4. +
          5. 多文件输入

            +
            $  ./bin/hadoop jar study.hdfs-0.0.1-SNAPSHOT.jar TestMapreduceMultipleInputs /test/input1/wordcount.txt /numbers.seq /test/output3
            +
          6. +
          +

          博客参考

          +

          淘宝博客

          +
          +
          +
          +
          +

          Hadoop MapReduce 工作机制

          + +

          + + + + + | 评论 +

          +
          + +

          工作流程

          +
            +
          1. 作业配置
          2. +
          3. 作业提交
          4. +
          5. 作业初始化
          6. +
          7. 作业分配
          8. +
          9. 作业执行
          10. +
          11. 进度和状态更新
          12. +
          13. 作业完成
          14. +
          15. 错误处理
          16. +
          17. 作业调度
          18. +
          19. shule(mapreduce核心)和sort
          20. +
          +

          作业配置

          +

          相对不难理解。 具体略。

          +

          作业提交

          +

          image

          +

          首先熟悉上图,4个实例对象: client jvm、jobTracker、TaskTracker、SharedFileSystem

          +

          MapReduce 作业可以使用 JobClient.runJob(conf) 进行 job 的提交。如上图,这个执行过程主要包含了4个独立的实例。

          +
            +
          • 客户端。提交MapReduce作业。
          • +
          • jobtracker:协调作业的运行。jobtracker一个java应用程序。
          • +
          • tasktracker:运行作业划分后的任务。tasktracker一个java应用程序。
          • +
          • shared filesystem(分布式文件系统,如:HDFS)
          • +
          +

          以下是Hadoop1.x 中旧版本的 MapReduce JobClient API. org.apache.hadoop.mapred.JobClient

          +
          /** JobClient is the primary interface for the user-job to interact with the JobTracker. JobClient provides facilities to submit jobs, track their progress, access component-tasks' reports/logs, get the Map-Reduce cluster status information etc.
          +The job submission process involves:
          +Checking the input and output specifications of the job.
          +Computing the InputSplits for the job.
          +Setup the requisite accounting information for the DistributedCache of the job, if necessary.
          +Copying the job's jar and configuration to the map-reduce system directory on the distributed file-system.
          +Submitting the job to the JobTracker and optionally monitoring it's status.
          +Normally the user creates the application, describes various facets of the job via JobConf and then uses the JobClient to submit the job and monitor its progress. */ 
          +Here is an example on how to use JobClient:
          +     // Create a new JobConf
          +     JobConf job = new JobConf(new Configuration(), MyJob.class);
          +     // Specify various job-specific parameters     
          +     job.setJobName("myjob");
          +     job.setInputPath(new Path("in"));
          +     job.setOutputPath(new Path("out"));
          +     job.setMapperClass(MyJob.MyMapper.class);
          +     job.setReducerClass(MyJob.MyReducer.class);
          +     // Submit the job, then poll for progress until the job is complete
          +     JobClient.runJob(job);   
          +// JobClient.runJob(job) --> JobClient. submitJob(job) -->  submitJobInternal(job) 
          +

          新API放在 org.apache.hadoop.mapreduce.* 包下. 使用 Job 类代替 JobClient。又由job.waitForCompletion(true) 内部进行 JobClient.submitJobInternal() 封装。

          +

          新旧API请参考博文 Hadoop编程笔记(二):Hadoop新旧编程API的区别

          +

          hadoop1.x 旧 API JobClient.runJob(job) 调用submitJob() 之后,便每秒轮询作业进度monitorAndPrintJob。并将其进度、执行结果信息打印到控制台上。

          +

          接着再看看 JobClient 的 submitJob() 方法的实现基本过程。上图步骤 2,3,4.

          +
            +
          1. 向 jobtracker 请求一个新的 jobId. (JobID jobId = jobSubmitClient.getNewJobId(); void org.apache.hadoop.mapred.JobClient.init(JobConf conf) throws IOException , 集群环境下是 RPC JobSubmissionProtocol 代理。本地环境使用 LocalJobRunner。

            +
          2. +
          3. 检查作业的相关的输出路径并提交 job 以及相关的 jar 到 job tracker, 相关的 libjar 通过distributedCache 传递给 jobtracker.

            +
            submitJobInternal(… …); 
            +// -->
            +copyAndConfigureFiles(jobCopy, submitJobDir); 
            +// --> 
            +copyAndConfigureFiles(job, jobSubmitDir, replication); 
            +… 
            +// --> 
            +output.checkOutputSpecs(context);
            +
          4. +
          5. 计算作业的分片。将 SplitMetaInfo 信息写入 JobSplit。 Maptask 的个数 = 输入的文件大小除以块的大小。

            +
            int maps = writeSplits(context, submitJobDir);
            +(JobConf)jobCopy.setNumMapTasks(maps);
            +// --> 
            +maps = writeNewSplits(job, jobSubmitDir); 
            +// --> (重写,要详细)
            +JobSplitWriter.createSplitFiles(jobSubmitDir, conf,
            +    jobSubmitDir.getFileSystem(conf), array); // List<InputSplit> splits = input.getSplits(job); 
            +// -->
            +SplitMetaInfo[] info = writeNewSplits(conf, splits, out);
            +
          6. +
          7. 写JobConf信息到配置文件 job.xml。 jobCopy.writeXml(out);

            +
          8. +
          9. 准备提交job。 RPC 通讯到 JobTracker 或者 LocalJobRunner.

            +
            jobSubmitClient.submitJob(jobId, submitJobDir.toString(), jobCopy.getCredentials());
            +
          10. +
          +

          作业初始化

          +
            +
          1. 当 JobTracker 接收到了 submitJob() 方法的调用后,会把此调用放入一个内部队列中,交由作业调度器(job scheduler)进行调度。

            +
            submitJob(jobId, jobSubmitDir, null, ts, false);
            +// -->
            +jobInfo = new JobInfo(jobId, new Text(ugi.getShortUserName()),
            +      new Path(jobSubmitDir));
            +
          2. +
          3. 作业调度器并对job进行初始化。初始化包括创建一个表示正在运行作业的对象——封装任务和纪录信息,以便跟踪任务的状态和进程(步骤5)。

            +
            job = new JobInProgress(this, this.conf, jobInfo, 0, ts);
            +// -->
            +status = addJob(jobId, job);
            +// -->
            +synchronized (jobs) {
            +  synchronized (taskScheduler) {
            +    jobs.put(job.getProfile().getJobID(), job);
            +    for (JobInProgressListener listener : jobInProgressListeners) {
            +      listener.jobAdded(job);
            +    }
            +  }
            +}
            +
          4. +
          5. 创建任务列表。在 JobInProgress的 initTask()方法中

            +
          6. +
          7. 从共享文件系统中获取 JobClient 已计算好的输入分片信息(步骤6)

            +
          8. +
          9. 创建 Map 任务和 Reduce 任务,为每个 MapTask 和 ReduceTask 生成 TaskProgress 对象。

            +
          10. +
          11. 创建的 reduce 任务的数量由 JobConf 的 mapred.reduce.task 属性决定,可用 setNumReduceTasks() 方法设置,然后调度器创建相应数量的要运行的 reduce 任务。任务被分配了 id。

            +
            JobInProgress initTasks() 
            +… …
            +TaskSplitMetaInfo[] splits = createSplits(jobId); // read input splits and create a map per a split
            +// -->
            +allSplitMetaInfo[i] = new JobSplit.TaskSplitMetaInfo(splitIndex, 
            +      splitMetaInfo.getLocations(), 
            +      splitMetaInfo.getInputDataLength());
            +maps = new TaskInProgress[numMapTasks]; // 每个分片创建一个map任务
            +this.reduces = new TaskInProgress[numReduceTasks]; // 创建reduce任务
            +
          12. +
          +

          任务分配

          +

          Tasktracker 和 JobTracker 通过心跳通信分配一个任务

          +
            +
          1. TaskTracker 定期发送心跳,告知 JobTracker, tasktracker 是否还存活,并充当两者之间的消息通道。

            +
          2. +
          3. TaskTracker 主动向 JobTracker 询问是否有作业。若自己有空闲的 solt,就可在心跳阶段得到 JobTracker 发送过来的 Map 任务或 Reduce 任务。对于 map 任务和 task 任务,TaskTracker 有固定数量的任务槽,准确数量由 tasktracker 核的个数核内存的大小来确定。默认调度器在处理 reduce 任务槽之前,会填充满空闲的 map 任务槽,因此,如果 tasktracker 至少有一个空闲的 map 任务槽,tasktracker 会为它选择一个 map 任务,否则选择一个 reduce 任务。选择 map 任务时,jobTracker 会考虑数据本地化(任务运行在输入分片所在的节点),而 reduce 任务不考虑数据本地化。任务还可能是机架本地化。

            +
          4. +
          5. TaskTracker 和 JobTracker heartbeat代码

            +
            TaskTracker.transmitHeartBeat()
            +// -->
            +//
            +// Check if we should ask for a new Task
            +//
            +if (askForNewTask) {
            +  askForNewTask = enoughFreeSpace(localMinSpaceStart);
            +  long freeDiskSpace = getFreeSpace();
            +  long totVmem = getTotalVirtualMemoryOnTT();
            +  long totPmem = getTotalPhysicalMemoryOnTT();
            +  long availableVmem = getAvailableVirtualMemoryOnTT();
            +  long availablePmem = getAvailablePhysicalMemoryOnTT();
            +  long cumuCpuTime = getCumulativeCpuTimeOnTT();
            +  long cpuFreq = getCpuFrequencyOnTT();
            +  int numCpu = getNumProcessorsOnTT();
            +  float cpuUsage = getCpuUsageOnTT();
            +// -->
            +// Xmit the heartbeat
            +HeartbeatResponse heartbeatResponse = jobClient.heartbeat(status, 
            +                                                          justStarted,
            +                                                          justInited,
            +                                                          askForNewTask, 
            +                                                          heartbeatResponseId);
            +注: InterTrackerProtocol jobClient RPC 到 JobTracker.heartbeat() 
            +JobTracker.heartbeat()
            +// -->
            +// Process this heartbeat 
            +short newResponseId = (short)(responseId + 1);
            +status.setLastSeen(now);
            +if (!processHeartbeat(status, initialContact, now)) {
            +  if (prevHeartbeatResponse != null) {
            +    trackerToHeartbeatResponseMap.remove(trackerName);
            +  }
            +  return new HeartbeatResponse(newResponseId, 
            +               new TaskTrackerAction[] {new ReinitTrackerAction()});
            +}
            +
          6. +
          +

          任务执行

          +

          tasktracker 执行任务大致步骤:

          +
            +
          1. 被分配到一个任务后,从共享文件中把作业的jar复制到本地,并将程序执行需要的全部文件(配置信息、数据分片)复制到本地
          2. +
          3. 为任务新建一个本地工作目录
          4. +
          5. 内部类TaskRunner实例启动一个新的jvm运行任务
          6. +
          +

          Tasktracker.TaskRunner.startNewTask()代码

          +
          // -->
          +RunningJob rjob = localizeJob(tip);
          +// -->
          +launchTaskForJob(tip, new JobConf(rjob.getJobConf()), rjob); 
          +// -->
          +tip.launchTask(rjob);
          +// -->
          +setTaskRunner(task.createRunner(TaskTracker.this, this, rjob));
          +this.runner.start(); // MapTaskRunner 或者 ReduceTaskRunner
          +//
          +//startNewTask 方法完整代码:
          +void startNewTask(final TaskInProgress tip) throws InterruptedException {
          +    Thread launchThread = new Thread(new Runnable() {
          +      @Override
          +      public void run() {
          +        try {
          +          RunningJob rjob = localizeJob(tip);//初始化job工作目录
          +          tip.getTask().setJobFile(rjob.getLocalizedJobConf().toString());
          +          // Localization is done. Neither rjob.jobConf nor rjob.ugi can be null
          +          launchTaskForJob(tip, new JobConf(rjob.getJobConf()), rjob); // 启动taskrunner执行task
          +        } catch (Throwable e) {
          +          String msg = ("Error initializing " + tip.getTask().getTaskID() + 
          +                        ":\n" + StringUtils.stringifyException(e));
          +          LOG.warn(msg);
          +          tip.reportDiagnosticInfo(msg);
          +          try {
          +            tip.kill(true);
          +            tip.cleanup(false, true);
          +          } catch (IOException ie2) {
          +            LOG.info("Error cleaning up " + tip.getTask().getTaskID(), ie2);
          +          } catch (InterruptedException ie2) {
          +            LOG.info("Error cleaning up " + tip.getTask().getTaskID(), ie2);
          +          }
          +          if (e instanceof Error) {
          +            LOG.error("TaskLauncher error " + 
          +                StringUtils.stringifyException(e));
          +          }
          +        }
          +      }
          +    });
          +    launchThread.start();
          +  }
          +

          进度和状态更新

          +
            +
          1. 状态包括:作业或认为的状态(成功,失败,运行中)、map 和 reduce 的进度、作业计数器的值、状态消息或描述
          2. +
          3. task 运行时,将自己的状态发送给 TaskTracker,由 TaskTracker 心跳机制向 JobTracker 汇报
          4. +
          5. 状态进度由计数器实现
          6. +
          +

          如图: +image

          +

          作业完成

          +
            +
          1. jobtracker收到最后一个任务完成通知后,便把作业任务状态置为成功
          2. +
          3. 同时jobtracker,tasktracker清理作业的工作状态
          4. +
          +

          错误处理

          +

          task 失败

          +
            +
          1. map 或者 reduce 任务中的用户代码运行异常,子 jvm 在进程退出之前向其父 tasktracker 发送报告, 并打印日志。tasktracker 会将此 task attempt 标记为 failed,释放一个任务槽 slot,以运行另一个任务。streaming 任务以非零退出代码,则标记为 failed.
          2. +
          3. 子进程jvm突然退出(jvm bug)。tasktracker 注意到会将其标记为 failed。
          4. +
          5. 任务挂起。tasktracker 注意到一段时间没有收到进度的更新,便将任务标记为 failed。此 jvm 子进程将被自动杀死。任务超时时间间隔通常为10分钟,使用 mapred.task.timeout 属性进行配置。以毫秒为单位。超时设置为0表示将关闭超时判定,长时间运行不会被标记为 failed,也不会释放任务槽。
          6. +
          7. tasktracker 通过心跳将子任务标记为失败后,自身计数器减一,以便向 jobtracker 申请新的任务
          8. +
          9. jobtracker 通过心跳知道一个 task attempt 失败之后,便重新调度该任务的执行(避开将失败的任务分配给执行失败的tasktracker)。默认执行失败尝试4次,若仍没有执行成功,整个作业就执行失败。
          10. +
          +

          tasktracker 失败

          +
            +
          1. 一个 tasktracker 由于崩溃或者运行过于缓慢而失败,就会停止将 jobtracker 心跳。默认间隔可由 mapred.tasktracker.expriy.interval 设置,毫秒为单位。
          2. +
          3. 同时 jobtracker 将从等待任务调度的 tasktracker 池将此 tasktracker 移除。jobtracker 重新安排此 tasktracker 上已运行并成功完成的 map 任务重新运行。
          4. +
          5. 若 tasktracker 上面的失败任务数远远高于集群的平均失败数,tasktracker 将被列入黑名单。重启后失效。
          6. +
          +

          jobtracker失败

          +

          Hadoop jobtracker 失败是一个单点故障。作业失败。可在后续版本中启动多个 jobtracker,使用zookeeper协调控制(YARN)。

          +

          作业调度

          +
            +
          1. hadoop默认使用先进先出调度器(FIFO) +先遵循优先级优先,在按作业到来顺序调度。缺点:高优先级别的长时间运行的task占用资源,低级优先级,短作业得不到调度。
          2. +
          3. 公平调度器(FairScheduler) +目标:让每个用户公平的共享集群的能力.默认情况下,每个用户都有自己的池。支持抢占,若一个池在特定的时间内未得到公平的资源分配共享,调度器将终止运行池中得到过多资源的任务,以便将任务槽让给资源不足的池。 +详细文档参见:http://hadoop.apache.org/docs/r1.2.1/fair_scheduler.html
          4. +
          5. 容量调度器(CapacityScheduler) +支持多队列,每个队列配置一定的资源,采用FIFO调度策略。对每个用户提交的作业所占的资源进行限定。 +详细文档参见:http://hadoop.apache.org/docs/r1.2.1/capacity_scheduler.html
          6. +
          +

          shuffle和sort

          +

          mapreduce 执行排序,将 map 输出作为输入传递给 reduce 称为 shuffle。其确保每个 reduce 的输入都时按键排序。shuffle 是调优 mapreduce 重要的阶段。

          +

          mapreduce 的 shuffle 和排序如下图: +image

          +

          map端

          +
            +
          1. map端并不是简单的将中间结果输出到磁盘。而是先用缓冲的方式写到内存,并预排序。
          2. +
          3. 每个map任务都有一个环形缓冲区,用于存储任务的输出。默认100mb,由 io.sort.mb 设置。 io.sort.spill.percent 设置阀值,默认80%。
          4. +
          5. 一旦内存缓冲区到达阀值,由一个后台线程将内存中内容 spill 到磁盘中。在写磁盘前,线程会根据数据最终要传送的 reducer 数目划分成相应的分区。每一个分区中,后台线程按键进行内排序,如果有一个 combiner 它会在排序后的输出上运行。
          6. +
          7. 在任务完成之前,多个溢出写文件会被合并成一个已分区已排序的输出文件。最终成为 reduce 的输入文件。属性 io.sort.factor 控制一次最多能合并多少流(分区),默认10.
          8. +
          9. 如果已指定 combiner,并且溢出写文件次数至少为3(min.num.spills.for.combiner 属性),则 combiner 就会在输出文件写到磁盘之前运行。目的时 map 输出更紧凑,写到磁盘上的数据更少。combiner 在输入上反复运行并不影响最终结果。
          10. +
          11. 压缩 map 输出。写磁盘速度更快、节省磁盘空间、减少传给 reduce 数据量。默认不压缩。可使 mapred.compress.map.output=true 启用压缩,并指定压缩库, mapred.map.output.compression.codec。
          12. +
          13. reducer 通过HTTP方式获取输出文件的分区。由于文件分区的工作线程数量任务的 tracker.http.threads 属性控制。
          14. +
          +

          MapTask代码,内部类MapOutputBuffer.collect()方法在收集key/value到容器中,一旦满足预值,则开始溢出写文件由sortAndSpill() 执行。

          +
          // sufficient acct space
          +          kvfull = kvnext == kvstart;
          +          final boolean kvsoftlimit = ((kvnext > kvend)
          +              ? kvnext - kvend > softRecordLimit
          +              : kvend - kvnext <= kvoffsets.length - softRecordLimit);
          +          if (kvstart == kvend && kvsoftlimit) {
          +            LOG.info("Spilling map output: record full = " + kvsoftlimit);
          +            startSpill();
          +          }
          +// --> startSpill();
          + spillReady.signal(); //    private final Condition spillReady = spillLock.newCondition();
          +// --> 溢出写文件主要由内部类 SpillThread(Thread) 执行
          +    try {
          +              spillLock.unlock();
          +              sortAndSpill(); // 排序并溢出
          +            } 
          +// --> sortAndSpill()
          + // create spill file
          +        final SpillRecord spillRec = new SpillRecord(partitions);
          + // sorter = ReflectionUtils.newInstance(job.getClass("map.sort.class", QuickSort.class, IndexedSorter.class), job);
          +… …
          + sorter.sort(MapOutputBuffer.this, kvstart, endPosition, reporter);
          +// -->
          + if (combinerRunner == null) {
          +… …
          + // Note: we would like to avoid the combiner if we've fewer
          +              // than some threshold of records for a partition
          +              if (spstart != spindex) {
          +                combineCollector.setWriter(writer);
          +                RawKeyValueIterator kvIter =
          +                  new MRResultIterator(spstart, spindex);
          +                combinerRunner.combine(kvIter, combineCollector);
          +              }
          +}
          +

          reduce 端

          +
            +
          1. reduce 端 shuffle 过程分为三个阶段:复制 map 输出、排序合并、reduce 处理
          2. +
          3. reduce 可以接收多个 map 的输出。若 map 相当小,则会复制到 reduce tasktracker 的内存中(mapred.job.shuffle.input.buffer.pecent控制百分比)。一旦内存缓冲区达到阀值大小(由 mapped.iob.shuffle.merge.percent 决定)或者达到map输出阀值( mapred.inmem.merge.threshold 控制),则合并后溢出写到磁盘
          4. +
          5. map任务在不同时间完成,tasktracker 通过心跳从 jobtracker 获取 map 输出位置。并开始复制 map 输出文件。
          6. +
          7. reduce 任务由少量复制线程,可并行复制 map 输出文件。由属性 mapred.reduce.parallel.copies 控制。
          8. +
          9. reduce 阶段不会等待所有输入合并成一个大文件后在进行处理,而是把部分合并的结果直接进行处理。
          10. +
          +

          ReduceTask源代码,run()方法

          +
          // --> 3个阶段
          + if (isMapOrReduce()) {
          +      copyPhase = getProgress().addPhase("copy");
          +      sortPhase  = getProgress().addPhase("sort");
          +      reducePhase = getProgress().addPhase("reduce");
          +    }
          +// --> copy 阶段
          +if (!isLocal) {
          +      reduceCopier = new ReduceCopier(umbilical, job, reporter);
          +      if (!reduceCopier.fetchOutputs()) {
          +        if(reduceCopier.mergeThrowable instanceof FSError) {
          +          throw (FSError)reduceCopier.mergeThrowable;
          +        }
          +        throw new IOException("Task: " + getTaskID() + 
          +            " - The reduce copier failed", reduceCopier.mergeThrowable);
          +      }
          +    }
          +    copyPhase.complete();                         // copy is already complete
          +// --> sort 阶段
          +setPhase(TaskStatus.Phase.SORT);
          +    statusUpdate(umbilical);
          +    final FileSystem rfs = FileSystem.getLocal(job).getRaw();
          +    RawKeyValueIterator rIter = isLocal
          +      ? Merger.merge(job, rfs, job.getMapOutputKeyClass(),
          +          job.getMapOutputValueClass(), codec, getMapFiles(rfs, true),
          +          !conf.getKeepFailedTaskFiles(), job.getInt("io.sort.factor", 100),
          +          new Path(getTaskID().toString()), job.getOutputKeyComparator(),
          +          reporter, spilledRecordsCounter, null)
          +      : reduceCopier.createKVIterator(job, rfs, reporter);
          +    // free up the data structures
          +    mapOutputFilesOnDisk.clear();
          +    sortPhase.complete();                         // sort is complete
          +// --> reduce 阶段
          +setPhase(TaskStatus.Phase.REDUCE); 
          +    statusUpdate(umbilical);
          +    Class keyClass = job.getMapOutputKeyClass();
          +    Class valueClass = job.getMapOutputValueClass();
          +    RawComparator comparator = job.getOutputValueGroupingComparator();
          +    if (useNewApi) {
          +      runNewReducer(job, umbilical, reporter, rIter, comparator, 
          +                    keyClass, valueClass);
          +    } else {
          +      runOldReducer(job, umbilical, reporter, rIter, comparator, 
          +                    keyClass, valueClass);
          +    }
          +// --> done 执行结果
          +    done(umbilical, reporter);
          +

          有关mapreduce shuffle和sort 原理、过程和调优

          +

          hadoop作业调优参数整理及原理, MapReduce:详解Shuffle过程 介绍的非常详尽。

          +
          +
          + + +
          + +
          +
          +

          + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

          +
          + + + + + + + + + diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 0000000..725ad11 --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,960 @@ + + + + + + + kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
          +

          kangfoo's blog

          +

          工作学习笔记,生活掠影。

          +
          +
          + +
          +
          +
          +
          +
          +

          Hadoop RPC

          + +

          + + + + + | 评论 +

          +
          + +

          Remote Procedure Call 远程方法调用。不需要了解网络细节,某一程序即可使用该协议请求来自网络内另一台及其程序的服务。它是一个 Client/Server 的结构,提供服务的一方称为Server,消费服务的一方称为Client。

          +

          Hadoop 底层的交互都是通过 rpc 进行的。例 如:datanode 和 namenode、tasktracker 和 jobtracker、secondary namenode 和 namenode 之间的通信都是通过 rpc 实现的。

          +

          TODO: 此文未写明了。明显需要画 4张图, rpc 原理图,Hadoop rpc 时序图, 客户端 流程图,服端流程图。最好帖几个包图+ 类图(组件图)。待完善。

          +

          要实现远程过程调用,需要有3要素: +1、server 必须发布服务 +2、在 client 和 server 两端都需要有模块来处理协议和连接 +3、server 发布的服务,需要将接口给到 client

          +

          Hadoop RPC

          +
            +
          1. 序列化层。 Client 与 Server 端通讯传递的信息采用实现自 Writable 类型
          2. +
          3. 函数调用层。 Hadoop RPC 通过动态代理和 java 反射实现函数调用
          4. +
          5. 网络传输层。Hadoop RPC 采用 TCP/IP socket 机制
          6. +
          7. 服务器框架层。Hadoop RPC 采用 java NIO 事件驱动模型提高 RPC Server 吞吐量
          8. +
          +

          TODO 缺个 RPC 图

          +

          Hadoop RPC 源代码主要在org.apache.hadoop.ipc包下。org.apache.hadoop.ipc.RPC 内部包含5个内部类。

          +
            +
          • Invocation :用于封装方法名和参数,作为数据传输层,相当于VO(Value Object)。
          • +
          • ClientCache :用于存储client对象,用 socket factory 作为 hash key,存储结构为 hashMap
          • +
          • Invoker :是动态代理中的调用实现类,继承了 java.lang.reflect.InvocationHandler。
          • +
          • Server :是ipc.Server的实现类。
          • +
          • VersionMismatch : 协议版本。
          • +
          +

          从客户端开始进行通讯源代码分析

          +

          org.apache.hadoop.ipc.Client 有5个内部类

          +
            +
          • Call: A call waiting for a value.
          • +
          • Connection: Thread that reads responses and notifies callers. Each connection owns a socket connected to a remote address. Calls are multiplexed through this socket: responses may be delivered out of order.
          • +
          • ConnectionId: This class holds the address and the user ticket. The client connections to servers are uniquely identified by
          • +
          • ParallelCall: Call implementation used for parallel calls.
          • +
          • ParallelResults: Result collector for parallel calls.
          • +
          +

          客户端和服务端建立连接的大致执行过程为

          +
            +
          1. 在 Object org.apache.hadoop.ipc.RPC.Invoker.invoke(Object proxy, Method method, Object[] args) 方法中调用
            +client.call(new Invocation(method, args), remoteId);

            +
          2. +
          3. 上述的 new Invocation(method, args) 是 org.apache.hadoop.ipc.RPC 的内部类,它包含被调用的方法名称及其参数。此处主要是设置方法和参数。 client 为 org.apache.hadoop.ipc.Client 的实例对象。

            +
          4. +
          5. org.apache.hadoop.ipc.Client.call() 方法的具体源代码。在call()方法中 getConnection()内部获取一个 org.apache.hadoop.ipc.Client.Connection 对象并启动 io 流 setupIOstreams()。

            +
            Writable org.apache.hadoop.ipc.Client.call(Writable param, ConnectionId remoteId) throwsInterruptedException, IOException {
            +Call call = new Call(param); //A call waiting for a value.   
            +// Get a connection from the pool, or create a new one and add it to the
            +// pool.  Connections to a given ConnectionId are reused. 
            +Connection connection = getConnection(remoteId, call);// 主要在 org.apache.hadoop.net 包下。
            +connection.sendParam(call); //客户端发送数据过程
            +boolean interrupted = false;
            +synchronized (call) {
            +   while (!call.done) {
            +    try {
            +      call.wait();                           // wait for the result
            +    } catch (InterruptedException ie) {
            +      // save the fact that we were interrupted
            +      interrupted = true;
            +    }
            +  }
            +… …
            +}
            +}
            +// Get a connection from the pool, or create a new one and add it to the
            +// pool.  Connections to a given ConnectionId are reused. 
            +private Connection getConnection(ConnectionId remoteId,
            +                               Call call)
            +                               throws IOException, InterruptedException {
            +if (!running.get()) {
            +  // the client is stopped
            +  throw new IOException("The client is stopped");
            +}
            +Connection connection;
            +// we could avoid this allocation for each RPC by having a  
            +// connectionsId object and with set() method. We need to manage the
            +// refs for keys in HashMap properly. For now its ok.
            +do {
            +  synchronized (connections) {
            +    connection = connections.get(remoteId);
            +    if (connection == null) {
            +      connection = new Connection(remoteId);
            +      connections.put(remoteId, connection);
            +    }
            +  }
            +} while (!connection.addCall(call)); 
            +//we don't invoke the method below inside "synchronized (connections)"
            +//block above. The reason for that is if the server happens to be slow,
            +//it will take longer to establish a connection and that will slow the
            +//entire system down.
            +connection.setupIOstreams(); // 向服务段发送一个 header 并等待结果
            +return connection;
            +}
            +
          6. +
          7. setupIOstreams() 方法。

            +
            void org.apache.hadoop.ipc.Client.Connection.setupIOstreams() throws InterruptedException {
            +// Connect to the server and set up the I/O streams. It then sends
            +// a header to the server and starts
            +// the connection thread that waits for responses.
            +while (true) {
            +      setupConnection();//  建立连接
            +      InputStream inStream = NetUtils.getInputStream(socket); // 输入
            +      OutputStream outStream = NetUtils.getOutputStream(socket); // 输出
            +      writeRpcHeader(outStream);
            +      }
            +… … 
            +// update last activity time
            +  touch();
            +// start the receiver thread after the socket connection has been set up            start(); 
            +}        
            +
          8. +
          9. 启动org.apache.hadoop.ipc.Client.Connection +客户端获取服务器端放回数据过程

            +
            void org.apache.hadoop.ipc.Client.Connection.run()
            +while (waitForWork()) {//wait here for work - read or close connection
            +    receiveResponse();
            +  }
            +
          10. +
          +

          ipc.Server源码分析

          +

          ipc.Server 有6个内部类:

          +
            +
          • Call :用于存储客户端发来的请求
          • +
          • Listener : 监听类,用于监听客户端发来的请求,同时Listener内部还有一个静态类,Listener.Reader,当监听器监听到用户请求,便让Reader读取用户请求。
          • +
          • ExceptionsHandler: 异常管理
          • +
          • Responder :响应RPC请求类,请求处理完毕,由Responder发送给请求客户端。
          • +
          • Connection :连接类,真正的客户端请求读取逻辑在这个类中。
          • +
          • Handler :请求处理类,会循环阻塞读取callQueue中的call对象,并对其进行操作。
          • +
          +

          大致过程为:

          +
            +
          1. Namenode的初始化时,RPC的server对象是通过ipc.RPC类的getServer()方法获得的。

            +
            void org.apache.hadoop.hdfs.server.namenode.NameNode.initialize(Configuration conf) throwsIOException
            +// create rpc server
            +InetSocketAddress dnSocketAddr = getServiceRpcServerAddress(conf);
            +if (dnSocketAddr != null) {
            +  int serviceHandlerCount =
            +    conf.getInt(DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_KEY,
            +                DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_DEFAULT);
            +  this.serviceRpcServer = RPC.getServer(this, dnSocketAddr.getHostName(), 
            +      dnSocketAddr.getPort(), serviceHandlerCount,
            +      false, conf, namesystem.getDelegationTokenSecretManager());
            +  this.serviceRPCAddress = this.serviceRpcServer.getListenerAddress();
            +  setRpcServiceServerAddress(conf);
            +}
            +… …
            +this.server.start();  //start RPC server  
            +
          2. +
          3. 启动 server

            +
            void org.apache.hadoop.ipc.Server.start()
            +// Starts the service.  Must be called before any calls will be handled.
            +public synchronized void start() {
            +responder.start();
            +listener.start();
            +handlers = new Handler[handlerCount];
            +for (int i = 0; i < handlerCount; i++) {
            +  handlers[i] = new Handler(i);
            +  handlers[i].start(); //处理call
            +}
            +}
            +
          4. +
          5. Server处理请求, server 同样使用非阻塞 nio 以提高吞吐量

            +
            org.apache.hadoop.ipc.Server.Listener.Listener(Server) throws IOException
            +public Listener() throws IOException {
            +  address = new InetSocketAddress(bindAddress, port);
            +  // Create a new server socket and set to non blocking mode
            +  acceptChannel = ServerSocketChannel.open();
            +  acceptChannel.configureBlocking(false);
            +… … }     
            +
          6. +
          7. 真正建立连接

            +
            void org.apache.hadoop.ipc.Server.Listener.doAccept(SelectionKey key) throws IOException,OutOfMemoryError
            +

            Reader 读数据接收请求

            +
            void org.apache.hadoop.ipc.Server.Listener.doRead(SelectionKey key) throws InterruptedException
            +try {
            +    count = c.readAndProcess();
            +  } catch (InterruptedException ieo) {
            +    LOG.info(getName() + ": readAndProcess caught InterruptedException", ieo);
            +    throw ieo;
            +  }
            +
            int org.apache.hadoop.ipc.Server.Connection.readAndProcess() throws IOException,InterruptedException
            +if (!rpcHeaderRead) {
            +      //Every connection is expected to send the header.
            +      if (rpcHeaderBuffer == null) {
            +        rpcHeaderBuffer = ByteBuffer.allocate(2);
            +      }
            +      count = channelRead(channel, rpcHeaderBuffer);
            +      if (count < 0 || rpcHeaderBuffer.remaining() > 0) {
            +        return count;
            +      }
            +      int version = rpcHeaderBuffer.get(0);
            +… … 
            +processOneRpc(data.array()); // 数据处理
            +
          8. +
          9. 下面贴出Server.Connection类中的processOneRpc()方法和processData()方法的源码。

            +
            void org.apache.hadoop.ipc.Server.Connection.processOneRpc(byte[] buf) throws IOException,InterruptedException
            +private void processOneRpc(byte[] buf) throws IOException,
            +    InterruptedException {
            +  if (headerRead) {
            +    processData(buf);
            +  } else {
            +    processHeader(buf);
            +    headerRead = true;
            +    if (!authorizeConnection()) {
            +      throw new AccessControlException("Connection from " + this
            +          + " for protocol " + header.getProtocol()
            +          + " is unauthorized for user " + user);
            +    }
            +  }
            +}
            +
          10. +
          11. 处理call

            +
            void org.apache.hadoop.ipc.Server.Handler.run()
            +while (running) {
            +    try {
            +      final Call call = callQueue.take(); // pop the queue; maybe blocked here
            +      … … 
            +      CurCall.set(call);
            +      try {
            +        // Make the call as the user via Subject.doAs, thus associating
            +        // the call with the Subject
            +        if (call.connection.user == null) {
            +          value = call(call.connection.protocol, call.param, 
            +                       call.timestamp);
            +        } else {
            +… …}
            +
          12. +
          13. 返回请求

            +
          14. +
          +

          下面贴出Server.Responder类中的doRespond()方法源码:

          +
          void org.apache.hadoop.ipc.Server.Responder.doRespond(Call call) throws IOException
          +    //
          +    // Enqueue a response from the application.
          +    //
          +    void doRespond(Call call) throws IOException {
          +      synchronized (call.connection.responseQueue) {
          +        call.connection.responseQueue.addLast(call);
          +        if (call.connection.responseQueue.size() == 1) {
          +          processResponse(call.connection.responseQueue, true);
          +        }
          +      }
          +    }
          +

          补充: +notify()让因wait()进入阻塞队列里的线程(blocked状态)变为runnable,然后发出notify()动作的线程继续执行完,待其完成后,进行调度时,调用wait()的线程可能会被再次调度而进入running状态。

          +

          参考资源:

          +
          +
          +
          +
          +

          Hadoop I/O

          + +

          + + + + + | 评论 +

          +
          + +

          HDFS 对网络IO, 磁盘IO 的操作是比较复杂且开销还比较高的。Hadoop 在设计中使用了内部的原子操作、压缩、随机读写、流式存储、数据完整性校验、序列化、基于文件的数据结构等方面进行 IO 操作。

          +

          数据完整性

          +

          保证数据在传输过程中不损坏,常见的保证数据完整性采用的技术

          +
            +
          • 奇偶校验技术
          • +
          • ECC 内存纠错校验技术
          • +
          • CRC-32 循环冗余校验技术
          • +
          +

          HDFS的数据完整性

          +

          HDFS 会对写入的所有数据计算校验和,并在读取数据时验证校验和。它针对每个由 io.bytes.per.checksum(默认512字节,开销低于1%)指定字节数据技术校验和。

          +

          DataNode 负责在验证收到的数据后存储数据及其校验和。从客户端和其它数据节点复制过来的数据。客户端写入数据并且将它发送到一个数据节点管线中,在管线的最后一个数据节点验证校验和。

          +

          客户端读取 DataNode 上的数据时,也会验证校验和。将其与 DataNode 上存储的校验和进行对比。每个 DataNode 维护一个连续的校验和验证日志,因此它知道每个数据块最后验证的时间。

          +

          每个 DataNode 还会在后台线程运行一个 DataBlockScanner(数据块检测程序),定期验证存储在数据节点上的所有块,以解决物理存储媒介上位损坏问题。

          +

          HDFS 通过复制完整的数据复本来修复损坏的数据块,进而得到一个新的、完好无损的复本。基本思路:如果客户端读取数据块时检测到错误,就向 NameNode 汇报已损坏的数据块及它试图从名称节点中要读取的 DataNode,并抛出 ChecksumException。 NameNode 将这个已损坏的数据块复本标记为已损坏,并不直接与 datanode 联系,或尝试将这个个复本复制到另一个 datanode。之后,namennode 安排这个数据块的一个复本复制到另一个 datanode。 至此,数据块复制因子恢复到期望水平。此后,并将已损坏的数据块复本删除。

          +

          LocalFileSystem

          +

          Hadoop的 LocalFileSystem 执行客户端校验。意味着,在写一个名filename的文件时,文件系统的客户端以透明的方式创建一个隐藏.filename.crc。在同一个文件夹下,包含每个文件块的校验和。

          +

          禁用校验和,使用底层文件系统原生支持校验和。这里通过 RawLocalFileSystem 来替代 LocalFileSystem 完成。要在一个应用中全局使用,只需要设置 fs.file.impl值为 org.apache.hadoop.fs.RawLocalFileSystem 来重新 map 执行文件的 URL。或者只想对某些读取禁用校验和校验。例:

          +
          Configuration conf = ...
          +FileSystem fs = new RawLocalFileSystem();
          +fs.initialize(null, conf);
          +

          ChecksumFileSystem

          +

          LocalFileSystem 继承自 ChecksumFileSystem(校验和文件系统),ChecksumFileSystem 继承自 FileSystem。ChecksumFileSystem 可以很容易的添加校验和功能到其他文件系统中。

          +

          压缩

          +

          将文件压缩有两大好处

          +
            +
          • 减少存储文件所需要的磁盘空间
          • +
          • 加速数据在网络和磁盘上的传输
          • +
          +

          编译native-hadoop

          +

          参见 《Native-hadoop 编译》

          +

          压缩算法

          +

          所有的压缩算法都需要权衡时间/空间比.压缩和解压缩速度越快,节省空间越少。gizp压缩空间/时间性能比较适中。bzip2比gzip更高效,但数度更慢; lzo 压缩速度比gzip比较快,但是压缩效率稍微低一点。

          + +

          Hadoop支持的压缩格式

          +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          压缩格式 工具 算法 文件扩展名 多文件 可切分
          DEFLATE DEFLATE .deflate
          GzipgzipDEFLATE.gz
          bzip2bzip2bzip2.bz
          LZOLzopLZO.lzo
          +
          +


          +编码/解码 +用以执行压缩解压算法,是否有java/原生实现

          +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          压缩格式 codecjava实现原生实现
          DEFLATEorg.apache.hadoop.io.compress.DefaultCodec
          gziporg.apache.hadoop.io.compress.GzipCodec
          bzip2org.apache.hadoop.io.compress.Bzip2Codec
          LZOcom.hadoop.compression.lzo.LzopCodec
          +
          +


          +压缩算法相关的 API

          +

          使用 CompressionCodecFactory.getCodec()方法来推断 CompressionCodec 具体实现。由 CompressionCodec 接口的实现对流进行进行压缩与解压缩。CodecPool 提供了重复利用压缩和解压缩的对象的机制。

          +

          … … 画个类图。## TOTO

          +

          NativeCodeLoader 加载 native-hadoop library +若想使用 snappycode 首先加载 snappy.so,再判断加载 native hadoop–>hadoop.so。native hadoop 中包含了 java 中申明的native 方法,由 native 方法去调用第三方的 natvie library。native_libraries官方参考文档

          +

          在 Hadoop core-site.xml 配置文件中可以设置是否使用本地库,默认以启用。

          +
          <property>
          +  <name>hadoop.native.lib</name>
          +  <value>true</value>
          +  <description>Should native hadoop libraries, if present, be used.</description>
          +</property>
          +

          编写使用压缩的测试程序

          +
            +
          1. 首先下载并编译 snappy,zlib
          2. +
          3. 编写 java 代码 CompressionTest.java, DeCompressionTest.java。 程序是由 maven test 进行。
          4. +
          5. 执行
            $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/hadoop/env/hadoop/lib/native/Linux-amd64-64:/usr/local/lib
            +$ mvn test -Dtest=com.kangfoo.study.hadoop1.io.CompressionTest
            +$ mvn test -Dtest=com.kangfoo.study.hadoop1.io.DeCompressionTest
            +## 结果:
            +rw-rw-r--. 1 hadoop hadoop 531859 7月  23 2013 releasenotes.html
            +-rw-rw-r--. 1 hadoop hadoop 140903 1月  22 15:13 releasenotes.html.deflate
            +-rw-rw-r--. 1 hadoop hadoop 531859 1月  22 15:22 releasenotes.html.deflate.decp
            +-rw-rw-r--. 1 hadoop hadoop 140915 1月  22 15:13 releasenotes.html.gz
            +-rw-rw-r--. 1 hadoop hadoop 531859 1月  22 15:22 releasenotes.html.gz.decp
            +-rw-rw-r--. 1 hadoop hadoop 224661 1月  22 15:13 releasenotes.html.snappy
            +-rw-rw-r--. 1 hadoop hadoop 531859 1月  22 15:22 releasenotes.html.snappy.decp
            +## 日志:
            +Running com.kangfoo.study.hadoop1.io.CompressionTest
            +2014-01-22 15:13:31,312 WARN  snappy.LoadSnappy (LoadSnappy.java:<clinit>(36)) - Snappy native library is available
            +2014-01-22 15:13:31,357 INFO  util.NativeCodeLoader (NativeCodeLoader.java:<clinit>(43)) - Loaded the native-hadoop library
            +2014-01-22 15:13:31,357 INFO  snappy.LoadSnappy (LoadSnappy.java:<clinit>(44)) - Snappy native library loaded
            +2014-01-22 15:13:31,617 INFO  zlib.ZlibFactory (ZlibFactory.java:<clinit>(47)) - Successfully loaded & initialized native-zlib library
            +
          6. +
          +

          启用压缩

          +

          出于性能考虑,使用原生的压缩库要比同时提供 java 实现的开销更小。可以修改 Hadoop core-site.xml 配置文件 io.compression.codecs 以启用压缩,前提是必须安装好对应的原生压缩库依赖,并配置正确的 Codec。

          +
            +
          • 属性名: io.compression.codecs
          • +
          • 默认值:org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.ompress.Bzip2Codec
          • +
          +

          压缩与输入分割

          +

          考虑如何压缩将由 MapReduce 处理的数据时,是否支持分割很重要。

          +

          案例假设,一个gzip压缩的文件的为1GB。HDFS 将其分为16块(64mb 块大小),其中每一数据块最为一个 map 任务输入。那么在 map 任务中,每一个分块是无法独立工作的( gzip 是使用的 DEFLATE 算法,它将数据存储在一系列的压缩块中。无法实现从数据流的任意位置读取数据,那么这些分块必须全部读取并与整个数据流进行同步才能从任意位置进行读取数据)。这样就失去了本地化的优势。一个 map 要处理其他15个分块的数据,而大多数据并不存储在当前 map 节点上。Map的任务数越少,作业的粒度就较大,运行的时间可能会更长。

          +

          具体应该选择哪种压缩形式,还要经过测试,才可以决定。大文件选择支持分割的压缩形式,目前只有 bzip2 支持分片,但没有原生库的实现。或者使用 SequenceFile, MapFile 数据格式进行小文件的合并再存储,这样可以满足分片。

          +

          在 MapReduce 中使用压缩

          +

          如果文件是压缩过的,那么在被 MapReduce 读取时,它们会被解压,根据文件扩展名选择对应的解码器。可参考 MapReduce 块压缩相关知识。

          +

          压缩 MapReduce 的作业输出

          +
            +
          1. 在作业配置中将 mapred.output.compress 属性设置为 true
          2. +
          3. 将 mapred.output.compression.codec 属性设置为自己需要使用的压缩解码/编码器的类名。
          4. +
          +

          代码示例

          +
          conf.setBoolean(“mapred.output.compress”,true)
          +Conf.setClass(“mapred.output.compression.codec”,GizpCodec.class,CompressionCodec.class);
          +

          对 Map 任务输出结果的压缩

          +

          压缩 Map 作业的中间结果以减少网络传输。

          +

          Map输出压缩属性
          +属性名称: mapred.compress.map.output
          +类型: boolean
          +默认值: false
          +描述: 对 map 任务输出是否进行压缩
          +
          +属性名称: mapred.map.output.compression.codec
          +类型: Class
          +默认值: org.apache.hadoop.io.compress.DefaultCodec
          +描述: map 输出所用的压缩 codec

          +

          代码示例

          +
          conf.setCompressMapOutput(true);
          +conf.setMapOutputCompressorClass(GzipCodec.classs)
          +


          +

          序列化和反序列化

          +

          什么是Hadoop的序列化? 序列化,将结构化对象转换为字节流,以便于在网络传输和磁盘存储的过程。反序列化,将字节流转化为结构化的对象的逆过程。可用于进程间的通讯和永久存储,数据拷贝

          +

          序列化特点:

          +
            +
          • 紧凑:可充分利用网络带宽(哈夫曼编码)
          • +
          • 快速:尽量减少序列化和反序列化的开销
          • +
          • 可扩展:通讯协议升级向下兼容
          • +
          • 互操作:支持不同语言间的通讯
          • +
          +

          Hadoop1.x 仅满足了紧凑和快速两个特性。 +java 自身提供的序列化并不精简。Java Serializaiton 是序列化图对象的通讯机制,它有序列化和反序列化的开销。 +java 序列化比较复杂,不能很精简的控制对象的读写。连接/延迟/缓冲。java 序列化不能满足: 精简,快速,可扩展,可互操作。

          +

          Hadoop1.x 使用 Writable 实现自己的序列化格式。它结构紧凑,快速。但难以用 java 以外的语言进行扩展。

          +

          Writable 接口

          +

          Writeable 接口定义了2个方法:

          +
          void write(DataOutput out) throws IOException; // 将其状态写入二进制格式的 DataOutput 流;
          +void readFields(DataInput in) throws IOException; // 从二进制格式的 DataInput 流读取其状态
          +

          画个类图 ## TODO

          +
          writable
          +writableComparable(interface WritableComparable<T> extends Writable, Comparable<T> )
          +comparator(int compare(T o1, T o2);)
          +comparable(public int compareTo(T o);)
          +rawcomparator(public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2);)
          +writablecomparator(ReflectionUtils.newInstance(keyClass, null);)
          +

          Writable 类的层次结构 +image

          +

          部分类型列举

          +
            +
          • NullWritable 是一种特殊的Writable类型,单例的, 序列化的长度是零。可以做占位符。
          • +
          • Text 是针对UTF-8序列化的Writable类。一般可等价于 java.lang.String 的 Writable。Text是可变的。
          • +
          • BytesWritable 是一个对二进制的封装,序列化为一个格式为一个用于制定后面数据字节数的整数域(4字节),后跟字节本身。它是可变的。如:
            BytesWritable b = new BytesWritable(new byte[]{2,5,127}); // 3个长度
            +byte[] bytes = serialize(b);
            +assertThat(StringUtils.byteToHexString(bytes), is("0000000302057f"));
            +
          • +
          • ObjectWritable 适用于java基本类型(String,enum,Writable,null或者这些类型组成的数组)的一个封装。
          • +
          • Writable集合。ArrayWritable和TwoDArrayWritable针对于数组和二维数组,它们中所有的元素必须是同一个类的实例。MapWritable和SortedMapWritable是针对于 Map 和 SorMap。
          • +
          +

          自定义Writable +•实现WritableComparable +•实现

          +
          write(); // 将对象转换为字节流并写入到输出流 out 中
          +readFields(); // 从输入流 in 中读取字节流并反序列化为对象
          +compareTo()方法。 // 将 this 对像与对象 O 比较
          +

          示例程序代码

          + +

          序列化框架

          +
            +
          • apache avro 旨在解决Hadoop中Writable类型的不足:缺乏语言的可移植性。
          • +
          • apache thrift 可伸缩的跨语言, 提供了 PRC 实现层。
          • +
          • Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。
          • +
          +

          参考

          + +

          基于文件的数据结构

          +

          使用 SequenceFile, MapFile 主要解决的问题是:支持分片的数据压缩格式的比较有限,对于某些应用而言,需要处理的数据格式来存储自己的格式,MapRedurce 需要更高级的容器。

          +

          SequenceFile

          +
            +
          1. 文件是以二进制键/值对形式存储的平面文件
          2. +
          3. 可以作为小文件的容器,它将小文件包装起来,以获取更高效率的存储和处理
          4. +
          5. 存储在 SequenceFile 中的 key/valu e并不一定是 Writable 类型
          6. +
          7. 可使用 append()方法在文件末位附加 key/value 对
          8. +
          +

          好处

          +
            +
          1. 支持纪录或者块压缩
          2. +
          3. 支持splittable, 可作为mapreduce输入分片
          4. +
          5. 修改简单(har是不可以修改的)
          6. +
          +

          SequenceFile 压缩

          +

          SequenceFile 文件格式内部结构与是否启用压缩有关。启用压缩又分两类:纪录压缩;数据块压缩。

          +
            +
          1. 无压缩。 默认是不启用压缩,则每个纪录就是它的纪录长度(字节数)、键长度、键和值组成。长度字段为4字节的整数。

            +
          2. +
          3. 纪录压缩。其格式与无压缩情况相同,不同在于纪录压缩的值需要通过文件头中定义的压缩codec进行压缩。键不压缩。
            +无压缩和纪录压缩的示意图: +image

            +
          4. +
          5. 块压缩。一次压缩多条纪录,比单条纪录压缩效率高。可以不断的向数据块中压缩纪录,直到字节数不小于io.seqfile.compress.blocksize属性中设置的字节数。默认1MB.每个新的块的开始处都需要插入同步标识。数据块的格式如下:首先是一个指示数据块中字节数的字段;紧跟是4个压缩字段(键长度、键、值长度、值)。块压缩示意图如下: +image

            +
          6. +
          +

          实例程序代码

          + +

          运行结果

          +
          ## 查看 sequence file
          +$ ./bin/hadoop fs -text /numbers.seq
          +## 排序
          +$ ./bin/hadoop jar ./hadoop-examples-1.2.1.jar sort -r 1 -inFormat org.apache.hadoop.mapred.SequenceFileInputFormat -outFormat org.apache.hadoop.mapred.SequenceFileOutputFormat -outKey org.apache.hadoop.io.IntWritable -outValue org.apache.hadoop.io.Text /numbers.seq sorted
          +## 查看排序后的结果(原键降序排列为从1到100升序排列)
          +$ ./bin/hadoop fs -text /user/hadoop/sorted/part-00000
          +

          博客参考

          +

          MapFile

          +

          MapFile 是已经排序的 SequenceFile,可以视为 java.util.Map 持久化形式。它已加入了搜索键的索引,可以根据 key 进行查找。它的键必须是 WritableComparable 类型的实例,值必须是 Writable 类型的实例,而 SequenceFile 无此要求。使用 MapFile.fix() 方法进行索引重建,把 SequenceFile 转换为 MapFile。

          +

          MapFile java 源代码

          +
          org.apache.hadoop.io.MapFile.Writer{ 
          +// 类的内部结构(MapFile是已经排序的SequenceFile):
          +private SequenceFile.Writer data;
          +private SequenceFile.Writer index;
          +… … 
          +}
          +org.apache.hadoop.io.MapFile.Reader{
          +// 二分法查找。一次磁盘寻址 + 一次最多顺序128(默认值等于每128下一个索引)个条目顺序扫瞄
          +public synchronized Writable get(WritableComparable key, Writable val){
          +… … 
          +}
          +

          实例程序代码

          + +

          运行结果

          +
          $ ./bin/hadoop fs -text /numbers.map/data 
          +$ ./bin/hadoop fs -text /numbers.map/index
          +

          SequenceFile合并为MapFile

          +
            +
          1. 新建SequenceFile文件
            $ ./bin/hadoop jar ./hadoop-examples-1.2.1.jar sort -r 1 -inFormat org.apache.hadoop.mapred.SequenceFileInputFormat -outFormat org.apache.hadoop.mapred.SequenceFileOutputFormat -outKey org.apache.hadoop.io.IntWritable -outValue org.apache.hadoop.io.Text /numbers.seq /numbers2.map
            +
          2. +
          3. 重命名文件夹
            $ ./bin/hadoop fs -mv /numbers2.map/part-00000 /numbers2.map/data
            +
          4. +
          5. 运行测试用例
          6. +
          7. 验证结果
            Created MapFile hdfs://master11:9000/numbers2.map with 100 entries
            +rw-r--r--   2 hadoop      supergroup       4005 2014-02-09 20:06 /numbers2.map/data
            +-rw-r--r--   3 kangfoo-mac supergroup        136 2014-02-09 20:13 /numbers2.map/index
            +
          8. +
          + +
          +
          +
          +
          +

          Hadoop Pipes 编译

          + +

          + + + + + | 评论 +

          +
          + +

          我在编译 Hadoop Pipes 的时候,出现了些小问题。主要是我没有安装 openssl-devel。本以为安装 openssl 就差不多了,可这个就是问题的根源, 我现在是自己动手编译 pipes, 而 Hadoop 的 pipes 编译需要 openssl 的依赖,那么在编译的时候最好还是将 openssl-devel 开发支持的依赖补上比较省事。在解决问题的时发现网上向我一样的同学还是有的。在此我就贴下我编译时的部分日志。

          +
            +
          1. 在Hadoop 根目录下执行

            +
            ant -Dcompile.c++=yes examples
            +##错误
            +[exec] checking for HMAC_Init in -lssl... no
            +BUILD FAILED
            +/home/hadoop/env/hadoop-1.2.1/build.xml:2164: exec returned: 255
            +… … 
            +./configure: line 5234: exit: please: numeric argument required
            +##具体日志:
            +… … 
            + [exec] configure: error: Cannot find libssl.so ## 没有 libssl.so
            + [exec] /home/hadoop/env/hadoop-1.2.1/src/c++/pipes/configure: line 5234: exit: please: numeric argument required
            + [exec] /home/hadoop/env/hadoop-1.2.1/src/c++/pipes/configure: line 5234: exit: please: numeric argument required
            + [exec] checking for HMAC_Init in -lssl... no 
            +
          2. +
          3. 检查 ssl

            +
            $ yum info openssl
            +$ ll /usr/lib64/libssl*
            +-rwxr-xr-x. 1 root root 221568 2月  23 2013 /usr/lib64/libssl3.so
            +lrwxrwxrwx. 1 root root     16 12月  8 18:14 /usr/lib64/libssl.so.10 -> libssl.so.1.0.1e
            +-rwxr-xr-x. 1 root root 436984 12月  4 04:21 /usr/lib64/libssl.so.1.0.1e
            +## 缺个 libssl.so 的文件, 于是添加软链接:
            +sudo ln -s /usr/lib64/libssl.so.1.0.1e /usr/lib64/libssl.so
            +
          4. +
          5. 切换目录到 pipes 下再次编译

            +
            $cd /home/hadoop/env/hadoop/src/c++/pipes
            +执行
            +$ make distclean
            +$ ./configure 
            +[hadoop@master11 pipes]$ ./configure 
            +checking for a BSD-compatible install... /usr/bin/install -c
            +… … 
            +checking whether it is safe to define __EXTENSIONS__... yes
            +checking for special C compiler options needed for large files... no
            +checking for _FILE_OFFSET_BITS value needed for large files... no
            +checking pthread.h usability... yes
            +checking pthread.h presence... yes
            +checking for pthread.h... yes
            +checking for pthread_create in -lpthread... yes
            +checking for HMAC_Init in -lssl... no
            +configure: error: Cannot find libssl.so ## 还是没找到
            +./configure: line 5234: exit: please: numeric argument required
            +./configure: line 5234: exit: please: numeric argument required
            +
          6. +
          7. 安装openssl-devel, sudo yum install openssl-devel

            +
          8. +
          9. 再切换到Hadoop根目录下执行

            +
            ant -Dcompile.c++=yes examples
            +##搞定,编译通过
            +compile-examples:
            +[javac] /home/hadoop/env/hadoop-1.2.1/build.xml:742: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
            +[javac] Compiling 24 source files to /home/hadoop/env/hadoop-1.2.1/build/examples
            +[javac] 警告: [options] 未与 -source 1.6 一起设置引导类路径
            +[javac] 注: /home/hadoop/env/hadoop-1.2.1/src/examples/org/apache/hadoop/examples/MultiFileWordCount.java使用或覆盖了已过时的 API。
            +[javac] 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
            +[javac] 1 个警告
            +examples:
            +  [jar] Building jar: /home/hadoop/env/hadoop-1.2.1/build/hadoop-examples-1.2.2-SNAPSHOT.jar
            +BUILD SUCCESSFUL
            +Total time: 1 minute 11 seconds
            +
          10. +
          +
          +
          +
          +
          +

          Native-hadoop 编译

          + +

          + + + + + | 评论 +

          +
          + +

          对我来讲编译 native hadoop 并不是很顺利。现将问题纪录在案。

          +

          主要问题

          +
            +
          1. ivy 联网获取资源并不稳定
          2. +
          3. hadoop-1.2.1/build.xml:62: Execute failed: java.io.IOException: Cannot run program “autoreconf” (in directory “/home/userxxx/hadoop/hadoop-1.2.1/src/native”): java.io.IOException: error=2, No such file or directory
          4. +
          5. [exec] configure: error: Zlib headers were not found… native-hadoop library needs zlib to build. Please install the requisite zlib development package.
          6. +
          7. 多次编译失败之后要记得执行 make distclean 清理一下。
          8. +
          9. 编译完 ant compile-native 之后,启动 hadoop 使用 http 访问 /dfshealth.jsp /jobtracker.jsp HTTP ERROR 404
          10. +
          11. 在 Linux 平台下编译 native hadoop 是不可以的,目前。错误:/hadoop-1.2.1/build.xml:694: exec returned: 1
          12. +
          +

          解决方案

          +
            +
          1. 第一个问题只能多次尝试。
          2. +
          3. 第二,第三个问题主要是是没有安装 zlib。顺便请保证 gcc c++, autoconf, automake, libtool, openssl,openssl-devel 也安装。安装 zlib 请参考 http://www.zlib.net/ 。
          4. +
          5. 第四个问题就是 基本的 make 三部曲的步骤。
          6. +
          7. 第五个问题原因是在 build native 库的同时,生成了 webapps 目录(在当前的 target 这个目录是个基本的结构,没有任何 jsp 等资源,404找不到很正常)。那么当我们编译过build之后,hadoop启动时又指向了这个目录,就导致这个错误。我们就可以直接将这个 build 文件夹删除了或者改脚本。问题搞定了。
          8. +
          9. 第六个问题,援引官方
            +Supported Platforms
            +The native hadoop library is supported on *nix platforms only. The library does not to work with Cygwin or the Mac OS X platform.
            +
            +那就老实点用 *nix platforms,就没事了。
          10. +
          +
          +
          +
          +
          +

          Java JNI练习

          + +

          + + + + + | 评论 +

          +
          + +

          在 Hadoop 中大量使用了 JNI 技术以提高性能并利用其他语言已有的成熟算法简化开发难度。如压缩算法、pipes 等。那么在具体学习 native hadoop, hadoop io 前先简单复习下相关知识。在此我主要是参见了 Oracle 官方网站 + IBM developerworks + csdn 论坛 写了个简单的 Hello World 程序。

          +

          此处主要是将我参考的资源进行了列举,已备具体深入学习参考。

          + +

          JNI 编程主要步骤

          +
            +
          1. 编写一个.java
          2. +
          3. javac *.java
          4. +
          5. javah -jni className -> *.h
          6. +
          7. 创建一个.so/.dll 动态链接库文件
          8. +
          +

          编程注意事项

          +
            +
          1. 不要直接使用从java里面传递过来的value.(在java里面的对象在本地调用前可能被jvm析构函数了)
          2. +
          3. 一旦不使用某对象或者变量,要去ReleaseXXX()。
          4. +
          5. 不要在 native code 里面去申请内存
          6. +
          7. 使用 javap -s 查看java 签名
          8. +
          +
          +
          + + +
          + +
          +
          +

          + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

          +
          + + + + + + + + + diff --git a/page/4/index.html b/page/4/index.html new file mode 100644 index 0000000..6f3c582 --- /dev/null +++ b/page/4/index.html @@ -0,0 +1,906 @@ + + + + + + + kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
          +

          kangfoo's blog

          +

          工作学习笔记,生活掠影。

          +
          +
          + +
          +
          +
          +
          +
          +

          HDFS API 练习使用

          + +

          + + + + + | 评论 +

          +
          + +

          经过前几页博客的知识巩固,现在开始使用 Hadoop API 不是什么难事。此处不重点讲述。参考 Hadoop API 利用 FileSystem 实例对象操作 FSDataInputStream/FSDataOutputStream 基本不是问题。

          +

          HDFS API入门级别的使用

          +
            +
          1. 获取 FileSystem 对象
            +get(Configuration conf)
            +Configuration 对象封装了客户端或者服务器端的 conf/core-site.xml 配置

            +
          2. +
          3. 通过 FileSystem 对象进行文件操作
            +读数据:open()获取FSDataInputStream(它支持随机访问),
            +写数据:create()获取FSDataOutputStream

            +
          4. +
          +

          参考代码:HDFSTest.java

          +

          代码中主要利用 FileSystem 对象进行文件的 读、写、重命名、删除、文件信息获取等操作。

          +
          +
          +
          +
          +

          Hadoop 机架感知

          + +

          + + + + + | 评论 +

          +
          + +

          HDFS 和 Map/Reduce 的组件是能够感知机架的。

          +

          NameNode 和 JobTracker 通过调用管理员配置模块中的 API resolve 来获取集群里每个 slave 的机架id。该 API 将 slave 的 DNS 名称(或者IP地址)转换成机架id。使用哪个模块是通过配置项 topology.node.switch.mapping.impl 来指定的。模块的默认实现会调用 topology.script.file.name 配置项指定的一个的脚本/命令。 如果 topology.script.file.name 未被设置,对于所有传入的IP地址,模块会返回 /default-rack 作为机架 id。

          +

          在 Map/Reduce 部分还有一个额外的配置项 mapred.cache.task.levels ,该参数决定 cache 的级数(在网络拓扑中)。例如,如果默认值是2,会建立两级的 cache—— 一级针对主机(主机 -> 任务的映射)另一级针对机架(机架 -> 任务的映射)。

          +

          我目前没有模拟环境先纪录个参考博客 机架感知 以备后用。

          +
          +
          +
          +
          +

          模拟使用 SecondaryNameNode 恢复 NameNode

          + +

          + + + + + | 评论 +

          +
          + +

          SecondaryNameNode

          +

          在试验前先了解下什么是 SecondaryNameNode、它的原理、检查点等知识点。再依次从开始配置 SecondaryNameNode 检查点、准备测试环境、模拟正常的 NameNode 故障,并手动启动 NameNode 并从 SecondaryNameNode 中恢复 fsimage。

          +

          :此试验思路主要借鉴于开源力量LouisT 老师 Hadoop Development 课程中的SecondaryNameNode章节。

          +

          作用

          +

          主要是为了解决namenode单点故障。不是 namenode 的备份。它周期性的合并 fsimage ( namenode 的镜像)和 editslog(或者 edits——所有对 fsimage 镜像文件操作的步骤),并推送给 namenode 以辅助恢复namenode。

          +

          SecondaryNameNode 定期合并 fsimage 和 edits 日志,将 edits 日志文件大小控制在一个限度下。因为内存需求和 NameNode 在一个数量级上,所以通常 SecondaryNameNode 和 NameNode 运行在不同的机器上。SecondaryNameNode 通过bin/start-dfs.sh 在 conf/masters 中指定的节点上启动。

          +

          在hadoop 2.x 中它的作用可以被两个节点替换:checkpoint node(于 SecondaryNameNode 作用相同), backup node( namenode 的完全备份)

          +

          原理(具体可参见《Hadoop权威指南》第10章 管理Hadoop)

          +

          edits 文件纪录了所有对 fsimage 镜像文件的写操作的步骤。文件系统客户端执行写操作时,这些操作首先会被记录到 edits 文件中。Nodename 在内存中维护文件系统的元数据;当 edits 被修改时,相关元数据也同步更新。内存中的元数据可支持客户端的读请求。

          +

          在每次执行写操作之后,且在向客户端发送成功代码之前,edits 编辑日志都需要更新和同步。当 namedone 向多个目录写数据时,只有在所有写操作执行完毕之后方可返回成功代码,以保证任何操作都不会因为机器故障而丢失。

          +

          fsimage 文件是文件系统元数据的一个永久检查点。它包含文件系统中的所有目录和文件 inode 的序列化谢谢。每个 inode 都是一个文件或目录的元数据的内部描述方式。对于文件来说,包含的信息有“复制级别”、修改时间、访问时间、访问许可、块大小、组成一个文件的块等;对于目录来说,包含的信息有修改时间、访问许可和配额元数据等信息。

          +

          fsimage 是一个大型文件,频繁执行写操作,会使系统运行极慢。并非每一写操作都会更新到 fsimage 文件。 +SecondaryNameNode 辅助 namenode,为 namenode 内存中的文件系统元数据创建检查点,并最终合并并更新 fsimage 镜像和减小 edits 文件。

          +

          SecondaryNameNode 的检查点

          +

          SecondaryNameNode 进程启动是由两个配置参数控制的。

          +
            +
          • fs.checkpoint.period,指定连续两次检查点的最大时间间隔, 默认值是1小时。
          • +
          • fs.checkpoint.size 定义了 edits 日志文件的最大值,一旦超过这个值会导致强制执行检查点(即使没到检查点的最大时间间隔)。默认值是64MB。
          • +
          +

          SecondaryNameNode 检查点的具体步骤

          +

          image

          +
            +
          1. SecondaryNameNode 请求主 namenode 停止使用 edits 文件,暂时将新的操作记录到 edits.new 文件中;
          2. +
          3. SecondaryNameNode 以 http get 复制 主 namenode 中的 fsimage, edits 文件;
          4. +
          5. SecondaryNameNode 将 fsimage 载入到内存,并逐一执行 edits 文件中的操作,创建新的fsimage.ckpt 文件;
          6. +
          7. SecondaryNameNode 以 http post 方式将新的fsimage.ckp 复制到主namenode.
          8. +
          9. 主 namenode 将 fsimage 文件替换为 fsimage.ckpt,同时将 edits.new 文件重命名为 edits。并更新 fstime 文件来记录下次检查点时间。
          10. +
          +

          SecondaryNameNode 保存最新检查点的目录与 NameNode 的目录结构相同。 所以 NameNode 可以在需要的时候读取 SecondaryNameNode上的检查点镜像。

          +

          模拟 NameNode 故障以从 SecondaryNameNode 恢复

          +

          场景假设:如果NameNode上除了最新的检查点以外,所有的其他的历史镜像和 edits 文件都丢失了,NameNode 可以引入这个最新的检查点以恢复。具体模拟步骤如下:

          +
            +
          1. 在配置参数 dfs.name.dir 指定的位置建立一个空文件夹;
          2. +
          3. 把检查点目录的位置赋值给配置参数 fs.checkpoint.dir;
          4. +
          5. 启动NameNode,并加上-importCheckpoint。
          6. +
          +

          NameNode 会从 fs.checkpoint.dir 目录读取检查点,并把它保存在 dfs.name.dir 目录下。 如果 dfs.name.dir 目录下有合法的镜像文件,NameNode 会启动失败。 NameNode 会检查fs.checkpoint.dir 目录下镜像文件的一致性,但是不会去改动它。

          +

          试验从 SecondaryNameNode 中备份恢复 NameNode

          +

          注意:此步骤执行并不能将原的数据文件系统从物理磁盘上移除,同样也不能在新格式化的 namenode 中查看旧的文件系统文件。请确定无误再试验。

          +

          试验知识准备

          +

          命令的使用方法请参考 SecondaryNameNode 命令。在试验前,可先了解些 hadoop 的默认配置 +core-site.xml-default, +hdfs-site.xml-default, +mapred-site.xml-default

          +

          SecondarynameNode 相关属性描述:

          +
          +属性:fs.checkpoint.dir     
          +值:${hadoop.tmp.dir}/dfs/namesecondary
          +描述:Determines where on the local filesystem the DFS secondary name node should store the temporary images to merge. If this is a comma-delimited list of directories then the image is replicated in all of the directories for redundancy.
          +fs.checkpoint.edits.dir
          +
          +属性:${fs.checkpoint.dir}     
          +值:Determines where on the local filesystem the DFS secondary name node should 
          +描述:store the temporary edits to merge. If this is a comma-delimited list of directoires then teh edits is replicated in all of the directoires for redundancy. Default value is same as fs.checkpoint.dir
          +
          +属性:fs.checkpoint.period  
          +值:3600   
          +描述:The number of seconds between two periodic checkpoints.
          +
          +属性:fs.checkpoint.size    
          +值:67108864   
          +描述:The size of the current edit log (in bytes) that triggers a periodic checkpoint even if the fs.checkpoint.period hasn't expired.
          +
          +

          试验环境配置

          +
            +
          1. 首先修改 core-site.xml 文件中的配置,主要是调小了 checkpoint 的周期并指定 SSN 的目录。

            +
            <property>
            +<name>fs.checkpoint.period</name>
            +<value>120</value>
            +</property>
            +<property>
            +<name>fs.checkpoint.dir</name>
            +<value>/home/${user.name}/env/data/snn</value>
            +</property>
            +

            vi hdfs-site.xml 查看 NameNode 数据文件存储路径

            +
            <property>
            + <name>dfs.name.dir</name>
            + <value>/home/${user.name}/env/data/name</value>
            +</property>
            +<property>
            + <name>dfs.data.dir</name>
            + <value>/home/${user.name}/env/data/data</value>
            +</property>
            +
          2. +
          3. 再次,format namenode 。 ./bin/hadoop namenode -format。查看当前的 master namenode namespaceID cat ./name/current/VERSION

            +
            #Tue Jan 21 15:14:40 CST 2014
            +namespaceID=1816120670 ## 文件系统的唯一标识符
            +cTime=0 ## namenode的创建时间,刚格式化为0,升级之后为时间戳
            +storageType=NAME_NODE ## 存储类型
            +layoutVersion=-41 ## 负的整数。描述了hdfs永久性数据结构的版本。与Hadoop的版本无关。与升级有关。
            +
          4. +
          5. 查看 datanode 下的version。cat data/current/VERSION

            +
            #Tue Jan 21 09:51:42 CST 2014
            +namespaceID=80003531
            +storageID=DS-949100596-192.168.56.12-50010-1387691685116
            +cTime=0
            +storageType=DATA_NODE
            +layoutVersion=-41
            +

            若 namespaceID 不相同,请将 datanode 中的id修改为 namenode 相同的 namespaceID。 +同样的步骤修改其他的 datanode. +若是第一次format可以跳过此步骤。此步骤要注意避免如下错误(Incompatible namespaceID):

            +
            2014-01-21 15:07:54,890 ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: java.io.IOException: Incompatible namespaceIDs in /home/hadoop/env/data/data: namenode namespaceID = 2020545490; datanode namespaceID = 80003531
            +
          6. +
          +

          查看 NameNode 试验前正常环境状况

          +
            +
          1. 启动hdfs./bin/start-dfs.sh

            +
          2. +
          3. jps 检查所有的进程(当前NameNode进程正常)

            +
            5832 SecondaryNameNode
            +6293 Jps
            +5681 NameNode
            +2212 DataNode
            +2198 DataNode
            +
          4. +
          5. 创建测试数据

            +
            $ ./bin/hadoop fs -mkdir /test
            +$ ./bin/hadoop fs -lsr /
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-21 15:21 /test
            +[hadoop@master11 hadoop]$ ./bin/hadoop fs -put ivy.xml /test
            +[hadoop@master11 hadoop]$ ./bin/hadoop fs -lsr /
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-21 15:22 /test
            +-rw-r--r--   2 hadoop supergroup      10525 2014-01-21 15:22 /test/ivy.xml
            +
          6. +
          7. 查看 SecondaryNameNode 文件目录

            +
            watch ls ./data/snn/ 
            +current
            +image
            +in_use.l
            +
          8. +
          9. namenode 对应日志

            +
            2014-01-21 15:54:02,654 INFO org.apache.hadoop.hdfs.server.namenode.FSNamesystem: Roll Edit Log from 192.168.56.11
            +2014-01-21 15:54:02,654 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: Number of transactions: 0 Total time for transactions(ms): 0 Number of transactions batched in Syncs: 0 Number of syncs: 0 SyncTimes(ms): 0
            +2014-01-21 15:54:02,655 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: closing edit log: position=4, editlog=/home/hadoop/env/data/name/current/edits
            +2014-01-21 15:54:02,655 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: close success: truncate to 4, editlog=/home/hadoop/env/data/name/current/edits
            +2014-01-21 15:54:02,778 INFO org.apache.hadoop.hdfs.server.namenode.TransferFsImage: Opening connection to http://0.0.0.0:50090/getimage?getimage=1
            +2014-01-21 15:54:02,781 INFO org.apache.hadoop.hdfs.server.namenode.GetImageServlet: Downloaded new fsimage with checksum: 4a75545e83f108e21ef321fb0066ede4
            +2014-01-21 15:54:02,781 INFO org.apache.hadoop.hdfs.server.namenode.FSNamesystem: Roll FSImage from 192.168.56.11
            +2014-01-21 15:54:02,781 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: Number of transactions: 0 Total time for transactions(ms): 0 Number of transactions batched in Syncs: 0 Number of syncs: 1 SyncTimes(ms): 56
            +2014-01-21 15:54:02,784 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: closing edit log: position=4, editlog=/home/hadoop/env/data/name/current/edits.new
            +2014-01-21 15:54:02,784 INFO org.apache.hadoop.hdfs.server.namenode.FSEditLog: close success: truncate to 4, editlog=/home/hadoop/env/data/name/current/edits.new
            +
          10. +
          11. namenode 文件目录

            +
            $ cd name/
            +$ tree
            +.
            +├── current
            +│   ├── edits
            +│   ├── fsimage
            +│   ├── fstime
            +│   └── VERSION
            +├── image
            +│   └── fsimage
            +├── in_use.lock
            +└── previous.checkpoint
            +├── edits
            +├── fsimage
            +├── fstime
            +└── VERSION
            +
          12. +
          +

          模拟 NameNode 故障

          +
            +
          1. 人为的杀掉 namenode 进程
            kill -9 6690 ## 6690 NameNode
            +删除 namenode 元数据
            +$ rm -rf ./data/name/*
            +删除 Secondary NameNode in_use.lock 文件 
            +$ rm -rf ./snn/in_use.lock
            +
          2. +
          +

          从 SecondaryNameNode 中恢复 NameNode

          +
            +
          1. 启动以 importCheckpoint 方式启动 NameNode。$ ./bin/hadoop namenode -importCheckpoint

            +
          2. +
          3. 验证是否恢复成功

            +
            ## HDFS 文件系统正常
            +$ ./bin/hadoop fsck /
            +The filesystem under path '/' is HEALTHY
            +$ ./bin/hadoop fs -lsr /
            +## 元文件信息已恢复
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-21 15:22 /test
            +-rw-r--r--   2 hadoop supergroup      10525 2014-01-21 15:22 /test/ivy.xml
            +$ tree
            +.
            +├── data
            +├── name(已恢复)
            +│   ├── current
            +│   │   ├── edits
            +│   │   ├── fsimage
            +│   │   ├── fstime
            +│   │   └── VERSION
            +│   ├── image
            +│   │   └── fsimage
            +│   ├── in_use.lock
            +│   └── previous.checkpoint
            +│       ├── edits
            +│       ├── fsimage
            +│       ├── fstime
            +│       └── VERSION
            +├── snn
            +│   ├── current
            +│   │   ├── edits
            +│   │   ├── fsimage
            +│   │   ├── fstime
            +│   │   └── VERSION
            +│   ├── image
            +│   │   └── fsimage
            +│   └── in_use.lock
            +└── tmp
            +9 directories, 16 files
            +
          4. +
          5. 查看恢复日志信息(截取部分信息)

            +
            ## copy fsimage
            +14/01/21 16:57:52 INFO common.Storage: Storage directory /home/hadoop/env/data/name is not formatted.
            +14/01/21 16:57:52 INFO common.Storage: Formatting ...
            +14/01/21 16:57:52 INFO common.Storage: Start loading image file /home/hadoop/env/data/snn/current/fsimage
            +14/01/21 16:57:52 INFO common.Storage: Number of files = 3
            +14/01/21 16:57:52 INFO common.Storage: Number of files under construction = 0
            +14/01/21 16:57:52 INFO common.Storage: Image file /home/hadoop/env/data/snn/current/fsimage of size 274 bytes loaded in 0 seconds.
            +##copy edits
            +4/01/21 16:57:52 INFO namenode.FSEditLog: Start loading edits file /home/hadoop/env/data/snn/current/edits
            +14/01/21 16:57:52 INFO namenode.FSEditLog: EOF of /home/hadoop/env/data/snn/current/edits, reached end of edit log Number of transactions found: 0.  Bytes read: 4
            +14/01/21 16:57:52 INFO namenode.FSEditLog: Start checking end of edit log (/home/hadoop/env/data/snn/current/edits) ...
            +14/01/21 16:57:52 INFO namenode.FSEditLog: Checked the bytes after the end of edit log (/home/hadoop/env/data/snn/current/edits):
            +14/01/21 16:57:52 INFO namenode.FSEditLog:   Padding position  = -1 (-1 means padding not found)
            +14/01/21 16:57:52 INFO namenode.FSEditLog:   Edit log length   = 4
            +14/01/21 16:57:52 INFO namenode.FSEditLog:   Read length       = 4
            +14/01/21 16:57:52 INFO namenode.FSEditLog:   Corruption length = 0
            +14/01/21 16:57:52 INFO namenode.FSEditLog:   Toleration length = 0 (= dfs.namenode.edits.toleration.length)
            +14/01/21 16:57:52 INFO namenode.FSEditLog: Summary: |---------- Read=4 ----------|-- Corrupt=0 --|-- Pad=0 --|
            +14/01/21 16:57:52 INFO namenode.FSEditLog: Edits file /home/hadoop/env/data/snn/current/edits of size 4 edits # 0 loaded in 0 seconds.
            +14/01/21 16:57:52 INFO common.Storage: Image file /home/hadoop/env/data/name/current/fsimage of size 274 bytes saved in 0 seconds.
            +14/01/21 16:57:54 INFO namenode.FSEditLog: closing edit log: position=4, editlog=/home/hadoop/env/data/name/current/edits
            +14/01/21 16:57:54 INFO namenode.FSEditLog: close success: truncate to 4, editlog=/home/hadoop/env/data/name/current/edits
            +14/01/21 16:57:54 INFO namenode.FSEditLog: Number of transactions: 0 Total time for transactions(ms): 0 Number of transactions batched in Syncs: 0 Number of syncs: 0 SyncTimes(ms): 0 
            +## 恢复 fsimage
            +14/01/21 16:57:54 INFO namenode.FSNamesystem: Finished loading FSImage in 1971 msecs
            +14/01/21 16:57:54 INFO hdfs.StateChange: STATE* Safe mode ON
            +... ...
            +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Safe mode termination scan for invalid, over- and under-replicated blocks completed in 15 msec
            +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Leaving safe mode after 33 secs
            +## 离开安全模式 Safe mode is OFF
            +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Safe mode is OFF
            +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* Network topology has 1 racks and 2 datanodes
            +14/01/21 16:58:26 INFO hdfs.StateChange: STATE* UnderReplicatedBlocks has 0 blocks
            +
          6. +
          +
          +
          +
          +
          +

          Hadoop 分布式文件系统

          + +

          + + + + + | 评论 +

          +
          + +

          管理网络跨多台计算机存储的文件系统称为分布式文件系统。当数据的大小超过单台物理计算机存储能力,就需要对它进行分区存储。Hadoop提供了一个综合性的文件系统抽象, Hadoop Distributed FileSystem 简称 HDFS或者DFS,Hadoop 分布式文件系统。它是Hadoop 3大组件之一。其他两大组件为 Hadoop-common 和 Hadoop-mapreduce。

          +

          传统以文件为基本单位的存储缺点:首先它很难实现并行化处理某个文件。单个节点一次只能处理一个文件,无法同时处理其他文件;再者,文件大小不同很难实现负载均衡。

          +

          HDFS的设计

          +
            +
          • HDFS以流式数据访问模式来存储超大文件,部署运行于廉价的机器上。
          • +
          • 可存储超大文件;流式访问,一次写入,多次读取;商用廉价PC,并不需要高昂的高可用的硬件。
          • +
          • 但不适用于,低时间延迟的访问;大小文件处理(浪费namenode内存,浪费磁盘空间。);多用户写入,任意修改文件(不支持并发写入。 同一时刻只能一个进程写入,不支持随机修改。)。
          • +
          +

          数据块

          +

          块是磁盘进行数据读写的最小单位,默认是512字节,构建单个磁盘之上的文件系统通过磁盘块来管理文件系统的来管理该文件系统中的块。HDFS的块默认是64MB,HDFS上的文件也被划分为块大小的多个分块(chunk),作为独立的存储单元。HDFS块默认64MB的好处是为了简化磁盘寻址的开销。

          +

          HDFS块的抽象好处

          +
            +
          • 一个文件的大小,可以大于网络中任意一个硬盘的大小。文件的块并不需要存储在同一个硬盘上可以存储在分布式文件系统集群中任意一个硬盘上。
          • +
          • 大大简化系统设计。这点对于故障种类繁多的分布式系统来说尤为重要。以块为单位,不仅简化存储管理(块大小是固定的,很容易计算一个硬盘放多少个块);而且,消除了元数据的顾虑(因为Block仅仅是存储的一块数据,其文件的元数据,例如权限等就不需要跟数据块一起存储,可以交由另外的其他系来处理)。适合批处理。支持离线的批量数据处理,支持高吞吐量。
          • +
          • 块更适合于数据备份。进而提供数据容错能力和系统可用性(将每个块复制至少几个独立的机器上,可以确保在发生块、磁盘或机器故障后数据不丢失。一旦发生某个块不可用,系统将从其他地方复制一份复本。以保证复本的数量恢复到正常水平)。容错性高,容易实现负载均衡。
          • +
          +

          Namenode 和 Datanode

          +

          HDFS采用master/slave架构。一个HDFS集群是由一个Namenode和一定数目的 Datanodes 组成。Namenode是一个中心服务器,负责管理文件系统的名字空间(namespace)以及客户端对文件的访问。集群中的Datanode一般是一个节点一个,负责管理它所在节点上的存储。HDFS暴露了文件系统的名字空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组Datanode上。Namenode执行文件系统的namespace操作。比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的 映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。

          +

          NameNode上维护文件系统树及整棵树内所有的文件和目录,并永久保存在本地磁盘,fsimage和editslog。

          +

          NameNode 将对文件系统的改动追加保存到本地文件系统上的一个日志文件(edits)。当一个 NameNode 启动时,它首先从一个映像文件(fsimage)中读取 HDFS 的状态,接着应用日志文件中的 edits 操作。然后它将新的 HDFS 状态写入(fsimage)中,并使用一个空的 edits 文件开始正常操作。因为 NameNode 只有在启动阶段才合并 fsimage 和 edits,所以久而久之日志文件可能会变得非常庞大,特别是对大型的集群。日志文件太大的副作用是下一次 NameNode 启动会花很长时间。

          +

          Hadoop HDFS 架构图

          +

          image

          +

          在上图中NameNode是Master上的进程,复制控制底层文件的io操作,处理mapreduce任务等。 +DataNode运行在slave机器上,负责实际的地层文件io读写。由NameNode存储管理文件系统的命名空间。

          +

          客户端代表用户通过与NameNode和DataNode交互来访问整个文件系统。

          +

          HDFS 读过程

          +

          image

          +
            +
          1. 客户端通过调用 FileSystem 对象的 open() 方法来打开要读取的文件。(步骤 1)在HDFS中是个 DistributedFileSystem 中的一个实例对象。
          2. +
          3. (步骤 2)DistributedFileSystem 通过PRC[需要提供一个外链接介绍RPC技术]调用 namenode ,获取文件起始块的位置。对于每个块,namenode 返回存有该块复本的 datanode 地址。Datanode 根据它们于该客户端的距离排序,如果该客户端就是一个 datanode 并保存有该数据块的一个复本,该节点就直接从本地 datanode 中读取数据。反之取网路拓扑最短路径。
          4. +
          5. DistributedFileSystem 返回 FSDataInputStream 给客户端,用来读取数据。FSDataInputStream 类封装 DFSInputStream 对象。由 DFSInputStream 负责管理 DataNode 和 NameNode 的 I/O。
          6. +
          7. (步骤 3)客户端调用 stream 的 read() 函数开始读取数据。
          8. +
          9. 存储着文件起始块的 DataNode 地址的 DFSInputStream 随即连接距离最近的 DataNode。反复 read() 方法,将数据从 datanode 传输到客户端。(网络拓扑与Hadoop[机架感知] ? 连接待补。)
          10. +
          11. 到达块的末端时,DFSInputStream 关闭和此数据节点的连接,然后连接此文件下一个数据块的最佳DataNode。
          12. +
          13. 客户端读取数据时,块也是按照打开 DFSInputStream 与 datanode 建立连接的顺序读取的。以通过询问NameNode 来检索下一批所需块的 datanode。当客户端读取完毕数据的时候,调用 FSDataInputStream 的 close() 函数。
          14. +
          15. 在读取数据的过程中,如果客户端在与数据节点通信出现错误,则尝试连接包含此数据块的下一个数据节点。失败的数据节点将被记录,以后不再连接。
          16. +
          +
          源代码理解
          +
            +
          1. 在客户端执行 class DistributedFileSystem open() 方法(装饰模式),打开文件并返回 DFSInputStream。

            +
            // DistributedFileSystem extends FileSystem --> 调用 open() 方法
            +public FSDataInputStream open(Path f, int bufferSize) throws IOException {
            +    statistics.incrementReadOps(1);
            +    return new DFSClient.DFSDataInputStream(
            +        dfs.open(getPathName(f), bufferSize, verifyChecksum, statistics));
            +}
            +
            // dfs 是 DFSClient 的实例对象。
            +public DFSInputStream open(String src, int buffersize, boolean verifyChecksum,
            +               FileSystem.Statistics stats
            +) throws IOException {
            +    checkOpen(); 
            +    //    Get block info from namenode
            +    return new DFSInputStream(src, buffersize, verifyChecksum);
            +}
            +
            // DFSInputStream 是 DFSClient 的内部类,继承自 FSInputStream。
            +// 调用的构造函数(具体略)中调用了 openInfo() 方法。在 openInfo() 中 重要的是 fetchLocatedBlocks() 向 NameNode 询问所需要的数据的元信息,通过 callGetBlockLocations() 实现。 此过程若没有找到将尝试3次。 
            +//
            +// 由 callGetBlockLocations()通过 RPC 方式询问 NameNode 获取到 LocatedBlocks 信息。
            +static LocatedBlocks callGetBlockLocations(ClientProtocol namenode,
            +  String src, long start, long length) throws IOException {
            +… … 
            +    return namenode.getBlockLocations(src, start, length);
            +… … 
            +}    
            +
            // 此处的 namenode 是通过代理模式创建的。它是 namenode  ClientProtocol 的实现(interface ClientProtocol extends VersionedProtocol)。
            +private static ClientProtocol createNamenode(ClientProtocol rpcNamenode,
            +Configuration conf) throws IOException {
            +… … 
            +    final ClientProtocol cp = (ClientProtocol)RetryProxy.create(ClientProtocol.class, rpcNamenode, defaultPolicy, methodNameToPolicyMap);
            +    RPC.checkVersion(ClientProtocol.class, ClientProtocol.versionID, cp);
            +… … 
            +}  
            +
          2. +
          3. 在 class NameNode 端获取数据块位置信息并排序

            +
            public LocatedBlocks   getBlockLocations(String src, long offset, long length) throws IOException {
            +    myMetrics.incrNumGetBlockLocations();
            +    // 获取数据块信息。namenode 为 FSNamesystem 实例。
            +    // 保存的是NameNode的name space树,其属性 FSDirectory dir 关联着 FSImage fsimage 信息,
            +    // fsimage 关联 FSEditLog editLog。
            +    return namesystem.getBlockLocations(getClientMachine(), src, offset, length);
            +}
            +
            // 类 FSNamesystem.getBlockLocationsInternal() 是具体获得块信息的实现。
            +private synchronized LocatedBlocks getBlockLocationsInternal(String src,
            +long offset, long length, int nrBlocksToReturn, 
            +boolean doAccessTime,  boolean needBlockToken) throws IOException {
            +    … … 
            +}  
            +
          4. +
          5. 在客户端DFSClient将步骤1中打开的读文件, DFSDataInputStream 对象内部的 DFSInputStream 对象的 read(long position, byte[] buffer, int offset, int length)方法进行实际的文件读取

            +
            // class DFSInputStream
            +public int read(long position, byte[] buffer, int offset, int length)
            +  throws IOException {
            +  // sanity checks
            +  checkOpen();
            +  if (closed) {
            +    throw new IOException("Stream closed");
            +  }
            +  failures = 0;
            +  long filelen = getFileLength();
            +  if ((position < 0) || (position >= filelen)) {
            +    return -1;
            +  }
            +  int realLen = length;
            +  if ((position + length) > filelen) {
            +    realLen = (int)(filelen - position);
            +  }
            +  //
            +  // determine the block and byte range within the block
            +  // corresponding to position and realLen
            +  // 判断块内的块和字节范围,位置和实际的长度
            +  List<LocatedBlock> blockRange = getBlockRange(position, realLen);
            +  int remaining = realLen;
            +  for (LocatedBlock blk : blockRange) {
            +    long targetStart = position - blk.getStartOffset();
            +    long bytesToRead = Math.min(remaining, blk.getBlockSize() - targetStart);
            +    fetchBlockByteRange(blk, targetStart, 
            +                        targetStart + bytesToRead - 1, buffer, offset);
            +    remaining -= bytesToRead;
            +    position += bytesToRead;
            +    offset += bytesToRead;
            +  }
            +  assert remaining == 0 : "Wrong number of bytes read.";
            +  if (stats != null) {
            +    stats.incrementBytesRead(realLen);
            +  }
            +  return realLen;
            +} 
            +
            // fetchBlockByteRange() 通过 socket 连接一个最优的 DataNode 来读取数据
            +private void fetchBlockByteRange(LocatedBlock block, long start,
            +                                 long end, byte[] buf, int offset) throws IOException {
            +  //
            +  // Connect to best DataNode for desired Block, with potential offset
            +  //
            +  Socket dn = null;
            +  int refetchToken = 1; // only need to get a new access token once
            +  //      
            +  while (true) {
            +    // cached block locations may have been updated by chooseDataNode()
            +    // or fetchBlockAt(). Always get the latest list of locations at the 
            +    // start of the loop.
            +    block = getBlockAt(block.getStartOffset(), false);
            +    DNAddrPair retval = chooseDataNode(block); // 选者最DataNode
            +    DatanodeInfo chosenNode = retval.info;
            +    InetSocketAddress targetAddr = retval.addr;
            +    BlockReader reader = null;
            +    try {
            +      Token<BlockTokenIdentifier> accessToken = block.getBlockToken();
            +      int len = (int) (end - start + 1);
            +  //
            +      // first try reading the block locally.
            +      if (shouldTryShortCircuitRead(targetAddr)) {// 本地优先
            +        try {
            +          reader = getLocalBlockReader(conf, src, block.getBlock(),
            +              accessToken, chosenNode, DFSClient.this.socketTimeout, start);
            +        } catch (AccessControlException ex) {
            +          LOG.warn("Short circuit access failed ", ex);
            +          //Disable short circuit reads
            +          shortCircuitLocalReads = false;
            +          continue;
            +        }
            +      } else {
            +        // go to the datanode
            +        dn = socketFactory.createSocket(); // socke datanode
            +        LOG.debug("Connecting to " + targetAddr);
            +        NetUtils.connect(dn, targetAddr, getRandomLocalInterfaceAddr(),
            +            socketTimeout);
            +        dn.setSoTimeout(socketTimeout);
            +        reader = RemoteBlockReader.newBlockReader(dn, src, 
            +            block.getBlock().getBlockId(), accessToken,
            +            block.getBlock().getGenerationStamp(), start, len, buffersize, 
            +            verifyChecksum, clientName);
            +      }
            +      int nread = reader.readAll(buf, offset, len); // BlockReader 负责读取数据
            +      return;
            +    }
            +    … … 
            +    finally {
            +      IOUtils.closeStream(reader);
            +      IOUtils.closeSocket(dn);
            +    }
            +    // Put chosen node into dead list, continue
            +    addToDeadNodes(chosenNode); // dead datanode
            +  }
            +}
            +
          6. +
          7. NameNode 实例化启动时便监听客户端请求

            +
            DataNode(final Configuration conf,
            +       final AbstractList<File> dataDirs, SecureResources resources) throws IOException {
            +super(conf);
            +SecurityUtil.login(conf, DFSConfigKeys.DFS_DATANODE_KEYTAB_FILE_KEY, 
            +    DFSConfigKeys.DFS_DATANODE_USER_NAME_KEY);
            +//
            +datanodeObject = this;
            +durableSync = conf.getBoolean("dfs.durable.sync", true);
            +this.userWithLocalPathAccess = conf
            +    .get(DFSConfigKeys.DFS_BLOCK_LOCAL_PATH_ACCESS_USER_KEY);
            +try {
            +  startDataNode(conf, dataDirs, resources);// startDataNode
            +} catch (IOException ie) {
            +  shutdown();
            +  throw ie;
            +}   
            +}
            +
            // startDataNode
            +void startDataNode(Configuration conf, 
            +                 AbstractList<File> dataDirs, SecureResources resources
            +                 ) throws IOException {
            +… …                      
            +// find free port or use privileged port provide
            +ServerSocket ss;
            +if(secureResources == null) {
            +  ss = (socketWriteTimeout > 0) ? 
            +    ServerSocketChannel.open().socket() : new ServerSocket();
            +  Server.bind(ss, socAddr, 0);
            +} else {
            +  ss = resources.getStreamingSocket();
            +}
            +ss.setReceiveBufferSize(DEFAULT_DATA_SOCKET_SIZE); 
            +// adjust machine name with the actual port
            +tmpPort = ss.getLocalPort();
            +selfAddr = new InetSocketAddress(ss.getInetAddress().getHostAddress(),
            +//                                     tmpPort);
            +this.dnRegistration.setName(machineName + ":" + tmpPort);
            +LOG.info("Opened data transfer server at " + tmpPort);
            +//
            +this.threadGroup = new ThreadGroup("dataXceiverServer");
            +this.dataXceiverServer = new Daemon(threadGroup, 
            +    new DataXceiverServer(ss, conf, this));
            +this.threadGroup.setDaemon(true); // DataXceiverServer为守护线程监控客户端连接
            +}
            +
            // class DataXceiverServer.run()
            +public void run() {
            +while (datanode.shouldRun) {
            +  try {
            +    Socket s = ss.accept();
            +    s.setTcpNoDelay(true);
            +    new Daemon(datanode.threadGroup, 
            +        new DataXceiver(s, datanode, this)).start();
            +  } catch (SocketTimeoutException ignored) {
            +  }
            +}
            +}   
            +
            // class DataXceiver.run()
            +// Read/write data from/to the DataXceiveServer.
            +// 操作类型:OP_READ_BLOCK,OP_WRITE_BLOCK,OP_REPLACE_BLOCK,
            +// OP_COPY_BLOCK,OP_BLOCK_CHECKSUM
            +public void run() {
            +DataInputStream in=null; 
            +try {
            +  in = new DataInputStream(
            +      new BufferedInputStream(NetUtils.getInputStream(s), 
            +                              SMALL_BUFFER_SIZE));
            +… … 
            +  switch ( op ) {
            +  case DataTransferProtocol.OP_READ_BLOCK:
            +    readBlock( in );// 读数据
            +    datanode.myMetrics.addReadBlockOp(DataNode.now() - startTime);
            +    if (local)
            +      datanode.myMetrics.incrReadsFromLocalClient();
            +    else
            +      datanode.myMetrics.incrReadsFromRemoteClient();
            +    break;
            +… … 
            +  default:
            +    throw new IOException("Unknown opcode " + op + " in data stream");
            +  }
            +}  
            +
            // class DataXceiver.readBlock()
            +// Read a block from the disk.
            +private void readBlock(DataInputStream in) throws IOException {
            +//
            +// Read in the header,读指令
            +//
            +long blockId = in.readLong();          
            +Block block = new Block( blockId, 0 , in.readLong());
            +// 
            +long startOffset = in.readLong();
            +long length = in.readLong();
            +String clientName = Text.readString(in);
            +Token<BlockTokenIdentifier> accessToken = new Token<BlockTokenIdentifier>();
            +accessToken.readFields(in);
            +// 向客户端写数据
            +OutputStream baseStream = NetUtils.getOutputStream(s, 
            +    datanode.socketWriteTimeout);
            +DataOutputStream out = new DataOutputStream(
            +             new BufferedOutputStream(baseStream, SMALL_BUFFER_SIZE));
            +… … 
            +// send the block,读取本地的block的数据,并发送给客户端
            +BlockSender blockSender = null;
            +final String clientTraceFmt =
            +  clientName.length() > 0 && ClientTraceLog.isInfoEnabled()
            +    ? String.format(DN_CLIENTTRACE_FORMAT, localAddress, remoteAddress,
            +        "%d", "HDFS_READ", clientName, "%d", 
            +        datanode.dnRegistration.getStorageID(), block, "%d")
            +    : datanode.dnRegistration + " Served " + block + " to " +
            +        s.getInetAddress();
            +try {
            +  try {
            +    blockSender = new BlockSender(block, startOffset, length,
            +        true, true, false, datanode, clientTraceFmt);
            +  } catch(IOException e) {
            +    out.writeShort(DataTransferProtocol.OP_STATUS_ERROR);
            +    throw e;
            +  }
            +  out.writeShort(DataTransferProtocol.OP_STATUS_SUCCESS); // send op status
            +  long read = blockSender.sendBlock(out, baseStream, null); // send data,发送数据
            +  … … 
            +} finally {
            +  IOUtils.closeStream(out);
            +  IOUtils.closeStream(blockSender);
            +}
            +}
            +
          8. +
          +

          HDFS 写过程

          +

          image

          +
            +
          1. 客户端通过对 DistributedFileSystem 对象调用 create() 方法来创建文件(步骤1)。
          2. +
          3. DistributedFileSystem 通过 PRC 对 namenode 调用create() 方法,在文件系统的命名空间中创建一个新的没有数据块文件(步骤2)。
          4. +
          5. namenode 检查并确保此文件不存在,并且客户端由创建该文件的权限。通过 namenode 即为创建的新文件创建一条纪录;否则,创建失败,并向客户端抛出 IOException 异常。
          6. +
          7. DistributedFileSystem 向客户端返回一个 FSDataOutputStream 对象。客户端可以开始写数据。FSDataOutputStream 同样封装一个 DFSoutPutstream 对象。由 DFSInputStream 负责处理 DataNode 和 NameNode 的 I/O。
          8. +
          9. (步骤3)在客户端写数据时,DFSoutPutstream 将它分成一个个的数据包,并写入内部队列(数据队列)。
          10. +
          11. 由 DataStreamer(DFSClient 内部类) 处理数据队列。它根据 datanode 列表要求 namenode 分配适合的新块来存储数据备份。这组 datanode 构成一个管线。假设当前复制数为3,那么管线中将有3个节点。DataStreamer 将数据包流式传输到管线(pipeline)的第一个 datanode 节点。该 datanode 存储数据包并将它发送到管线中的第2个 datanode。 同样地,第二个 datanode 存储该数据包并发哦少年宫到管线中的第3个 datanode(步骤4)。
          12. +
          13. DFSOutputStream 内部维护一个对应的数据包队列等待 datanode 收到确认确认回执(ack queue),当 DFSOutputStream 收到所有的 datanode 确认信息之后,该数据包才从确认队列中删除。
          14. +
          15. 若在写数据时,datanode 发生故障。则先关闭管线,确认把队列中任何数据包都添加回数据队列的最前端,以确保故障节点下游的 datanode 不会漏掉任何一个数据包。并为存储在另一个 datanode 的当前数据块指定一个新的标志,并将该标志发送个 namenode,以便故障的 datanode 在恢复后可以删除存储的部分数据块。从管线中删除故障 datanode 节点并把余下的数据块写入管线中的2个 datanode。Namenode 注意到块复本量不足时,会在另一个节点上创建一个新的复本。后续数据块继续正常处理。一个块在写入期间发生多个 datanode 故障的概率不高,只要写入了最小复本数(dfs.replication.min默认为1),写入即为成功。此块由异步执行复制以达到目标复本数,默认为3。
          16. +
          17. 当客户端结束写入数据,则调用 stream 的 close()函数。此操作将剩余所有的数据包写入 datanode pipeline 中,并等待 ack queue 返回成功。最后通知元数据节点写入完毕。namenode 是通过 Datastreamer 询问的数据块的分配,它在返回成功前只需要等待数据块进行最小量的复制。
          18. +
          +
          源代码理解
          +

          TODO,原笔记已丢失,待补。 +hdfs 架构一页。且读过程和写过程各独立一页。

          +

          NadeNode 和 DataNode 实现的协议

          +

          TODO ,独立 一页

          +

          补充

          +

          详细介绍HDFS读写过程解析

          +
          +
          +
          +
          +

          Hadoop1.x 学习准备

          + +

          + + + + + | 评论 +

          +
          + +

          开始学习Hadoop之前先了解下Hadoop之父Doug Cutting 膜拜是必须的。路子是一步不走出来了。各位前辈给我们留下了宝贝的资源。不加以学习利用有些说不过去。

          +

          我个人是从2013年夏天开始拿到 《Hadoop权威指南》第二版的,但由于各种原因,也可以直接说我比较懒,从夏天到冬季中途到是翻过那书,感觉每次都没由什么实际的记忆和理解过程。在12月初,发现开源力量提供了相关的网上视频在线教学课程。选择适合自己的就是对的。 +,我毅然成为了其中的一员。 +目前我没有从事相关的工作,出于在工作之外寻找乐趣,就来了。

          +

          学习之前还是多看些相关的资源比较容易找到赶脚。

          +
          资源列举:
          + +

          部分网站可能是比较难打开的。经常遇到问题谷歌之后发现很多问题还是跳转到这些权威论坛上面了。先登记在案,以备使用。在此仅以纪录我学习《Hadoop权威指南》一书的笔记。 +笔记中图片资源大部分出自于原书英文版,章节内容来自于原书中/英文版 + 开源力量 LouisT 老师ppt/课堂示例及扩展,当然不乏在网站上找到各位博主的精品博客参考。

          +

          我自己的练习代码存放在 github。

          +
          +
          + + +
          + +
          +
          +

          + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

          +
          + + + + + + + + + diff --git a/page/5/index.html b/page/5/index.html new file mode 100644 index 0000000..1f9c340 --- /dev/null +++ b/page/5/index.html @@ -0,0 +1,1011 @@ + + + + + + + kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + + +
          +

          kangfoo's blog

          +

          工作学习笔记,生活掠影。

          +
          +
          + +
          +
          +
          +
          +
          +

          Hadoop1.x Wordcount分析

          + +

          + + + + + | 评论 +

          +
          + +

          hadoop mapreduce 过程粗略的分为:map, redurce(copy, sort, reduce)两个阶段。具体的工作机制还是挺复杂的,这里主要通过hadoop example jar中提供的wordcount来对hadoop mapredurce做个简单的理解。Wordcount程序输入文件类型,计算单词的频率。输出是文本文件:每行是单词和它出现的频率,用Tab键隔开。

          +

          Hadoop内置的计数器,

          +
            +
          1. 首先确保Hadoop集群正常运行,并了解mapredurce工作时涉及到的基本的文件备配。vi mapred-site.xml

            + +
            <configuration>
            +<property>
            +<name>mapred.job.tracker</name> <!--JobTracker的主机(或者IP)和端口。 -->
            +<value>master11:9001</value>
            +</property>
            +<property>
            +<name>mapred.system.dir</name> <!--Map/Reduce框架存储系统文件的HDFS路径。-->
            +<value>/home/${user.name}/env/mapreduce/system</value>
            +</property>
            +<property>
            +<name>mapred.local.dir</name> <!--Map/Reduce在本地文件系统下中间结果存放路径. -->
            +<value>/home/${user.name}/env/mapreduce/local</value>
            +</property>
            +</configuration>
            +
          2. +
          3. 上传一个文件到hdfs文件系统

            + +
            $ ./bin/hadoop fs -mkdir /test/input
            +$ ./bin/hadoop fs -put ./testDir/part0 /test/input
            +$ ./bin/hadoop fs -lsr /
            +## part0 文件中的内容为:
            +hadoop zookeeper hbase hive
            +rest osgi http ftp
            +hadoop zookeeper
            +
          4. +
          5. 执行workcount +$ ./bin/hadoop jar hadoop-examples-1.2.1.jar wordcount /test/input /test/output
            日志如下

            + +
            14/01/19 18:23:25 INFO input.FileInputFormat: Total input paths to process : 1
            +## 使用 native-hadoop library
            +14/01/19 18:23:25 INFO util.NativeCodeLoader: Loaded the native-hadoop library
            +14/01/19 18:23:25 WARN snappy.LoadSnappy: Snappy native library not loaded
            +14/01/19 18:23:25 INFO mapred.JobClient: Running job: job_201401181723_0005
            +14/01/19 18:23:26 INFO mapred.JobClient:  map 0% reduce 0%
            +14/01/19 18:23:32 INFO mapred.JobClient:  map 100% reduce 0%
            +14/01/19 18:23:40 INFO mapred.JobClient:  map 100% reduce 33%
            +14/01/19 18:23:42 INFO mapred.JobClient:  map 100% reduce 100%
            +## jobid job_201401181723_0005 (job_yyyyMMddHHmm_(顺序自然数,不足4位补0,已保证磁盘文件目录顺序))
            +14/01/19 18:23:43 INFO mapred.JobClient: Job complete: job_201401181723_0005
            +## Counters 计数器
            +14/01/19 18:23:43 INFO mapred.JobClient: Counters: 29
            +## Job Counters
            +14/01/19 18:23:43 INFO mapred.JobClient:   Job Counters
            +14/01/19 18:23:43 INFO mapred.JobClient:     Launched reduce tasks=1
            +14/01/19 18:23:43 INFO mapred.JobClient:     SLOTS_MILLIS_MAPS=6925
            +14/01/19 18:23:43 INFO mapred.JobClient:     Total time spent by all reduces waiting after reserving slots (ms)=0
            +14/01/19 18:23:43 INFO mapred.JobClient:     Total time spent by all maps waiting after reserving slots (ms)=0
            +14/01/19 18:23:43 INFO mapred.JobClient:     Launched map tasks=1
            +14/01/19 18:23:43 INFO mapred.JobClient:     Data-local map tasks=1
            +14/01/19 18:23:43 INFO mapred.JobClient:     SLOTS_MILLIS_REDUCES=9688
            +## File Output Format Counters
            +14/01/19 18:23:43 INFO mapred.JobClient:   File Output Format Counters
            +14/01/19 18:23:43 INFO mapred.JobClient:     Bytes Written=63
            +## FileSystemCounters
            +14/01/19 18:23:43 INFO mapred.JobClient:   FileSystemCounters
            +14/01/19 18:23:43 INFO mapred.JobClient:     FILE_BYTES_READ=101
            +14/01/19 18:23:43 INFO mapred.JobClient:     HDFS_BYTES_READ=167
            +14/01/19 18:23:43 INFO mapred.JobClient:     FILE_BYTES_WRITTEN=112312
            +14/01/19 18:23:43 INFO mapred.JobClient:     HDFS_BYTES_WRITTEN=63
            +## File Input Format Counters
            +14/01/19 18:23:43 INFO mapred.JobClient:   File Input Format Counters
            +14/01/19 18:23:43 INFO mapred.JobClient:     Bytes Read=65
            +## Map-Reduce Framework
            +14/01/19 18:23:43 INFO mapred.JobClient:   Map-Reduce Framework
            +14/01/19 18:23:43 INFO mapred.JobClient:     Map output materialized bytes=101
            +14/01/19 18:23:43 INFO mapred.JobClient:     Map input records=3
            +14/01/19 18:23:43 INFO mapred.JobClient:     Reduce shuffle bytes=101
            +14/01/19 18:23:43 INFO mapred.JobClient:     Spilled Records=16
            +14/01/19 18:23:43 INFO mapred.JobClient:     Map output bytes=104
            +14/01/19 18:23:43 INFO mapred.JobClient:     Total committed heap usage (bytes)=176230400
            +14/01/19 18:23:43 INFO mapred.JobClient:     CPU time spent (ms)=840
            +14/01/19 18:23:43 INFO mapred.JobClient:     Combine input records=10
            +14/01/19 18:23:43 INFO mapred.JobClient:     SPLIT_RAW_BYTES=102
            +14/01/19 18:23:43 INFO mapred.JobClient:     Reduce input records=8
            +14/01/19 18:23:43 INFO mapred.JobClient:     Reduce input groups=8
            +14/01/19 18:23:43 INFO mapred.JobClient:     Combine output records=8
            +14/01/19 18:23:43 INFO mapred.JobClient:     Physical memory (bytes) snapshot=251568128
            +14/01/19 18:23:43 INFO mapred.JobClient:     Reduce output records=8
            +14/01/19 18:23:43 INFO mapred.JobClient:     Virtual memory (bytes) snapshot=1453596672
            +14/01/19 18:23:43 INFO mapred.JobClient:     Map output records=10
            +
          6. +
          7. 运行之后的文件系统结构 +
            日志如下

            + +
            drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/input
            +-rw-r--r--   2 hadoop supergroup         65 2014-01-19 18:23 /test/input/part0
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/output
            +-rw-r--r--   2 hadoop supergroup          0 2014-01-19 18:23 /test/output/_SUCCESS
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/output/_logs
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-19 18:23 /test/output/_logs/history
            +## job 执行结果的数据文件
            +-rw-r--r--   2 hadoop supergroup      13647 2014-01-19 18:23 /test/output/_logs/history/job_201401181723_0005_1390127005579_hadoop_word+count
            +## job 配置文件
            +-rw-r--r--   2 hadoop supergroup      48374 2014-01-19 18:23 /test/output/_logs/history/job_201401181723_0005_conf.xml
            +## 只分了1个
            +-rw-r--r--   2 hadoop supergroup         63 2014-01-19 18:23 /test/output/part-r-00000
            +drwxr-xr-x   - hadoop supergroup          0 2013-12-22 14:02 /user
            +drwxr-xr-x   - hadoop supergroup          0 2014-01-18 23:16 /user/hadoop
            +
          8. +
          9. 查看结果。$ ./bin/hadoop fs -cat /test/output/part-r-00000

            + +
            ftp     1
            +hadoop  2
            +hbase   1
            +hive    1
            +http    1
            +osgi    1
            +rest    1
            +zookeeper       2
            +
          10. +
          11. 更详细的信息可web访问 http://master11:50030/jobtracker.jsp 进行查看.

            +
          12. +
          +
          +
          +
          +
          +

          Hadoop1.x 命令手册列举

          + +

          + + + + + | 评论 +

          +
          + +

          hadoop命令一般分为两类:

          +

          用户命令
          archive, distcp, fs, fsck, jar, job, pipes, version, CLASSNAME

          +

          管理命令
          balancer, daemonlog, datanode, dfsadmin, jobtracker, namenode, secondarynamenode, tasktracker

          +

          用户命令

          +
            +
          1. fs命令,可参见 file system shell

            + +
            ## 新建一个文件夹
            +$ ./hadoop fs -mkdir /test
            +## 上传一个文件到hdfs
            +$ ./hadoop fs -put ./rcc /test
            +## 查看文件
            +$ ./hadoop fs -lsr /test
            +## 文件模式  备份个数   用户   用户组   字节大小 最后修改日期 和时间 文件或者目录的绝对路径
            +##-rw-r--r--   2   hadoop supergroup 2810   2014-01-18 19:20  /test/rcc
            +## 
            +## 从hdfs文件系统中复制一个文件到本地
            +$ ./hadoop fs -copyToLocal /test/rcc rcc.copy
            +## md5对比
            +$ md5sum rcc rcc.copy
            +# b9d8a383bba2dd1b2ee0ede6c5cabeae  rcc
            +# b9d8a383bba2dd1b2ee0ede6c5cabeae  rcc.copy
            +##
            +## 删除
            +$ ./hadoop fs -rmr /test
            +
          2. +
          3. fsck命令。 可以参考文件系统的健康状态;查看一个文件所在的数据块;可以删除一个坏块;可以查找一个缺失的块。 可参见fsck

            + +
            ## 新建一个文件夹
            +$ ./hadoop fsck /
            +
          4. +
          5. archive命令。 创建一个hadoop档案文件。语法:archive -archiveName NAME -p <parent path> <src>* <dest> 可参考hadoop_archives

            + +
            ## 创建一个归档文件
            +$ ./hadoop archive -archiveName archive.har -p  /test rcc
            +## 查看归档文件列表
            +$ ./hadoop dfs -lsr har:///user/hadoop/rcc/archive.har
            +## 查看归档文件
            +$ ./hadoop dfs -cat har:///user/hadoop/rcc/archive.har/rcc
            +
          6. +
          7. distcp 并行复制。可以从 Hadoop 文件系统中复制大量数据,也可以将大量数据复制到HDFS文件系统中。它其实是个没有reducer的MapReduce作业。

            + +
            $ hadoop distcp hadfs://datanode1/test1 hdfs://datanode2/
            +## 跨 RPC 版本可使用 http 方式进行复制
            +$ hadoop distcp http://datanode1:50070/test1 hdfs://datanode2/
            +

            管理命令

            +
          8. +
          9. dfsadmin

            + +
            ## 报告文件系统的基本信息和统计信息
            +$ ./hadoop dfsadmin -report
            +## 安全模式维护命令
            +$ ./hadoop dfsadmin -safemode enter
            +## -safemode enter | leave | get | wait
            +## 不接受对名字空间的更改(只读), 不复制或删除块
            +## -setQuota 为每个目录 <dirname>设定配额<quota>。包括文件夹和文件名称。
            +$ ./hadoop dfsadmin -setQuota 2 /test
            +$ ./hadoop fs -put ./rcc /test
            +$ ./hadoop fs -put ./hadoop /test
            +put: org.apache.hadoop.hdfs.protocol.NSQuotaExceededException: The NameSpace quota (directories and files) of directory /test is exceeded: quota=2 file count=3
            +
          10. +
          11. balancer命令。集群平衡工具

            + +
            ##
            +$ ./hadoop balancer
            +或者
            +$ ./bin/./start-balancer.sh
            +
          12. +
          +
          其他未列举的命令可以参见官方文档。
          +

          官方文档链接: hadoop1.2.1 Commands Guide, hadoop1.0.4 命令手册

          +
          +
          +
          +
          +

          编译hadoop 2.x Hadoop-eclipse-plugin插件

          + +

          + + + + + | 评论 +

          +
          + +

          经过hadoop1.x的发展,编译hadoop2.x版本的eclipse插件视乎比之前要轻松的多。如果你不在意编译过程中提示的警告,那么根据how to build - hadoop2x-eclipse-plugin文档就可一步到位。若想自己设置部分变量,可参考编译hadoop 1.2.1 Hadoop-eclipse-plugin插件。当然有问题及时和开发社区联系你会收到意想不到的收获.

          +

          issuce站点.

          +

          主要步骤

          +
            +
          • 介质准备
          • +
          • 执行
          • +
          • 安装验证
          • +
          +

          具体操作

          +
            +
          1. 设置语言环境

            + +
            $ export LC_ALL=en
            +
          2. +
          3. 检查ANT_HOME,JAVA_HOME

            +
          4. +
          5. 下载hadoop2x-eclipse-plugin
            +目前hadoop2的eclipse-plugins源代码由github脱管,下载地址how to build - hadoop2x-eclipse-plugin 右侧的 “Download ZIP” 或者 克隆到桌面. 当然你也可以fork到你自己的帐户下,在使用git clone。

            +
          6. +
          7. 执行

            + +
            $ cd src/contrib/eclipse-plugin
            +$ ant jar -Dversion=2.2.0 -Declipse.home=/opt/eclipse -Dhadoop.home=/usr/share/hadoop
            +

            将上述java system property eclipse.home 和 hadoop.home 设置成你自己的环境路径。 执行上述命令可能很快或者很慢。请耐心等待。主要慢的target:ivy-download,ivy-resolve-common。最后jar生成在 +$root/build/contrib/eclipse-plugin/hadoop-eclipse-plugin-2.2.0.jar路径下。

            +
          8. +
          9. 安装验证
            +将生成好的jar,复制到${eclipse.home}/plugins目录下。启动eclipse,新建Map/Reduce Project,配置hadoop location.验证插件完全分布式的插件配置截图和core-site.xml端口配置。

            +
          10. +
          11. 效果图
            +使用插件访问本地的伪分布式hadoop环境。查看文件texst1.txt和test2.txt同使用命令 hadoop dfs -ls /in 效果相同。 +image

            +
          12. +
          13. 已编译的插件 +hadoop-eclipse-plugin-2.2.0.jar

            +
          14. +
          15. 备注
            +目前我在使用这个版本的插件时发现还时挺不稳定的。发现了两个缺陷。我的环境为:Java HotSpot™ 64-Bit Server VM、 eclipse-standard-kepler-SR1-macosx-cocoa、 hadoop2.2.0。

            +
          16. +
          +

          缺陷:

          +
            +
          • Editor could not be initalized.
          • +
          • NullPointException
          • +
          +

          截图如下: +image

          +
          +
          +
          +
          +

          在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境

          + +

          + + + + + | 评论 +

          +
          + +

          一、初衷

          +

          对于学习 Hadoop 的我来讲,没有足够的硬件设备,但又想安装完全分布式的Hadoop,一个 master 两个 slave。手上就一台能联网的笔记本,那就使用 oracle vitual box 进行环境搭建吧。环境搭建的效果为:在虚拟机中虚拟3台 centos6.4 64 位系统,每台都配置双网卡 NAT,host-only 模式。在宿主机器上安装 eclipse 进行 Hadoop 开发。

          +

          Hadoop 环境搭建很大部分是在准备操作系统。具体如何搭建 Hadoop 其实就像解压缩普通的 tar 类似。然后再适当的配置 Hadoop 的 dfs,mapredurce 相关的配置,调整操作系统就可以开始着手学习 Hadoop了。

          +

          二、拓补图

          +

          图片制作中… …

          +
            +
          • master11:192.168.56.11
          • +
          • slave12:192.168.56.12
          • +
          • slave14:192.168.56.14
          • +
          +

          三、介质准备

          + +

          其他版本可到相关官方网站根据需要自行下载

          +

          四、虚拟机和基础环境搭建

          +

          这里主要操作有:安装一个新的oracle virtual box,并先安装一个centos6.4 的64位的操作系统。配置操作系统双网卡、修改机器名为master11、新建hadoop用户组和hadoop用户、配置sudo权限、安装配置java环境、同步系统时间、关闭防火墙。其中有些步骤需要重启操作系统后成效,建议一切都配置后再重启并再次验证是否生效,并开始克隆两个DataNode节点服务器slave12\slave14。

          +

          4.1 安装虚拟机

          +

          可参见官方文档安装oracle virtual box

          +

          4.2 在虚拟机里安装centos6.4

          +

          此处使用的是mini版的centos6.4 64位网易提供的镜像。在安装中内存调整大于等于1g,默认为视图安装界面,小于1g则为命令行终端安装方式。可更具实际情况调整虚拟机资源分配。此处为内存1g,存储20g.网络NAT模式。

          +

          具体可参见centos安装

          +

          4.3 配置双网卡

          +

          使用自己的笔记本经常遇到的问题就是在不同的网络下ip是不一样的。那么我们在学习hadoop的时候岂不是要经常修改这些ip呢。索性就直接弄个host-only模式的让oracle virtual box提供一个虚拟的网关。具体步骤:

          +
            +
          1. 先关闭计算机 sudo poweroff

            +
          2. +
          3. 打开virtualbox主界面,依次点击屏幕左上角virtualbox->偏好设置->网络->点击右侧添加图标添加一个Host-Only网络vboxnet0,再设置参数值

            +主机虚拟网络界面(A)
            +IPv4地址(I):192.168.56.1
            +IPv4网络掩码(M):255.255.255.0
            +IPv6地址(P):空
            +IPv6网络掩码长度(L):0
            +DHCP服务器(D)
            +选择启动服务器
            +服务器地址(r):192.168.56.100
            +服务器网络掩码(M):255.255.255.0
            +最小地址(L):192.168.56.254
            +最大地址(U):192.168.56.254
            +
            +截图:image

            +
          4. +
          5. 配置网卡1。选中你刚新建的虚拟机,右键设置->网络->网卡1->点击启动网络连接(E)

            +连接方式(A):仅主机(Host-Only)适配器
            +界面名称(N):vboxnet0(此处需要注意,如果没有进行步骤1那么这里可能无法选择,整个设置流程会提示有错误而无法继续)
            +高级
            +控制芯片(T):xxx
            +混杂模式(P):拒绝
            +MAC地址(M):系统随机即可
            +接入网线(C):选中
            +

            +
          6. +
          7. 配置网卡2。点击网卡2->点击启动网络连接(E)

            +连接方式(A):网络地址装换(NAT)
            +界面名称(N):
            +高级(d)
            +控制芯片(T):xxx
            +混杂模式(P):拒绝
            +MAC地址(M):系统随机即可
            +接入网线(C):选中
            +

            +
          8. +
          9. 配置网络。启动操作系统,使用root用户进行网络配置。

            + +
            cd /etc/sysconfig/network-scripts/
            +cp ifcfg-eth0 ifcfg-eth1
            +

            配置ifcfg-eth0,ifcfg-eth1两个文件与virtual box 中的网卡mac值一一对应, ONBOOT=yes开机启动。并将ifcfg-eth1中的网卡名称eth0改为eth1,再重启网路服务。

            + +
            service network start
            +

            其中eth0的网关信息比较多,需要根据情况具体配置。如,这里使用eth0为host-only模式,eth1为nat模式,eth0为固定ip,eth1为开机自动获取ip。可参考如下:

            +
            +[root@master11 network-scripts]# cat ifcfg-eth0
            +DEVICE=eth0
            +HWADDR=08:00:27:55:99:EA(必须和virtual box 中的mac地址一致)
            +TYPE=Ethernet
            +UUID=dc6511c2-b5bb-4ccc-9775-84679a726db3(没有可不填)
            +ONBOOT=yes
            +NM_CONTROLLED=yes
            +BOOTPROTO=static
            +NETMASK=255.255.255.0
            +BROADCAST=192.168.56.255
            +IPADDR=192.168.56.11
            +

            [root@master11 network-scripts]# cat ifcfg-eth1 +DEVICE=eth1 +HWADDR=08:00:27:13:36:C3 +TYPE=Ethernet +UUID=b8f8485e-b731-4b64-8363-418dbe34880d +ONBOOT=yes +NM_CONTROLLED=yes +BOOTPROTO=dhcp +

            +
          10. +
          +

          4.4 检查机器名称

          +

          检查机器名称。修改后,重启生效。

          + +
             cat /etc/sysconfig/network
          +

          这里期望得机器名称信息是:

          +
          + NETWORKING=yes
          + HOSTNAME=master11
          +
          +

          4.5 建立hadoop用户组和用户

          +

          新建hadoop用户组和用户(以下步骤如无特殊说明默认皆使用hadoop)

          + +
          groupadd hadoop
          +useradd hadoop -g hadoop
          +passwd hadoop
          +

          4.6 配置sudo权限

          +

          CentOS普通用户增加sudo权限的简单配置
          +查看sudo是否安装:

          + +
          rpm -qa|grep sudo
          +

          修改/etc/sudoers文件,修改命令必须为visudo才行

          + +
          visudo -f /etc/sudoers
          +

          在root ALL=(ALL) ALL 之后增加

          + +
          hadoop ALL=(ALL) ALL
          +Defaults:hadoop timestamp_timeout=-1,runaspw
          +
          +增加普通账户hadoop的sudo权限
          +timestamp_timeout=-1 只需验证一次密码,以后系统自动记忆
          +runaspw  需要root密码,如果不加默认是要输入普通账户的密码
          +

          修改普通用户的.bash_profile文件(vi /home/hadoop/.bash_profile),在PATH变量中增加 +/sbin:/usr/sbin:/usr/local/sbin:/usr/kerberos/sbin

          +

          4.7 安装java

          +

          使用hadoop用户sudo rpm -ivh jdk-7-linux-x64.rpm进行安装jdk7。 +配置环境变量参考CentOS-6.3安装配置JDK-7

          +

          4.8 同步服务

          +

          安装时间同步服务sudo yum install -y ntp +设置同步服务器 sudo ntpdate us.pool.ntp.org

          +

          4.9 关闭防火墙

          +

          hadoop使用的端口太多了,图省事,关掉。chkconfig iptables off。需要重启。

          +

          4.10 克隆

          +

          使用虚拟机进行克隆2个datanode节点。配置网卡(参见第五步)。配置主机名(参见第六步)。配置hosts,最好也包括宿主机(sudo vi /etc/hosts

          +
          +192.168.56.11 master11
          +192.168.56.12 slave12
          +192.168.56.14 slave14
          +
          +

          同步时间

          + +
          sudo ntpdate us.pool.ntp.org
          +

          重启服务service network start。可能出现错误参见device eth0 does not seem to be present, delaying initialization

          +

          4.11 配置ssh

          +
            +
          1. 单机ssh配置并回环测试 +
            ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa   
            +chmod 700 ~/.ssh
            +cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
            +chmod 600 ~/.ssh/authorized_keys
            +
            输入ssh localhost不用输入密码直接登陆,表明ssh配置成功。(若未重新权限分配,可能无法实现ssh免密码登陆的效果。浪费了我不少时间。)
          2. +
          3. 配置master和slave间的ssh +在slave12上执行 +
            scp hadoop@master11:~/.ssh/id_dsa.pub ./master_dsa.pub
            +cat master_dsa.pub >>authorized_keys
            +
            在master上执行ssh slave12若,不输入密码直接登陆,配置即通过。
          4. +
          5. 相同的步骤需要也在slave14和master11上重复一遍。
          6. +
          +

          机器环境确认无误后可以轻松的安装hadoop。

          +

          五、hadoop1.2.1 安装与配置

          +

          经过了以上步骤准备Hadoop1.2.1的环境搭建就相对容易多了。此处就仅需要解压缩安装并配置Hadoop,再验证是否正常便可大功告成。

          +

          5.1 安装

          +
            +
          1. 重启master11,准备工作环境目录 +
            cd ~/
            +mkdir env
            +
          2. +
          3. 解压Hadoop 1.2.1 tar +
            tar -zxvf hadoop-1.2.1.tar.gz -C ~/env/
            +
          4. +
          5. 建立软链接 +
            ln -s hadoop-1.2.1/ hadoop
            +
          6. +
          7. 配置环境变量(vi ~/.bashrc +
            export JAVA_HOME=/usr/java/jdk1.7.0_45
            +source ~/.bashrc
            +
          8. +
          9. 同步.bashrc
            scp ~/.bashrc hadoop@slave12:~/
            +scp ~/.bashrc hadoop@slave14:~/
            +
          10. +
          11. 创建数据文件存放路径。主要便于管理Hadoop的数据文件。 +
            cd /home/hadoop/env
            +mkdir data
            +mkdir data/tmp
            +mkdir data/name
            +mkdir data/data
            +chmod 755 data/data/
            +mkdir mapreduce
            +mkdir mapreduce/system
            +mkdir mapreduce/local
            +
            效果如下 +
            +├── env
            +│   ├── data
            +│   │   ├── data
            +│   │   ├── name
            +│   │   └── tmp
            +│   ├── hadoop -> hadoop-1.2.1/
            +│   ├── hadoop-1.2.1
            +│   │   ├── bin
            +│   │   ├── build.xml
            +│   └── mapreduce
            +│       ├── local
            +│       └── system
          12. +
          +

          +

          5.2 配置

          +
            +
          1. 配置 conf/core-site.xml +指定hdfs协议下的存储和临时目录

            + +
            <property>
            +  <name>fs.default.name</name>
            +  <value>hdfs://master11:9000</value>
            +</property>
            +<property>
            +  <name>hadoop.tmp.dir</name>
            +  <value>/home/${user.name}/env/data/tmp</value>
            +</property>
            +
          2. +
          3. 配置 conf/hdfs-site.xml +配置关于hdfs相关的配置。这里将原有默认复制3个副本调整为2个。学习时可根据需求适当调整。

            + +
            <property>
            + <name>dfs.name.dir</name>
            + <value>/home/${user.name}/env/data/name</value>
            +</property>
            +<property>
            + <name>dfs.data.dir</name>
            + <value>/home/${user.name}/env/data/data</value>
            +</property>
            +<property>
            + <name>dfs.replication</name>
            +  <value>2</value>
            +</property>
            +<property>
            + <name>dfs.web.ugi</name>
            + <value>hadoop,supergroup</value>
            + <final>true</final>
            + <description>The user account used by the web interface. Syntax: USERNAME,GROUP1,GROUP2, ……</description>
            +</property>
            +
          4. +
          5. 配置 conf/mapred-site.xml

            + +
            <property>
            + <name>mapred.job.tracker</name>
            + <value>master11:9001</value>
            +</property>
            +<property>
            + <name>mapred.system.dir</name>
            + <value>/home/${user.name}/env/mapreduce/system</value>
            +</property>
            +<property>
            + <name>mapred.local.dir</name>
            + <value>/home/${user.name}/env/mapreduce/local</value>
            +</property>
            +
          6. +
          7. 配置masters

            + +
            vi masters
            +

            写为

            + +
            master11
            +
          8. +
          9. 配置slaves

            + +
            vi slaves
            +

            写为

            + +
            slave12
            +slave14
            +
          10. +
          11. 同步hadoop到子节点

            + +
            scp -r ~/env/ hadoop@slave12:~/
            +scp -r ~/env/ hadoop@slave14:~/
            +
          12. +
          +

          5.3 启动hadoop

          +
            +
          1. 在主节点上格式化namenode

            + +
            ./bin/hadoop namenode -format
            +
          2. +
          3. 在主节点上启动Hadoop

            + +
            ./bin/start-all.sh
            +
          4. +
          +

          5.4 检查运行状态

          +
            +
          1. 通过web查看Hadoop状态

            +
            +http://192.168.56.11:50030/jobtracker.jsp
            +http://192.168.56.11:50070/dfshealth.jsp
            +
            +
          2. +
          3. 验证Hadoop mapredurce +执行hadoop jar hadoop-xx-examples.jar 验证jobtracker和tasktracker

            + +
            ./bin/hadoop jar hadoop-0.16.0-examples.jar wordcount input output
            +

            可wordcount参考Hadoop集群(第6期)_WordCount运行详解

            +
          4. +
          +

          六、常见错误

          +
            +
          • expected: rwxr-xr-x, while actual: rwxrwxr-x +WARN org.apache.hadoop.hdfs.server.datanode.DataNode: Invalid directory in dfs.data.dir: Incorrect permission for /home/hadoop/env/data/data, expected: rwxr-xr-x, while actual: rwxrwxr-x
            +解决方案:chmod 755 /home/hadoop/env/data/data

            +
          • +
          • 节点之间不能通信 +java.io.IOException: File xxx/jobtracker.info could only be replicated to 0 nodes, instead of 1
            +java.net.NoRouteToHostException: No route to host
            +解决方案:关闭iptables,sudo /etc/init.d/iptables stop

            +
          • +
          • got exception trying to get groups for user webuser

            +
            +org.apache.hadoop.util.Shell$ExitCodeException: id: webuser:无此用户
            +     at org.apache.hadoop.util.Shell.runCommand(Shell.java:255)
            +     at org.apache.hadoop.util.Shell.run(Shell.java:182)
            +
            +

            解决方案: +在hdfs-site.xml文件中添加

            + +
            <property>
            + <name>dfs.web.ugi</name>
            + <value>hadoop,supergroup</value>
            + <final>true</final>
            + <description>The user account used by the web interface.Syntax: USERNAME,GROUP1,GROUP2, ……
            + </description>
            +</property>
            +

            加上这个配置。可以解决了

            +
            +value  第一个为你自己搭建hadoop的用户名,第二个为用户所属组因为默认  
            +
            +

            web访问授权是webuser用户。访问的时候。我们一般用户名不是webuser所有要覆盖掉默认的webuser

            +
          • +
          +

          七、附录

          + +
          +
          +
          +
          +

          编译hadoop 1.2.1 Hadoop-eclipse-plugin插件

          + +

          + + + + + | 评论 +

          +
          + +

          编译hadoop1.x.x版本的eclipse插件为何如此繁琐?

          +

          个人理解,ant的初衷是打造一个本地化工具,而编译hadoop插件的资源间的依赖超出了这一目标。导致我们在使用ant编译的时候需要手工去修改配置。那么自然少不了设置环境变量、设置classpath、添加依赖、设置主函数、javac、jar清单文件编写、验证、部署等步骤。

          +

          那么我们开始动手

          +

          主要步骤如下

          +
            +
          • 设置环境变量
          • +
          • 设置ant初始参数
          • +
          • 调整java编译参数
          • +
          • 设置java classpath
          • +
          • 添加依赖
          • +
          • 修改META-INF文件
          • +
          • 编译打包、部署、验证
          • +
          +

          具体操作

          +
            +
          1. 设置语言环境

            + +
            $ export LC_ALL=en
            +
          2. +
          3. 设置ant初始参数
            +修改build-contrib.xml文件

            + +
            $ cd /hadoop-1.2.1/src/contrib
            +$ vi build-contrib.xml
            +

            编辑并修改hadoop.root值为实际hadoop解压的根目录

            + +
            <property name="hadoop.root" location="/Users/kangfoo-mac/study/hadoop-1.2.1"/>
            +

            添加eclipse依赖

            + +
            <property name="eclipse.home" location="/Users/kangfoo-mac/work/soft/eclipse-standard-kepler-SR1-macosx-cocoa" />
            +

            设置版本号

            + +
            <property name="version" value="1.2.1"/>
            +
          4. +
          5. 调整java编译设置
            +启用javac.deprecation

            + +
            $ cd /hadoop-1.2.1/src/contrib
            +$ vi build-contrib.xml
            +


            + +
            <property name="javac.deprecation" value="off"/>
            +

            改为

            + +
            <property name="javac.deprecation" value="on"/>
            +
          6. +
          7. ant 1.8+ 版本需要额外的设置javac includeantruntime=“on” 参数

            + +
            <!-- ====================================================== -->
            +<!-- Compile a Hadoop contrib's files                       -->
            +<!-- ====================================================== -->
            +<target name="compile" depends="init, ivy-retrieve-common" unless="skip.contrib">
            +<echo message="contrib: ${name}"/>
            +<javac
            + encoding="${build.encoding}"
            + srcdir="${src.dir}"
            + includes="**/*.java"
            + destdir="${build.classes}"
            + debug="${javac.debug}"
            + deprecation="${javac.deprecation}"
            + includeantruntime="on">
            + <classpath refid="contrib-classpath"/>
            +</javac>
            +</target> 
            +
          8. +
          9. 修改编译hadoop插件 classpath

            + +
            $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
            +$ vi build.xml
            +

            添加 文件路径 hadoop-jars

            + +
            <path id="hadoop-jars">
            +  <fileset dir="${hadoop.root}/">
            +    <include name="hadoop-*.jar"/>
            +  </fileset>
            +</path>
            +

            将hadoop-jars 添加到classpath

            + +
            <path id="classpath">
            +  <pathelement location="${build.classes}"/>
            +  <pathelement location="${hadoop.root}/build/classes"/>
            +  <path refid="eclipse-sdk-jars"/>
            +  <path refid="hadoop-jars"/>
            +</path> 
            +
          10. +
          11. 修改或添加额外的jar依赖
            +因为我们根本都没有直接编译过hadoop,所以就直接使用${HADOOP_HOME}/lib下的资源.需要注意,这里将依赖jar的版本后缀去掉了。
            +同样还是在hadoop-1.2.1/src/contrib/eclipse-plugin/build.xml文件中修改或添加

            + +
            $ cd hadoop-1.2.1/src/contrib/eclipse-plugin
            +$ vi build.xml
            +

            找到 <!-- Override jar target to specify manifest --> 修改target name为 jar 中的 copy file 的路径,具体如下:

            + +
            <copy file="${hadoop.root}/hadoop-core-${version}.jar" tofile="${build.dir}/lib/hadoop-core.jar" verbose="true"/>
            +<copy file="${hadoop.root}/lib/commons-cli-${commons-cli.version}.jar"  tofile="${build.dir}/lib/commons-cli.jar" verbose="true"/>
            +<copy file="${hadoop.root}/lib/commons-configuration-1.6.jar"  tofile="${build.dir}/lib/commons-configuration.jar" verbose="true"/>
            +<copy file="${hadoop.root}/lib/commons-httpclient-3.0.1.jar"  tofile="${build.dir}/lib/commons-httpclient.jar" verbose="true"/>
            +<copy file="${hadoop.root}/lib/commons-lang-2.4.jar"  tofile="${build.dir}/lib/commons-lang.jar" verbose="true"/>
            +<copy file="${hadoop.root}/lib/jackson-core-asl-1.8.8.jar"  tofile="${build.dir}/lib/jackson-core-asl.jar" verbose="true"/>
            +<copy file="${hadoop.root}/lib/jackson-mapper-asl-1.8.8.jar"  tofile="${build.dir}/lib/jackson-mapper-asl.jar" verbose="true"/>
            +
          12. +
          13. 修改 jar 清单文件

            + +
            cd ./hadoop-1.2.1/src/contrib/eclipse-plugin/META-INF
            +vi MANIFEST.MF
            +

            找到这个文件的Bundle-ClassPath这一行,然后,修改成

            + +
            Bundle-ClassPath: classes/,lib/commons-cli.jar,lib/commons-httpclient.jar,lib/hadoop-core.jar,lib/jackson-mapper-asl.jar,lib/commons-configuration.jar,lib/commons-lang.jar,lib/jackson-core-asl.jar
            +

            请保证上述字符占用一行,或者满足osgi bundle 配置文件的换行标准语法也行的。省事就直接写成一行,搞定。

            +
          14. +
          15. 新建直接打包并部署jar到eclipse/plugin目录的target

            + +
            cd hadoop-1.2.1/src/contrib/eclipse-plugin
            +vi build.xml
            +

            添加target直接将编译的插件拷贝到eclipse插件目录

            + +
            <target name="deploy" depends="jar" unless="skip.contrib"> 
            +<copy file="${build.dir}/hadoop-${name}-${version}.jar" todir="${eclipse.home}/plugins" verbose="true"/> </target>
            +

            将ant默认target default=“java"改为default=“deploy”

            + +
            <project default="deploy" name="eclipse-plugin">
            +
          16. +
          17. 编译并启动eclipse验证插件

            + +
            ant -f ./hadoop-1.2.1/src/contrib/eclipse-plugin/build.xml
            +

            启动eclipse,新建Map/Reduce Project,配置hadoop location.验证插件完全分布式的插件配置截图和core-site.xml端口配置

            +
          18. +
          19. 效果图 +image

            +
          20. +
          +

          相关源文件

          + +

          在此非常感谢kinuxroot这位博主的的博文参考。

          +
          +
          + + +
          + +
          +
          +

          + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

          +
          + + + + + + + + + diff --git a/page/6/index.html b/page/6/index.html new file mode 100644 index 0000000..d639461 --- /dev/null +++ b/page/6/index.html @@ -0,0 +1,195 @@ + + + + + + + kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + +
          +

          kangfoo's blog

          +

          工作学习笔记,生活掠影。

          +
          +
          + +
          +
          +
          +
          +
          +

          Mac Java乱码 Maven OOM 异常

          + +

          + + + + + | 评论 +

          +
          + +

          在中文环境下苹果电脑中的 java,javac 默认以GBK编码输出信息到控制台,终端terminal默认以UTF-8编码,就出现了编码错误。

          +

          可如下2种方式修改:

          +
            +
          1. 更改系统语言环境
            export LC_ALL=en 或者 export LANG=zh_CN.UTF-8
            +
          2. +
          3. 指定输出编码方式
            javac -Dfile.encoding=UTF-8
            +
          4. +
          +

          但在有maven的情况下还需要这么设置:先调整JVM大小,避免工程太大中途出现OOM,再设置文件的编码。

          +
          export MAVEN_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
          +
          +
          + + +
          + +
          +
          +

          + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

          +
          + + + + diff --git a/sample-page.html b/sample-page.html index 496f934..ec3f8fd 100644 --- a/sample-page.html +++ b/sample-page.html @@ -4,7 +4,7 @@ - Sample Page - kangfoo's 博客 + Sample Page - kangfoo's blog @@ -12,12 +12,12 @@ - + - + @@ -36,7 +36,7 @@
          -

          kangfoo's 博客

          +

          kangfoo's blog

          工作学习笔记,生活掠影。

          diff --git a/sitemap.xml b/sitemap.xml index 969acb3..e63460a 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,16 +2,96 @@ http://kangfoo.u.qiniudn.com// - 2014-01-19T17:00:00+08:00 + 2014-03-05T01:01:00+08:00 daily 1.0 http://kangfoo.u.qiniudn.com//archives/ - 2014-01-19T17:00:00+08:00 + 2014-03-05T01:01:00+08:00 daily 0.8 + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop--xue-xi-can-kao-bo-ke-hui-zong/ + 2014-03-05T01:01:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-pipes--streaming/ + 2014-03-03T22:26:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce-sort/ + 2014-03-03T22:24:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce-join/ + 2014-03-03T22:23:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce--ji-shu-qi/ + 2014-03-03T22:22:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce-recordreader-zu-jian/ + 2014-03-03T22:21:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce-partitioner--zu-jian/ + 2014-03-03T22:20:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce-combiner--zu-jian/ + 2014-03-03T22:19:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce--lei-xing-yu-ge-shi/ + 2014-03-03T22:18:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-mapreduce--gong-zuo-ji-zhi/ + 2014-03-03T22:17:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/03/hadoop-rpc/ + 2014-03-02T23:33:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/02/hadoop-io-1/ + 2014-02-26T23:50:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/02/hadoop-c-pipes--bian-yi/ + 2014-02-26T01:01:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/02/nativehadoop--bian-yi/ + 2014-02-26T00:57:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/02/java-jni--lian-xi/ + 2014-02-26T00:55:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/02/hdfs-api--lian-xi-shi-yong/ + 2014-02-26T00:53:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/02/hadoop-ji-jia-gan-zhi/ + 2014-02-26T00:52:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/02/shi-yong-secondenamenode-hui-fu-namenode/ + 2014-02-26T00:50:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/02/hadoop-hdfs-1/ + 2014-02-25T22:46:00+08:00 + + + http://kangfoo.u.qiniudn.com//article/2014/02/hadoop1.x--xue-xi-zhun-bei/ + 2014-02-24T23:28:00+08:00 + http://kangfoo.u.qiniudn.com//article/2014/01/hadoop1.x-wordcount-fen-xi/ 2014-01-19T17:00:00+08:00 diff --git a/tag/ant/index.html b/tag/ant/index.html index b92da15..3d761ec 100644 --- a/tag/ant/index.html +++ b/tag/ant/index.html @@ -4,7 +4,7 @@ - 标签:ant - kangfoo's 博客 + 标签:ant - kangfoo's blog @@ -12,12 +12,12 @@ - + - + @@ -35,7 +35,7 @@
          -

          kangfoo's 博客

          +

          kangfoo's blog

          工作学习笔记,生活掠影。

          @@ -96,7 +96,7 @@

          被贴了 java, ant, hadoop +被贴了 ant, hadoop2 标签 @@ -109,7 +109,7 @@

          分类 -被贴了 java, ant, hadoop +被贴了 java, ant, hadoop1 标签 @@ -123,19 +123,34 @@

          近期文章

        34. - Hadoop1.x Wordcount分析 + Hadoop MapReduce 学习参考博客汇总
        35. - Hadoop1.x 命令手册列举 + Hadoop Pipes & Streaming
        36. - 编译hadoop 2.x Hadoop-eclipse-plugin插件 + Hadoop MapReduce Sort
        37. - 在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境 + Hadoop MapReduce Join
        38. - 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件 + Hadoop MapReduce 计数器 +
        39. +
        40. + Hadoop MapReduce RecordReader 组件 +
        41. +
        42. + Hadoop MapReduce Partitioner 组件 +
        43. +
        44. + Hadoop MapReduce Combiner 组件 +
        45. +
        46. + Hadoop MapReduce 类型与格式 +
        47. +
        48. + Hadoop MapReduce 工作机制
        49. diff --git a/tag/hadoop/index.html b/tag/hadoop/index.html index c6b21c1..2fee0ed 100644 --- a/tag/hadoop/index.html +++ b/tag/hadoop/index.html @@ -4,7 +4,7 @@ - 标签:hadoop - kangfoo's 博客 + 标签:hadoop - kangfoo's blog @@ -12,12 +12,12 @@ - + - + @@ -35,7 +35,7 @@
          -

          kangfoo's 博客

          +

          kangfoo's blog

          工作学习笔记,生活掠影。

          @@ -88,8 +88,8 @@

          标签:hadoop

          2014

          - -

          2013

          - -
          @@ -150,19 +110,34 @@

          近期文章

        50. - Hadoop1.x Wordcount分析 + Hadoop MapReduce 学习参考博客汇总 +
        51. +
        52. + Hadoop Pipes & Streaming +
        53. +
        54. + Hadoop MapReduce Sort +
        55. +
        56. + Hadoop MapReduce Join +
        57. +
        58. + Hadoop MapReduce 计数器 +
        59. +
        60. + Hadoop MapReduce RecordReader 组件
        61. - Hadoop1.x 命令手册列举 + Hadoop MapReduce Partitioner 组件
        62. - 编译hadoop 2.x Hadoop-eclipse-plugin插件 + Hadoop MapReduce Combiner 组件
        63. - 在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境 + Hadoop MapReduce 类型与格式
        64. - 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件 + Hadoop MapReduce 工作机制
        65. diff --git a/tag/hadoop1/index.html b/tag/hadoop1/index.html new file mode 100644 index 0000000..91e7de5 --- /dev/null +++ b/tag/hadoop1/index.html @@ -0,0 +1,448 @@ + + + + + + + 标签:hadoop1 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + +
          +

          kangfoo's blog

          +

          工作学习笔记,生活掠影。

          +
          +
          + +
          +
          +
          +
          +
          +

          标签:hadoop1

          + +
          +
          +

          2014

          + + + + + + + + + + + + + + + + + + + + +

          2013

          + + +
          +
          +
          + +
          +
          +

          + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

          +
          + + + + diff --git a/tag/hadoop2/index.html b/tag/hadoop2/index.html new file mode 100644 index 0000000..824b644 --- /dev/null +++ b/tag/hadoop2/index.html @@ -0,0 +1,174 @@ + + + + + + + 标签:hadoop2 - kangfoo's blog + + + + + + + + + + + + + + + + + + + + + + + + +
          +

          kangfoo's blog

          +

          工作学习笔记,生活掠影。

          +
          +
          + +
          +
          +
          + +
          + +
          +
          +

          + 版权所有 © 2014 - kangfoo - + Powered by OpooPress + + +

          +
          + + + + diff --git a/tag/java/index.html b/tag/java/index.html index aa7e6f1..dbe5614 100644 --- a/tag/java/index.html +++ b/tag/java/index.html @@ -4,7 +4,7 @@ - 标签:java - kangfoo's 博客 + 标签:java - kangfoo's blog @@ -12,12 +12,12 @@ - + - + @@ -35,7 +35,7 @@
          -

          kangfoo's 博客

          +

          kangfoo's blog

          工作学习笔记,生活掠影。

          @@ -88,19 +88,6 @@

          标签:java

          2013

          - @@ -136,19 +123,34 @@

          近期文章

        66. - Hadoop1.x Wordcount分析 + Hadoop MapReduce 学习参考博客汇总 +
        67. +
        68. + Hadoop Pipes & Streaming +
        69. +
        70. + Hadoop MapReduce Sort +
        71. +
        72. + Hadoop MapReduce Join +
        73. +
        74. + Hadoop MapReduce 计数器 +
        75. +
        76. + Hadoop MapReduce RecordReader 组件
        77. - Hadoop1.x 命令手册列举 + Hadoop MapReduce Partitioner 组件
        78. - 编译hadoop 2.x Hadoop-eclipse-plugin插件 + Hadoop MapReduce Combiner 组件
        79. - 在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境 + Hadoop MapReduce 类型与格式
        80. - 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件 + Hadoop MapReduce 工作机制
        81. diff --git a/tag/mac/index.html b/tag/mac/index.html index cbc1f75..60a9f63 100644 --- a/tag/mac/index.html +++ b/tag/mac/index.html @@ -4,7 +4,7 @@ - 标签:mac - kangfoo's 博客 + 标签:mac - kangfoo's blog @@ -12,12 +12,12 @@ - + - + @@ -35,7 +35,7 @@
          -

          kangfoo's 博客

          +

          kangfoo's blog

          工作学习笔记,生活掠影。

          @@ -110,19 +110,34 @@

          近期文章

        82. - Hadoop1.x Wordcount分析 + Hadoop MapReduce 学习参考博客汇总
        83. - Hadoop1.x 命令手册列举 + Hadoop Pipes & Streaming
        84. - 编译hadoop 2.x Hadoop-eclipse-plugin插件 + Hadoop MapReduce Sort
        85. - 在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境 + Hadoop MapReduce Join
        86. - 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件 + Hadoop MapReduce 计数器 +
        87. +
        88. + Hadoop MapReduce RecordReader 组件 +
        89. +
        90. + Hadoop MapReduce Partitioner 组件 +
        91. +
        92. + Hadoop MapReduce Combiner 组件 +
        93. +
        94. + Hadoop MapReduce 类型与格式 +
        95. +
        96. + Hadoop MapReduce 工作机制
        97. diff --git a/tag/maven/index.html b/tag/maven/index.html index ac5c03d..0c1e67e 100644 --- a/tag/maven/index.html +++ b/tag/maven/index.html @@ -4,7 +4,7 @@ - 标签:maven - kangfoo's 博客 + 标签:maven - kangfoo's blog @@ -12,12 +12,12 @@ - + - + @@ -35,7 +35,7 @@
          -

          kangfoo's 博客

          +

          kangfoo's blog

          工作学习笔记,生活掠影。

          @@ -110,19 +110,34 @@

          近期文章

        98. - Hadoop1.x Wordcount分析 + Hadoop MapReduce 学习参考博客汇总
        99. - Hadoop1.x 命令手册列举 + Hadoop Pipes & Streaming
        100. - 编译hadoop 2.x Hadoop-eclipse-plugin插件 + Hadoop MapReduce Sort
        101. - 在oracle Virtual Box 虚拟机中搭建hadoop1.2.1完全分布式环境 + Hadoop MapReduce Join
        102. - 编译hadoop 1.2.1 Hadoop-eclipse-plugin插件 + Hadoop MapReduce 计数器 +
        103. +
        104. + Hadoop MapReduce RecordReader 组件 +
        105. +
        106. + Hadoop MapReduce Partitioner 组件 +
        107. +
        108. + Hadoop MapReduce Combiner 组件 +
        109. +
        110. + Hadoop MapReduce 类型与格式 +
        111. +
        112. + Hadoop MapReduce 工作机制