前言

本文是Hadoop最佳实践系列第二篇,上一篇为《Hadoop管理员的十个最佳实践》。

MapRuduce开发对于大多数程序员都会觉得略显复杂,运行一个WordCount(Hadoop中hello word程序)不仅要熟悉MapRuduce模型,还要了解Linux命令(尽管有Cygwin,但在Windows下运行MapRuduce仍然很麻烦),此外还要学习程序的打包、部署、提交job、调试等技能,这足以让很多学习者望而退步。

所以如何提高MapReduce开发效率便成了大家很关注的问题。但Hadoop的Committer早已经考虑到这些问题,从而开发了ToolRunner、MRunit(MapReduce最佳实践第二篇中会介绍)、MiniMRCluster、MiniDFSCluster等辅助工具,帮助解决开发、部署等问题。举一个自己亲身的例子:

 

某周一和搭档(结对编程)决定重构一个完成近10项统计工作的MapRuduce程序,这个MapReduce(从Spring项目移植过来的),因为依赖Spring框架(原生Spring,非Spring Hadoop框架),导致性能难以忍受,我们决定将Spring从程序中剔除。重构之前程序运行是正确的,所以我们要保障重构后运行结果与重构前一致。搭档说,为什么我们不用TDD来完成这个事情呢?于是我们研究并应用了MRunit,令人意想不到的是,重构工作只用了一天就完成,剩下一天我们进行用findbug扫描了代码,进行了集成测试。这次重构工作我们没有给程序带来任何错误,不但如此我们还拥有了可靠的测试和更加稳固的代码。这件事情让我们很爽的同时,也在思考关于MapReduce开发效率的问题,要知道这次重构我们之前评估的时间是一周,我把这个事情分享到EasyHadoop群里,大家很有兴趣,一个朋友问到,你们的评估太不准确了,为什么开始不评估2天完成呢?我说如果我们没有使用MRUnit,真的是需要一周才能完成。因为有它单元测试,我可以在5秒内得到我本次修改的反馈,否则至少需要10分钟(编译、打包、部署、提交MapReduce、人工验证结果正确性),而且重构是个反复修改,反复运行,得到反馈,再修改、再运行、再反馈的过程,MRunit在这里帮了大忙。

相同智商、相同工作经验的开发人员,借助有效的工具和方法,竟然可以带来如此大的开发效率差距,不得不让人惊诧!

PS. 本文基于Hadoop 1.0(Cloudera CDH3uX)。本文适合读者:Hadoop初级、中级开发者。

 

1. 使用ToolRunner让参数传递更简单

关于MapReduce运行和参数配置,你是否有下面的烦恼:

  1. 将MapReduce Job配置参数写到java代码里,一旦变更意味着修改java文件源码、编译、打包、部署一连串事情。
  2. 当MapReduce 依赖配置文件的时候,你需要手工编写java代码使用DistributedCache将其上传到HDFS中,以便map和reduce函数可以读取。
  3. 当你的map或reduce 函数依赖第三方jar文件时,你在命令行中使用”-libjars”参数指定依赖jar包时,但根本没生效。

其实,Hadoop有个ToolRunner类,它是个好东西,简单好用。无论在《Hadoop权威指南》还是Hadoop项目源码自带的example,都推荐使用ToolRunner。

下面我们看下src/example目录下WordCount.java文件,它的代码结构是这样的:

public class WordCount {
// 略...
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf,
args).getRemainingArgs();
// 略...
Job job = new Job(conf, "word count");
// 略...
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}

WordCount.java中使用到了GenericOptionsParser这个类,它的作用是将命令行中参数自动设置到变量conf中。举个例子,比如我希望通过命令行设置reduce task数量,就这么写:

bin/hadoop jar MyJob.jar com.xxx.MyJobDriver -Dmapred.reduce.tasks=5

上面这样就可以了,不需要将其硬编码到java代码中,很轻松就可以将参数与代码分离开。

其它常用的参数还有”-libjars”和-“files”,使用方法一起送上:

bin/hadoop jar MyJob.jar com.xxx.MyJobDriver -Dmapred.reduce.tasks=5 \
-files ./dict.conf \
-libjars lib/commons-beanutils-1.8.3.jar,lib/commons-digester-2.1.jar

参数”-libjars”的作用是上传本地jar包到HDFS中MapReduce临时目录并将其设置到map和reduce task的classpath中;参数”-files”的作用是上传指定文件到HDFS中mapreduce临时目录,并允许map和reduce task读取到它。这两个配置参数其实都是通过DistributeCache来实现的。

至此,我们还没有说到ToolRunner,上面的代码我们使用了GenericOptionsParser帮我们解析命令行参数,编写ToolRunner的程序员更懒,它将 GenericOptionsParser调用隐藏到自身run方法,被自动执行了,修改后的代码变成了这样:

public class WordCount extends Configured implements Tool {

    @Override
public int run(String[] arg0) throws Exception {
Job job = new Job(getConf(), "word count");
// 略...
System.exit(job.waitForCompletion(true) ? 0 : 1);
return 0;
} public static void main(String[] args) throws Exception {
int res = ToolRunner.run(new Configuration(), new WordCount(), args);
System.exit(res);
}
}

看看代码上有什么不同:

  1. 让WordCount继承Configured并实现Tool接口。
  2. 重写Tool接口的run方法,run方法不是static类型,这很好。
  3. 在WordCount中我们将通过getConf()获取Configuration对象。

关于GenericOptionsParser更多用法,请点击这里:GenericOptionsParser.html

推荐指数:★★★★

推荐理由:通过简单的几步,就可以实现代码与配置隔离、上传文件到DistributeCache等功能。修改MapReduce参数不需要修改java代码、打包、部署,提高工作效率。

2. 有效使用Hadoop源码

作为MapReduce程序员不可避免的要使用Hadoop源码,Why?记得2010刚接触hadoop的时候,总是搞不清旧api和新api的使用方法。写了一段程序,在一个新api里面调用某个方法每次都是返回Null,非常恼火,后来附上源码发现,这个方法真的就是只做了“return null”并没有给予实现,最后只得想其它方法曲线救国。总之要想真正了解MapReduce开发,源码是不可缺少的工具。

下面是我的源码使用实践,步骤有点麻烦不过配置一次就好:

1. Eclipse中创建Hadoop源码项目

1.1 下载并解压缩Hadoop分发包(通常是tar.gz包)

1.2 Eclipse中新建Java项目

1.3 将解压后hadoop源码包/src目录中core, hdfs, mapred, tool几个目录(其它几个源码根据需要进行选择)copy到eclipse新建项目的src目录。

1.4 右键点击eclipse项目,选择“Properties”,在弹出对话框中左边菜单选择“Java Build Path”:
    a) 点击“Source”标签。先删除src这个目录,然后依次添加刚才copy过来的目录
    b) 点击当前对话框“Libaries”,点击“Add External JARs”,在弹出窗口中添加$HADOOPHOME下几个hadoop程序jar包,然后再次添加$HADOOPHOME /lib、$HADOOP_HOME /lib/jsp-2.1两个目录下所有jar包,最后还要添加ANT项目lib目录下ant.jar文件。

1.5 此时源码项目应该只有关于找不到sun.security包的错误了。这时我们还是在“Libraries”这个标签中,展开jar包列表最低下的“JRE System Library”,双击”Access rules”,在弹出窗口中点击“add按钮”,然后在新对话框中"Resolution"下拉框选择"Accessible","Rule Pattern"填写*/,保存后就OK了。如下图:

2. 如何使用这个源码项目呢?

比如我知道Hadoop某个源码文件的名称,在eclipse中可以通过快捷键“Ctrl + Shift + R”调出查找窗口,输入文件名,如“MapTask”,那可以打开这个类的源码了。

还有个使用场景,当我们编写MapReduce程序的时候,我想直接打开某个类的源码,通过上面的操作还是有点麻烦,比如我想看看Job类是如何实现的,当我点击它的时候会出现下面的情景:

解决办法很简单:

点击图中“Attach Source”按钮-> 点击“Workspace”按钮->选择刚才新建的Hadoop源码项目。完成后源码应该就蹦出来了。

总结一下,本实践中我们获得了什么功能:

  1. 知道hadoop源码文件名,快速找到该文件
  2. 写程序的时候直接查看Hadoop相关类源码
  3. Debug程序的时候,可以直接进入源码查看并跟踪运行

推荐指数:★★★★

推荐理由:通过源码可以帮助我们更深入了解Hadoop,可以帮助我们解决复杂问题

3. 正确使用压缩算法

下表资料引用cloudera官方网站的一篇博客,原文点这里

Compression File Size(GB) Compression Time (s) Decompression Time (s)
None some_logs 8.0 - -
Gzip some_logs.gz 1.3 241 72
LZO some_logs.lzo 2.0 55 35

上面表格与笔者集群实际环境测试结果一致,所以我们可以得出如下结论:

  1. LZO文件的压缩和解压缩性能要远远好于Gzip文件。
  2. 相同文本文件,使用Gzip压缩可以比LZO压缩大幅减少磁盘空间。

上面的结论对我们有什么帮助呢?在合适的环节使用合适压缩算法。

在中国的带宽成本是非常贵的,费用上要远远高于美国、韩国等国家。所以在数据传输环节,我们希望使用了Gzip算法压缩文件,目的是减少文件传输量,降低带宽成本。使用LZO文件作为MapReduce文件的输入(创建lzo index后是支持自动分片输入的)。对于大文件,一个map task的输入将变为一个block,而不是像Gzip文件一样读取整个文件,这将大幅提升MapReduce运行效率。

主流传输工具FlumeNG和scribe默认都是非压缩传输的(都是通过一行日志一个event进行控制的),这点大家在使用时要注意。FlumeNG可以自定义组件方式实现一次传输多条压缩数据,然后接收端解压缩的方式来实现数据压缩传输,scribe没有使用过不评论。

另外值得一提的就是snappy,它是由Google开发并开源的压缩算法的,是Cloudera官方大力提倡在MapReduce中使用的压缩算法。它的特点是:与LZO文件相近的压缩率的情况下,还可以大幅提升压缩和解压缩性能,但是它作为MapReduce输入是不可以分割的。

延伸内容:

Cloudera官方Blog对Snappy介绍:

http://blog.cloudera.com/blog/2011/09/snappy-and-hadoop/

老外上传的压缩算法性能测试数据:

http://pastebin.com/SFaNzRuf

推荐指数:★★★★★

推荐理由:压缩率和压缩性能一定程度是矛盾体,如何均衡取决于应用场景。使用合适压缩算法直接关系到老板的钱,如果能够节省成本,体现程序员的价值。

4. 在合适的时候使用Combiner

map和 reduce 函数的输入输出都是key-value,Combiner和它们是一样的。作为map和reduce的中间环节,它的作用是聚合map task的磁盘,减少map端磁盘写入,减少reduce端处理的数据量,对于有大量shuffle的job来说,性能往往取决于reduce端。因为reduce 端要经过从map端copy数据、reduce端归并排序,最后才是执行reduce方法,此时如果可以减少map task输出将对整个job带来非常大的影响。

什么时候可以使用Combiner?

比如你的Job是WordCount,那么完全可以通过Combiner对map 函数输出数据先进行聚合,然后再将Combiner输出的结果发送到reduce端。

什么时候不能使用Combiner?

WordCount在reduce端做的是加法,如果我们reduce需求是计算一大堆数字的平均数,则要求reduce获取到全部的数字进行计算,才可以得到正确值。此时,是不能使用Combiner的,因为会其会影响最终结果。 注意事项:即使设置Combiner,它也不一定被执行(受参数min.num.spills.for.combine影响),所以使用Combiner的场景应保证即使没有Combiner,我们的MapReduce也能正常运行。

推荐指数:★★★★★

推荐理由:在合适的场景使用Combiner,可以大幅提升MapReduce 性能。

5. 通过回调通知知道MapReduce什么时候完成

你知道什么时候MapReduce完成吗?知道它执行成功或是失败吗?

Hadoop包含job通知这个功能,要使用它非常容易,借助我们实践一的ToolRunner,在命令行里面就可以进行设置,下面是一个例子:

hadoop jar MyJob.jar com.xxx.MyJobDriver \
-Djob.end.notification.url=http://moniter/mapred_notify/\$jobId/\$jobStatus

通过上面的参数设置后,当MapReduce完成后将会回调我参数中的接口。其中$jobId和$jobStatus会自动被实际值代替。

上面在$jobId和$jobStatus两个变量前,我添加了shell中的转义符”\”,如果使用java代码设置该参数是不需要转义符的。

总结下:看看我们通过该实践可以获得什么?

  1. 获取MapReduce运行时间和回调完成时间,可以分析最耗时Job,最快完成Job。
  2. 通过MapReduce运行状态(包括成功、失败、Kill),可以第一时间发现错误,并通知运维。
  3. 通过获取MapReduce完成时间,可以第一时间通过用户,数据已经计算完成,提升用户体验

Hadoop这块功能的源码文件是JobEndNotifier.java,可以马上通过本文实践二看看究竟。其中下面两个参数就是我通过翻源码的时候发现的,如果希望使用该实践赶紧通过ToolRunner设置上吧(别忘了加-D,格式是-Dkey=value)。

  1. job.end.retry.attempts // 设置回调通知retry次数
  2. job.end.retry.interval // 设置回调时间间隔,单位毫秒

当然如果hadoop没有提供Job状态通知的功能,我们也可以通过采用阻塞模式提交MapReduce Job,然后Job完成后也可以获知其状态和运行时间。

推荐指数:★★★

推荐理由:对mapreduce job监控最省事有效的办法,没有之一。

[转] Hadoop MapReduce开发最佳实践(上篇)的更多相关文章

  1. Hadoop MapReduce开发最佳实践(上篇)

    body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...

  2. web前端开发最佳实践笔记

    一.文章开篇 由于最近也比较忙,一方面是忙着公司的事情,另外一方面也是忙着看书和学习,所以没有时间来和大家一起分享知识,现在好了,终于回归博客园的大家庭了,今天我打算来分享一下关于<web前端开 ...

  3. [转]Android开发最佳实践

    ——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢—— 原文链接:https://github.com/futurice/and ...

  4. 【读书笔记】iOS-微信公众平台开发最佳实践

    一,微信是由腾讯公司广州研发中心产品团队开发,该团队经理张小龙被称为“微信之父”,公司总裁马化腾确定该产品名称为“微信”. 二,常见问题及解决方案. 1,请求URL超时. 这种情况一般是由于服务器网速 ...

  5. Android开发最佳实践《IT蓝豹》

    Android开发最佳实践   移动开发Android经验分享应用GoogleMaterial Design 摘要:前 段时间,Google公布了Android开发最佳实践的一系列课程,涉及到一些平时 ...

  6. Android和PHP开发最佳实践

    Android和PHP开发最佳实践 <Android和PHP开发最佳实践>基本信息作者: 黄隽实丛书名: 移动应用开发技术丛书出版社:机械工业出版社ISBN:9787111410508上架 ...

  7. iOS应用开发最佳实践

    <iOS应用开发最佳实践> 基本信息 作者: 王浩    出版社:电子工业出版社 ISBN:9787121207679 上架时间:2013-7-22 出版日期:2013 年8月 开本:16 ...

  8. 【社区公益】送《Web前端开发最佳实践》给需要的人

    算起来至今,我进入软件开发行业已经有11年之久.从最初的研究人工智能,到后来的Web开发,控件开发,直到现在纯粹的Web前端开发.虽然没有大的作品问世,但也是勤勤恳恳,踏实做事,低调做人.从来不吹牛逼 ...

  9. Web前端开发最佳实践系列文章汇总

    Web前端开发最佳实践(1):前端开发概述 Web前端开发最佳实践(2):前端代码重构 Web前端开发最佳实践(3):前端代码和资源的压缩与合并 Web前端开发最佳实践(4):在页面中添加必要的met ...

随机推荐

  1. DBCC--常用跟踪标记

    使用DBCC TRACEON 和DBCC TRACEOFF来打开和关闭跟踪标记 使用DBCC TRACESTATUS来查看所有打开的跟踪标记 --260:打印关于扩展存储过程动态链接库的版本信息 -- ...

  2. STM32的时钟配置随笔

    以前使用STM32都是使用库函数开发,最近心血来潮想要使用寄存器来试试手感,于是乎便在工作之余研究了一下STM32F4的时钟配置,在此将经历过程写下来作为锻炼,同时也供和我一样的新手参考,如有错误或者 ...

  3. Android学习之ViewPager

     1.定义 ViewPager是android扩展包v4包中的类,这个类可以让用户左右切换当前的view.其中,android.support.v4是谷歌公司为了解决当前版本碎片化的问题,从而提供的一 ...

  4. 对于equals和==的理解

    很多时候equals和==大家都分不太清楚怎么样来使用,今天小编就来教大家怎么使用 equals比较的是两个变量的值是否相等 而==则比较的是这个变量的内存地址是否相同 打个比方来说 String a ...

  5. Open GL的学习路程

    一.EGL的使用 1.Dispaly与原生窗口的链接 -EGLDisplay  eglGetDisplay -EGLBoolean  egllinitialize 2.Surface 配置和创建sur ...

  6. Ubuntu16.04 - 安装RabbitVCS,linux下的TortoiseSVN!!!

    RabbitVCS 官网:http://rabbitvcs.org/ 1,添加PPA源.在shell里面执行下面命令: sudo add-apt-repository ppa:rabbitvcs/pp ...

  7. flask之信号

    Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为. pip3 install blinker 1. 内置信号 request_started = ...

  8. iOS - 安全

    1. CheckList http://www.jianshu.com/p/d3cc2d5c177d a 数据安全.分为数据传输安全和数据存储安全 数据存储安全为保存在App中的数据安全.不允许明文存 ...

  9. jzoj5804

    這道題n-m很小,可以從此入手 記f[i][j]為i個字符括號綜合為j的合法方案數 則第i個括號可以枚舉為(和),所以f[i][j]=f[i-1][j-1]+f[i-1][j+1],小心越界 再記a為 ...

  10. JavaScript基础函数和词法分析以及常用的内置对象和使用方法(4)

    day52 参考:https://www.cnblogs.com/liwenzhou/p/8004649.html 函数 函数定义 JavaScript中的函数和Python中的非常类似,只是定义方式 ...