1.问题陈述:
找出销往各个国家商品数量。
输入: 我们的畋输入数据集合是一个 CSV 文件, Sales2014.csv
输出:国家名 销往次国家的商品数量
商品信息如图所示
2.前提条件:
• 本教程是在Linux上开发
• 已经安装了Hadoop(本教程使用版本2.6.5)
• 系统上已安装了Java(本教程使用 JDK1.8.0)。
3.步骤:(先运行成功再看4的解释)
3.1.创建一个新的目录名称是:MapReduceTutorial
[root@hdp-node-01 /]# cd /usr/local
[root@hdp-node-01 local]# mkdir MapReduceTutorial
3.2.授予权限
[root@hdp-node-01 local]# chmod -R 777 MapReduceTutorial
3.3 下载相关文件:下载 Java 程序文件,拷贝以下文件:SalesMapper.java, SalesCountryReducer.java 和 SalesCountryDriver.java 到 MapReduceTutorial 目录中,
3.4 检查所有这些文件的文件权限是否正确:ll
如果“读取”权限缺少可重新再授予权限,执行以下命令: chmod +r *
3.5 在MapReduceTutorial文件夹下新建一个文件夹SalesCountry用于存放编译好的class文件
[root@hdp-node-01 MapReduceTutorial]# mkdir SalesCountry
3.6.导出类路径
[root@hdp-node-01 MapReduceTutorial]# export CLASSPATH="$HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-client-core-2.6.5.jar:$HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-client-common-2.6.5.jar:$HADOOP_HOME/share/hadoop/common/hadoop-common-2.6.5.jar:~/MapReduceTutorial/SalesCountry/*:$HADOOP_HOME/lib/*"
注意:其中Hadoop版本需替换为自己的
执行完此步后java编译后将放在SalesCountry文件夹下
3.7 编译Jav[root@hdp-node-01 MapReduceTutorial]# javac -d . SalesMapper.java SalesCountryReducer.java SalesCountryDriver.java上面-d后有一点
3.8查看SalesCountry文件夹
[root@hdp-node-01 MapReduceTutorial]# ls ./SalesCountry
SalesCountryDriver.class SalesCountryReducer.class SalesMapper.class
3.9 创建一个新的文件:Manifest.txt——–这样运行jar时不用再指定主类
[root@hdp-node-01 MapReduceTutorial]# vi Manifest.txt
添加以下内容到文件中:
Main-Class: SalesCountry.SalesCountryDriver
其中SalesCountry.SalesCountryDriver 是主类的名称。请注意,必须键入回车键,在该行的末尾。
3.10 创建一个 jar 文件
[root@hdp-node-01 MapReduceTutorial]# jar cfm ProductSalePerCountry.jar Manifest.txt SalesCountry/*.class
注:前提需配置了Java环境变量
检查所创建的 jar 文件,结果如下:
3.11 启动 Hadoop
[root@hdp-node-01 MapReduceTutorial]#$HADOOP_HOME/sbin/start-dfs.sh
[root@hdp-node-01 MapReduceTutorial]#$HADOOP_HOME/sbin/start-yarn.sh
3.12 传到HDFS
[root@hdp-node-01 MapReduceTutorial]# hdfs dfs -mkdir /inputMapReduce
[root@hdp-node-01 MapReduceTutorial]# hdfs dfs -put ./Sales2014.csv /inputMapReduce
[root@hdp-node-01 MapReduceTutorial]# hadoop fs -ls /inputMapReduce
3.13. 运行MapReduce 作业
[root@hdp-node-01 MapReduceTutorial]# hadoop jar ProductSalePerCountry.jar /inputMapReduce /mapreduce_output_sales
SalesCountry.SalesCountryDriver :主类
inputMapReduce :输入目录
mapreduce_output_sales: 程序将在HDFS上自动创建一个输出目录。
若没有成功配置Manifest指定主类打包jar,则需加上主类
[root@hdp-node-01 MapReduceTutorial]# hadoop jar ProductSalePerCountry.jar SalesCountry.SalesCountryDriver /inputMapReduce /mapreduce_output_sales
3.14. 结果可以通过命令界面中可以看到
[root@hdp-node-01 MapReduceTutorial]# hdfs dfs -cat /mapreduce_output_sales/part-00000
结果也可以通过 Web 界面看到,打开 Web 浏览器,输入网址:http://192.168.33.101:50070/dfshealth.jsp ,结果如下:
现在选择 ‘Browse the filesystem’ 并导航到 /mapreduce_output_sales 如下:
打开 part-r-00000 ,如下图所示:
下载后,查看结果内容。
3.15 重新运行需删除输出目录:
[root@hdp-node-01 MapReduceTutorial]# hdfs dfs -rmr /mapreduce_output_sales/
4.程序说明
4.1 SalesMapper.java
package SalesCountry;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.*;
//每一个 Mapper可以选择性地继承 MapReduceBase ,它必须实现 Mapper 接口。
//Mapper
public class SalesMapper extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> {
private final static IntWritable one = new IntWritable(1);
public void map(LongWritable key, Text value, OutputCollector output, Reporter reporter) throws IOException {
String valueString = value.toString();
//这里,“,” 被用作分隔符。
String[] SingleCountryData = valueString.split(",");
//在这之后,使用记录在数组 'SingleCountryData' 中的第七索引,其值为 '1'.
output.collect(new Text(SingleCountryData[7]), one);
}
}
注:
在这之后,使用记录在数组 ‘SingleCountryData’ 中的第七索引,其值为 ‘1’.
output.collect(new Text(SingleCountryData[7]), one);
我们在选择第7索引记录,因为我们需要的国家数据,它位于数组 ‘SingleCountryData’ 的第七索引。
请注意,我们输入的数据是下面的格式 (Country 在索引的位置为:7, 0 是开始的索引)-
Transaction_date,Product,Price,Payment_Type,Name,City,State,Country,Account_Created,Last_Login,Latitude,Longitude
Mapper的输出使用的是 ‘OutputCollector’ 的 ‘collect()’ 方法的键值对.
package SalesCountry;
import java.io.IOException;
import java.util.*;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.*;
//嵌套类Reducer
//Reduce
//Reducer的valuein类型要和Mapper的valueout类型一致,
// Reducer的valuein是Mapper的valueout经过shuffle之后的值
public class SalesCountryReducer extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {
public void reduce(Text t_key, Iterator values, OutputCollector output, Reporter reporter) throws IOException {
Text key = t_key;
int frequencyForCountry = 0;
while (values.hasNext()) {
// replace type of value with the actual type of our value
IntWritable value = (IntWritable) values.next();
frequencyForCountry += value.get();
}
output.collect(key, new IntWritable(frequencyForCountry));
}
}
注:
输入到 reduce() 方法是在具有多个值的列表中选择一个键。
例如,在我们的示例中,这将是 -
< United Arab Emirates, 1>, < United Arab Emirates, 1>, < United Arab Emirates, 1>,< United Arab Emirates, 1>, < United Arab Emirates, 1>, < United Arab Emirates, 1>.
这赋予 reducer 作为 < United Arab Emirates, {1,1,1,1,1,1}>
因此,接受这种形式参数,前两个数据类型的使用,即 Text 和 Iterator< IntWritable>.
Text是一个数据类型的键 和,Iterator< IntWritable>为对于键的值的列表的数据类型。
接下来的参数的类型是 OutputCollector< Text,IntWritable> 它收集 reducer 阶段的输出。
reduce() 方法开始通过复制键值和初始化频率计数为0。
Text key = t_key;
int frequencyForCountry = 0;
然后,使用 “while” 循环,我们通过与键关联的值列表循环,并通过总结所有计算的值。
while(values.hasNext()) {
// replace type of value with the actual type of our value
IntWritable value = (IntWritable) values.next();
frequencyForCountry += value.get();
}
现在,结果中的键得到的频率计数输出到收集器。
下面的代码执行这个 -output.collect(key, new IntWritable(frequencyForCountry));
4.3 SalesCountryDriver.java
package SalesCountry;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapred.*;
public class SalesCountryDriver {
public static void main(String[] args) {
JobClient my_client = new JobClient();
// Create a configuration object for the job
JobConf job_conf = new JobConf(SalesCountryDriver.class);
// Set a name of the Job
job_conf.setJobName("SalePerCountry");
// Specify data type of output key and value
job_conf.setOutputKeyClass(Text.class);
job_conf.setOutputValueClass(IntWritable.class);
// Specify names of Mapper and Reducer Class
job_conf.setMapperClass(SalesCountry.SalesMapper.class);
job_conf.setReducerClass(SalesCountry.SalesCountryReducer.class);
// Specify formats of the data type of Input and output
job_conf.setInputFormat(TextInputFormat.class);
job_conf.setOutputFormat(TextOutputFormat.class);
// Set input and output directories using command line arguments,
//arg[0] = name of input directory on HDFS, and arg[1] = name of output directory to be created to store the output file.
FileInputFormat.setInputPaths(job_conf, new Path(args[0]));
FileOutputFormat.setOutputPath(job_conf, new Path(args[1]));
my_client.setConf(job_conf);
try {
// Run the job
JobClient.runJob(job_conf);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注:
1 定义一个用于创建一个新的客户端工作,配置 Mapper及Reducer 类对象驱动程序类。该驱动程序类负责设置我们的 MapReduce 作业在 Hadoop 运行。 在这个类中,我们指定作业名称,输入/输出,mapper 和 reducer 类名称的数据类型。
2 在下面的代码片段中,我们设置这是用来输入数据集消费和生产输出,分别输入和输出目录。
arg[0] 和 arg[1] 是通过 MapReduce 的实际操作,也就是赋予在命令行参数执行命令
[root@hdp-node-01 MapReduceTutorial]# hadoop jar ProductSalePerCountry.jar /inputMapReduce /mapreduce_output_sales
3 触发我们的作业
下面的代码开始执行 MapReduce 作业
try{
// Run the job
JobClient.runJob(job_conf);
} catch(Exception e) {
e.printStackTrace();
}