- 参考教材: Hadoop-The Definitive Guide (4th edition), Tom White
环境
- Operation System: MacOS Mojave Version 10.14.3,
- Java Version: Java 10.0.1
- Hadoop Version: Hadoop 3.1.1
准备工作 & 项目概述
- 首先到本书配套的网站上下载本书中所涉及到的代码. 可以看到书中每个章节都有一个单独的文件夹目录. 这里拿chapter 02 中的项目试手(源码在
ch02-mr-intro
文件夹中), 我们的任务是通过分析weather数据, 从而找出每一年最高气温. 该项目涉及到的样例输入在这里.
I/O格式
1. 输入格式
- 样例输入中有两个文件, 分别是1901.gz与1902.gz, 解压之后是两个txt文件, 代表两个年份的weather数据. 每个文件由很多行组成, 每一行是某一个时刻的weather数据, 由多个字段组成, 字段与字段之间没有分隔符. 每个字段的含义包括观测地点、气温、气压等等. 这里我们只关心两个字段, 气温字段与quality code字段. 气温字段是从index = 88(每一行第一个字符index = 0)开始的5个字符, 表示气温数据; quality code是从index = 93开始的一个数字字符, 当它为0, 1, 4, 5或9时表示气温数据有效, 否则无效.
2.输出格式
- 对于每个年份, 输出该年份的最高气温.
实现
- 整个代码的逻辑很简单, 对于每一年份, 从该年份对应的txt文件里面的每一行抽取气温数据, 用每一行对应的quality node过滤掉无效的数据后, 求剩下的这些气温数据的最大值. 下面是作为对比的两种方法.
1. 传统方法
- 书中给出了一个脚本文件用来实现我们的要求, 该脚本文件在
ch02-mr-intro/src/main/awk/max_temperature.sh
中. 为了运行该脚本, 我们需要把前面提到的样例输入(连同外面的all文件夹一起)放在awk目录下, 然后在awk目录下运行sh文件, 输出如下:
2. 用java实现mapreduce算法
- 这一部分就很复杂了, 涉及mapreduce的原理与java的配置, 还与hadoop的配置有关. 折腾了两三天之后终于把书中的代码给跑起来了. 关于mapreduce的原理书中介绍得很详细, 这里只记录一下跑代码的全过程.
a) 准备输入文件
- 将1901与1902两个txt文件合并, 即将其中一个文件的全部内容追加到另一个文件的末尾, 并把整合后的文件命名为input.txt作为输入文件.
- 开启集群, 并确保namenode、datanode已经打开.
start-dfs.sh
start-yarn.sh # 如果报Not found的错是因为没有把$HADOOP_HOME/sbin/添加进环境变量$PATH
- 把input.txt上传到hdfs上.
hdfs dfs -mkdir /user
hdfs dfs -mkdir /user/katsura
hdfs dfs -put input.txt /user/katsura # 这里katsura应该改成你自己的用户名
b) 配置环境变量
- 我们首先进入
ch02-mr-intro/src/main/java
文件夹查看源码, 发现里面有好几个文件, 我们只需要关注三个文件:
MaxTemperature.java # 建立一个 job 类并提交到 hadoop 系统
MaxTemperatureMapper.java # Map 模块
MaxTemperatureReducer.java # Reduce 模块
- 可以尝试打开这些java文件看一看. 这里我们只关注怎么跑起来, 所以我们只关心import了哪些. 可以看到每个源代码import了一堆
org.apache.hadoop
开头的包. 为了让java编译器找到这些包, 我们需要把包的位置加入$CLASSPATH
这一环境变量中. ($CLASSPATH
是什么?) 打开~/.bash_profile
, 在文件中输入以下命令. 保存后记得用source ~/.bash_profile
导入设置. (如果用的是zsh, 则把~/.bash_profile
替换为~/.zshrc
即可)
export CLASSPATH=$HADOOP_HOME/share/hadoop/common/*:$HADOOP_HOME/share/hadoop/common/lib/*:$CLASSPATH
export CLASSPATH=$HADOOP_HOME/share/hadoop/hdfs/*:$HADOOP_HOME/share/hadoop/hdfs/lib/*:$CLASSPATH
export CLASSPATH=$HADOOP_HOME/share/hadoop/mapreduce/*:$HADOOP_HOME/share/hadoop/mapreduce/lib/*:$CLASSPATH
export CLASSPATH=$HADOOP_HOME/share/hadoop/yarn/*:$HADOOP_HOME/share/hadoop/yarn/lib/*:$CLASSPATH
还没完, 这里还需要一个重要的库jaxb, 如果没有的话会在程序运行的时候报java.lang.NoClassDefFoundError的错. 这个库在java 8中是自带的, 但不幸的是java 9 与 java 10 已经默认不启用这个库了(详细介绍请移步这里). 不过已经安装了java9或java10的小伙伴不要灰心, 下面的方法在java 10下亲测管用(java 9应该也是差不多的).
按道理来讲java 10 的jdk是自带了jaxb的包的, 但是我找半天没找到, 就直接粗暴地从网上下了: javax.activation-1.2.0.jar, jaxb-api-2.3.0.jar, jaxb-core-2.3.0.jar, jaxb-impl-2.3.0.jar. 新建一个文件夹
jaxb
并把这四个文件放进去. 然后需要把jaxb的路径加入到$CLASSPATH
中. 再次打开~/.bash_profile
(或~/.zshrc
), 添加以下命令后, 使用source ~/.bash_profile
使配置生效.
# 这里我是把jaxb文件夹放在了$JAVA_HOME目录下
export CLASSPATH=$JAVA_HOME/jaxb/*:$CLASSPATH
-
这还没完, 上述只是让java编译器找到各种库的位置, 我们还需要配置hadoop的classpath. 用
echo $CLASSPATH
查看java的classpath, 如下图所示:
然后把这一大串地址复制下来, 再打开
$HADOOP_HOME/etc/hadoop/yarn-site.xml
, 然后把以下代码加进去, 到这一步环境变量就算配置完了.
<property>
<name>yarn.application.classpath</name>
<value>
(这里填刚才复制的一大串地址)
</value>
</property>
c) 编译运行
# 进入java文件夹
cd your_path/hadoop-book-master/ch02-mr-intro/src/main/java
# 依次编译三个java文件, 得到三个class文件
javac MaxTemperatureMapper.java
javac MaxTemperatureReducer.java
javac MaxTemperature.java
# 打包成jar文件
jar -cvf MaxTemperature.jar *.class
# 提交到hadoop上运行
hadoop jar MaxTemperature.jar MaxTemperature input.txt output
d) 运行结果
- 成功运行之后, 会出现以下的提示信息.
hadoop jar MaxTemperature.jar MaxTemperature input.txt output
2019-03-08 23:31:45,994 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
2019-03-08 23:31:46,612 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
2019-03-08 23:31:47,105 WARN mapreduce.JobResourceUploader: Hadoop command-line option parsing not performed. Implement the Tool interface and execute your application with ToolRunner to remedy this.
2019-03-08 23:31:47,129 INFO mapreduce.JobResourceUploader: Disabling Erasure Coding for path: /tmp/hadoop-yarn/staging/katsura/.staging/job_1552058233079_0004
2019-03-08 23:31:47,342 INFO input.FileInputFormat: Total input files to process : 1
2019-03-08 23:31:47,459 INFO mapreduce.JobSubmitter: number of splits:1
2019-03-08 23:31:47,514 INFO Configuration.deprecation: yarn.resourcemanager.system-metrics-publisher.enabled is deprecated. Instead, use yarn.system-metrics-publisher.enabled
2019-03-08 23:31:47,653 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1552058233079_0004
2019-03-08 23:31:47,654 INFO mapreduce.JobSubmitter: Executing with tokens: []
2019-03-08 23:31:47,796 INFO conf.Configuration: resource-types.xml not found
2019-03-08 23:31:47,796 INFO resource.ResourceUtils: Unable to find 'resource-types.xml'.
2019-03-08 23:31:47,841 INFO impl.YarnClientImpl: Submitted application application_1552058233079_0004
2019-03-08 23:31:47,868 INFO mapreduce.Job: The url to track the job: http://katsura:8088/proxy/application_1552058233079_0004/
2019-03-08 23:31:47,869 INFO mapreduce.Job: Running job: job_1552058233079_0004
2019-03-08 23:31:53,954 INFO mapreduce.Job: Job job_1552058233079_0004 running in uber mode : false
2019-03-08 23:31:53,956 INFO mapreduce.Job: map 0% reduce 0%
2019-03-08 23:31:59,012 INFO mapreduce.Job: map 100% reduce 0%
2019-03-08 23:32:03,036 INFO mapreduce.Job: map 100% reduce 100%
2019-03-08 23:32:05,068 INFO mapreduce.Job: Job job_1552058233079_0004 completed successfully
2019-03-08 23:32:05,151 INFO mapreduce.Job: Counters: 49
File System Counters
FILE: Number of bytes read=144425
FILE: Number of bytes written=717595
FILE: Number of read operations=0
FILE: Number of large read operations=0
FILE: Number of write operations=0
HDFS: Number of bytes read=1777277
HDFS: Number of bytes written=18
HDFS: Number of read operations=8
HDFS: Number of large read operations=0
HDFS: Number of write operations=2
Job Counters
Launched map tasks=1
Launched reduce tasks=1
Data-local map tasks=1
Total time spent by all maps in occupied slots (ms)=2419
Total time spent by all reduces in occupied slots (ms)=2262
Total time spent by all map tasks (ms)=2419
Total time spent by all reduce tasks (ms)=2262
Total vcore-milliseconds taken by all map tasks=2419
Total vcore-milliseconds taken by all reduce tasks=2262
Total megabyte-milliseconds taken by all map tasks=2477056
Total megabyte-milliseconds taken by all reduce tasks=2316288
Map-Reduce Framework
Map input records=13130
Map output records=13129
Map output bytes=118161
Map output materialized bytes=144425
Input split bytes=109
Combine input records=0
Combine output records=0
Reduce input groups=2
Reduce shuffle bytes=144425
Reduce input records=13129
Reduce output records=2
Spilled Records=26258
Shuffled Maps =1
Failed Shuffles=0
Merged Map outputs=1
GC time elapsed (ms)=38
CPU time spent (ms)=0
Physical memory (bytes) snapshot=0
Virtual memory (bytes) snapshot=0
Total committed heap usage (bytes)=271581184
Shuffle Errors
BAD_ID=0
CONNECTION=0
IO_ERROR=0
WRONG_LENGTH=0
WRONG_MAP=0
WRONG_REDUCE=0
File Input Format Counters
Bytes Read=1777168
File Output Format Counters
Bytes Written=18
e) 查看结果
- 运行的结果在hdfs://user/katsura/output中, 需要我们先把output文件夹下载下来再查看.
# 下载output文件夹到本地
hdfs dfs -get /user/katsura/output
# 依次查看output文件夹中的内容.
for file in output/*
do
cat $file
done
-
结果如下图所示, 可以看到最后的输出与前面一个方法的结果是一样的.