1.程序初始化
此常规Java项目,不是Maven项目,也不是Java Enterprise项目。
打开 File->New->Project菜单,选择Java即可,逐步点击Next,在目录D:Javahadoopmr下创建一个项目名称。
这里我们创建的项目叫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:Javahadoopjar,到这里功能上已经可以用了,但是为了便于区分管理我们导入的Jar包,这里可以点击这个目录,选择右侧的小铅笔(EDIT),给这些包在这个项目里起一个分组名称,这里我们起了hadoop-2.7.6。
3.编写mapreduce程序
写Java类,通常有两种方式,一种是类中类,只写一个Java文件,一种是一个类一个Java文件,多个Java文件。
这里我们选择分开写,增加对mapreduce原理对认识和理解。
创建包体,右击src,new->package,输入包名,这里我们命名包为com.leeyk99.com。(这个包名写的瞎眼了,后续会写com.leeyk99.hadoop)
重点参考:
https://www.cnblogs.com/bovenson/p/6275762.html?utm_source=itdadao&utm_medium=referral
个人学习的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文件,如果有就进行配置即可。流程步骤:(未完待续)
6.代码、JAR包、测试数据下载
https://files.cnblogs.com/files/leeyuki/code.rar 代码
JAR包大于10M,博客园传不了,如需要自行下载或留言索取。