创建一个普通的Java项目,编写MapReduce程序

1.程序初始化

此常规Java项目,不是Maven项目,也不是Java Enterprise项目。

打开 File->New->Project菜单,选择Java即可,逐步点击Next,在目录D:\Java\hadoop\mr下创建一个项目名称。

这里我们创建的项目叫groupbysum,表示groupbysum MapReduce小项目。

以后各种功能的mapreduce程序均已小项目形式放在mr目录下。

其实我们也可以把mr创建为一个项目(类似空间),各个小mapreduce程序作为modules(项目)放在该项目(空间)下,但这种方式一般是是大项目,分组协同开发各个模块功能的时候使用较多,这里就不采用了,一次只开发一个项目。

2.导入Hadoop核心依赖包

导入执行mapreduce程序依赖的Hadoop包:

  • hadoop-core-1.2.1
  • hadoop-hdfs-2.7.6
  • hadoop-client-2.7.6
  • hadoop-auth-2.7.6
  • hadoop-mapreduce-client-core-2.7.6
  • commons-io-2.6
  • commons-logging-1.2

Apache Commons IO : 主要是文件处理,比如复制、输入输出、文件名处理、大小写敏感等等。

The Apache Commons IO library contains utility classes, stream implementations, file filters, file comparators, endian transformation classes, and much more.

导入操作:打开项目结构(CTRL+ALT_SHIFT+S 或者使用工具栏的 图标 ),点击Modules,切换tab标签到Dependencies,点击 +  加号,选择准备好的包含这些Jar包的目录D:\Java\hadoop\jar,到这里功能上已经可以用了,但是为了便于区分管理我们导入的Jar包,这里可以点击这个目录,选择右侧的小铅笔(EDIT),给这些包在这个项目里起一个分组名称,这里我们起了hadoop-2.7.6。

3.编写mapreduce程序

写Java类,通常有两种方式,一种是类中类,只写一个Java文件,一种是一个类一个Java文件,多个Java文件。

这里我们选择分开写,增加对mapreduce原理对认识和理解。

创建包体,右击src,new->package,输入包名,这里我们命名包为com.leeyk99.com。(这个包名写的瞎眼了,后续会写com.leeyk99.hadoop)

EN50f251c1EN50f251d1EN50f251e1EN50f251f0

重点参考:

  1. https://www.cnblogs.com/bovenson/p/6275762.html?utm_source=itdadao&utm_medium=referral

  2. https://www.cnblogs.com/hunttown/p/5810591.html

  3. https://hadoop.apache.org/docs/current/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html

  4. 个人学习的Java代码(文件和目录io代码 ioReadWrite.java)

意外发现:

因为之前在公司通过Maven创建了WordCount官方示例MR项目,仅在pom.xml中配置了最重要的几个Hadoop jar包依赖,成功运行。实际上Maven项目自动下载了相关的jar包,比如jackson-core-asl-1.9.13.jar、commons-configuration-1.10.ja等。

<!-- 基础依赖hadoop-core和hadoop-common -->

<!--hadoop-core的version一般为1.2.1-->

<dependency>

<groupId>org.apache.hadoop</groupId>

<artifactId>hadoop-core</artifactId>

<version>1.2.1</version>

</dependency>

<!--hadoop-common的version可以依照你的实际需要来-->

<dependency>

<groupId>org.apache.hadoop</groupId>

<artifactId>hadoop-common</artifactId>

<version>2.7.6</version>

</dependency>

<!--如果需要读写HDFS,则还需要依赖hadoop-hdfs和hadoop-client-->

<dependency>

<groupId>org.apache.hadoop</groupId>

<artifactId>hadoop-hdfs</artifactId>

<version>2.7.6</version>

</dependency>

<dependency>

<groupId>org.apache.hadoop</groupId>

<artifactId>hadoop-client</artifactId>

<version>2.7.6</version>

</dependency>

在这个普通Java项目导入第2步指定的jar包,报告了很多类缺失错误,最后都一一找来了。故,实际依赖完整的Jar包如下:

  • commons-cli-1.4.jar
  • commons-configuration-1.10.jar
  • commons-httpclient-3.1.jar (这个就是去了一个warn)
  • commons-io-2.6.jar
  • commons-lang-2.6.jar
  • commons-logging-1.2.jar
  • hadoop-auth-2.7.6.jar
  • hadoop-client-2.7.6.jar
  • hadoop-common-2.7.6.jar
  • hadoop-core-1.2.1.jar
  • hadoop-hdfs-2.7.6.jar
  • hadoop-mapreduce-client-core-2.7.6.jar
  • jackson-core-asl-1.9.13.jar
  • jackson-mapper-asl-1.8.8.jar

导入这些包后,运行无误。

故,在对Hadoop的包的功能基本了解或者实际开发的时候,为提高效率,可以使用Maven项目。

4.MapReduce学习总结

本次Hadoop MR学习总结,主要集中在对整体运行逻辑和局部编写细节的学习和测试,源码原理未研究。

4.1单维度单度量统计

该MR程序测试了单度量在单维度上聚合统计的情形,即常用的:

select dim_a,sum(kpi_a) from table_name group by dim_a

4.2继承Mapper类,重写map方法

GroupBySumMapper类继承抽象类:org.apache.hadoop.mapreduce.Mapper (新API)。(旧API:extends MapReduceBase implements Mapper<LongWritable, Text, Text, DoubleWritable>)

Mapper是一个泛型类型,有四个形参,分别是map函数的输入键、输入值、输出键、输出值。泛型类型的形参只能是引用类型,不能是原始类型(如int、double、char)。

Hadoop提供了一套可优化网络序列化传输的引用类型(LongWritable、Text、DoubleWritable、IntWritable),而不是直接用Java的引用类型(Long、String、Double、Integer,被hadoop提供的四个类型替代).

map函数是对一行记录进行处理,数据集的每一行是输入,输入键就是相对于文件起始位置的偏移量(如果从第一行开始,就是行号了),因为Hadoop通常是处理大数据量,因此输入键类型通常指定为 LongWritable.

获取到一行记录后,将行转换成成Java的String类型,提取相应的数据域(列),常用的方法:

  • a.无分隔符但每组数据长度都是固定的,可以使用字符串截取,比如substring;
  • b.有分隔符,可以使用split按分隔符分隔得到数组,获取特定的数组元素;
  • c.使用StringTokenizer(标记)类及其nextToken方法,按顺序依次获取数据列;

获取到指定数据域后,conntext.write将结果输出,写出到临时文件,作为reduce函数的输入。

map任务将其输出写到本地硬盘,而非HDFS。因为map的输出只是中间结果,一旦作业完成,map的输出即可以删除,存储到HDFS并实现备份,难免小题大做(说白了,占用存储,数据备份也需要时间和带宽)。

4.3继承Reducer类,重写reduce方法

reduce函数以map的输出作为输入,因此reduce函数的输入键、输入值和map的输出键、输出值类型需要是一致的,这种情况下reduce函数的输出类型也必须是Text和DoubleWritable。

reduce函数实现的操作通常是对输入值的遍历处理,比如求和、计数、比较、取均、去重等多种运算,然后将结果输出,写入到HDFS,作为最终产出结果。

4.4Hadoop Job配置及启动

Job对象指定作业执行规范。可以将代码打包成Jar文件,发不到Hadoop集群。不必指明JAR文件名称,在Job对象的setJarByClass(GroupBySumRun.class)方法中传递类即可。Hadoop会根据这个类查找相关的JAR文件.

构造Job对象后,指定输入(调用FileInputFormat类的静态方法addInputPath(),可以多次调用实现多路径输入,路径可以是单个文件、一个目录、符合特定模式的一些列文件)、输出(调用FileOutputFormat类的静态方法setOutputPath(),只能一个,且在运行前不能存在)路径。

接着setMapperClass、setReducerClass指定要使用的map类型、reduce类型。至于setCombinerClass根据需要来使用。combiner是对map的输出在MR框架混洗后的分组结果,进行组内计算,减少需要传递给reduce函数的数据量。

setOutputKeyClass()、setOutputValueClass()控制reduce函数的输出类型,并且必须和Reduce类产生的相匹配,map函数的输出类型默认情况下和reduce函数是相同的,因此mapper产生出和reducer函数相同的类型时,不需要单独设置map的输出类型,否则需要通过setMapOutputKeyClass()、setMapOutputValueClass()方法来设置map函数的输出类型。

文件的输入输出:

输入的类型通过输入格式来控制,setInputFormatClass(),如果不指定,则使用默认的格式TextInputFormat.class(文本输入格式);

输出的类型通过输出格式来控制,setOutputFormatClass(),如果不指定,则使用默认的格式TextOutputFormat.class(文本输入格式).


设置完成后,可以开始运行作业。waitForCompletion()方法提交作业并等待执行完成。其参数true表示作业会把其进度信息写到控制台,返回结果是布尔值,true-成,false-败。

这个例子的代码不是最简洁的,后续参照《Hadoo权威指南》再写个 简洁点的。

4.5Java io温习

由于reduce函数的输出目录在运行前必须不存在,为方便调试代码,不用每次都去手动删目录,写了DelOutputDir类,在提交作业前执行删除output目录及其目录下文件的方法。

这个方法要求output目录里只有文件,不能有目录,因为代码未对子目录作处理。

4.6 Intellij IDEA使用技巧

使用Intellij IDEA创建一个常规Java项目,导入外部依赖。参见1、2步。

这里未使用导入libraries的方法(这个是最推荐的方法,创建lib目录,添加JAR文件,后续尝试)。

5.MR程序打包提交到集群

Inerllij IDEA将程序打包成JAR文件,主要是做两件事:1.创建MANIFEST.MF文件,这个文件指定MAIN CLASS的位置(包);2.将MANIFEST.MF和编译后的class文件一起打包。

打开IDEA的项目结构 (CTRL+SHILT+ALT+S 或者 快捷图标 ),在Artifacts菜单中新建一个空JAR文件,如果有就进行配置即可。流程步骤:(未完待续)

Image  ---》


6.代码、JAR包、测试数据下载

https://files.cnblogs.com/files/leeyuki/code.rar 代码

JAR包大于10M,博客园传不了,如需要自行下载或留言索取。

posted @ 2018-08-22 10:53  leeyuki  阅读(10394)  评论(2编辑  收藏  举报