1.我们刚一开始的时候,在HDFS上面处理文件时候,我们并没有自己写MapReduce,而是用的是镜像架包下面的/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.6.4.jar,同样的也将运行出来结果(hadoop jar hadoop-mapreduce-examples-2.6.4.jar wordcount 文件的在Linux上的源路径 文件处理后结果存放在HDFS上的路径)
例如:hadoop jar hadoop-mapreduce-examples-2.6.4.jar wordcount /root/wordcount.txt /wordcount/input
这其实是hadoop架包里面自带的一个Mapreduce的demo实例,红色是我们要运行的主方法名,通常我们在实例中需要写绝对路径;黄色是我们将要处理的文件在Linux上的绝对路径;绿色是我们处理后文件,得到的结果保存的位置(指的是保存在HDFS上的绝对路径,并且这个路径在HDFS上不能存在,当运行命令的时候回自动创建,如果存在程序会包错误)
2.往往在实际开发中我们需要自己写MapReduce的业务逻辑,hadoop架包里的已经不能满足我们的多样化需求了,下面就介绍如何去customMapReduce
我们在创建项目时候可以选择maven也可以选择javaProject,maven比较简单直接从aliyun仓库在线下载架包即可,Javaproject则需要我们机子手动搭建所需架包,
- 首先将你的cenos-6.5-hadoop-2.6.4架包解压,
- 进入\cenos-6.5-hadoop-2.6.4\hadoop-2.6.4\share\hadoop;将common下面lib下的+sources下的+hadoop-common-2.6.4+hdfs下面的lib下面的+sources下面的+hadoop-hdfs-2.6.4导入buildpath下面
- \cenos-6.5-hadoop-2.6.4\hadoop-2.6.4\share\hadoop\mapreduce\下面的全部架包导入buildpath下面
- 最后apply一下就可以
3.实例:统计每一个用户的使用总流量
此案例我们以对象的形式在网络间传输,所以要想在网络间传输,我们就要对对象进行序列化,正好hadoop内部封装有序列化接口,我们只需要实现这个接口即可WritableComparable
public class TelBean implements WritableComparable<TelBean> {
private String tel;用户电话号码
private Long upPayLoad;上行流量
private Long downPayLoad;下行流量
private Long totalPayLoad;总流量=上行+下行
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public Long getUpPayLoad() {
return upPayLoad;
}
public void setUpPayLoad(Long upPayLoad) {
this.upPayLoad = upPayLoad;
}
public Long getDownPayLoad() {
return downPayLoad;
}
public void setDownPayLoad(Long downPayLoad) {
this.downPayLoad = downPayLoad;
}
public Long getTotalPayLoad() {
return totalPayLoad;
}
public void setTotalPayLoad(Long totalPayLoad) {
this.totalPayLoad = totalPayLoad;
}
//序列化
@Override
public void readFields(DataInput in) throws IOException {
// TODO Auto-generated method stub
this.tel = in.readUTF();
this.upPayLoad = in.readLong();
this.downPayLoad = in.readLong();
this.totalPayLoad = in.readLong();
}
//反序列化
@Override
public void write(DataOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeUTF(tel);
out.writeLong(upPayLoad);
out.writeLong(downPayLoad);
out.writeLong(totalPayLoad);
}
//对象之间的比较,排序
@Override
public int compareTo(TelBean bean) {
// TODO Auto-generated method stub
if (this.getTotalPayLoad() > bean.getTotalPayLoad()) {
return -1;
} else if (this.getTotalPayLoad() < bean.getTotalPayLoad()) {
return 1;
} else {
if (this.getDownPayLoad() > bean.getDownPayLoad()) {
return -1;
} else {
return 1;
}
}
}
@Override
public String toString() {
return tel + "\t" + upPayLoad + "\t" + downPayLoad + "\t" + totalPayLoad;
}
public TelBean(String tel, Long upPayLoad, Long downPayLoad, Long totalPayLoad) {
super();
this.tel = tel;
this.upPayLoad = upPayLoad;
this.downPayLoad = downPayLoad;
this.totalPayLoad = totalPayLoad;
}
public TelBean() {
super();
// TODO Auto-generated constructor stub
}
public TelBean(Long upPayLoad, Long downPayLoad, Long totalPayLoad) {
super();
this.upPayLoad = upPayLoad;
this.downPayLoad = downPayLoad;
this.totalPayLoad = totalPayLoad;
}
}
4.重写Mapper,同样只需要继承hadoop内部封装好的Mapper类即可,要注意的要在网络间传输,一切都要序列化,string对应的序列化后是text,int序列化后是IntWritable,long序列化后LongWritable等等
LongWritable指的是获取一行内容的起始偏移量
Text指的是一行文本内容
Text指的是我们将要输出的tel
TelBean指的是以对象的形式进行输出
public class TCMapper extends Mapper<LongWritable, Text, Text, TelBean>{
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, TelBean>.Context context)
throws IOException, InterruptedException {
// TODO Auto-generated method stub
获取一行信息
String line = value.toString();
将单词以\t进行分割
String[] fields = line.split("\t");
实例化对象
TelBean bean = new TelBean(fields[1],Long.valueOf(fields[8]),Long.valueOf(fields[9]),new Long(0));
设置将要传送给reduce的数据形式
context.write(new Text(fields[1]), bean);
}
}
5.对于Reduce同样hadoop内部封装有Reducer
注意的是map的输出就是reduce的输入
Text指的是map传送过来的keyout,在这里是keyin
我们最后想要的结果肯定是手机号对应的上行,下行,总流量
所以text数字的是tel
其他的封装在telBean里面
public class TCReducer extends Reducer<Text, TelBean, Text, TelBean>{
@Override
protected void reduce(Text key, Iterable<TelBean> value, Context context)
throws IOException, InterruptedException {
// TODO Auto-generated method stub
// 做统计
long sumUp = 0;
long sumDown = 0;
for (TelBean bean : value) {
sumUp += bean.getUpPayLoad();
sumDown += bean.getDownPayLoad();
}
// telBean的属性应该与log一一对应
// 当前的bean应该是一个新的bean
TelBean bean =
new TelBean(sumUp, sumDown, sumUp+sumDown);
context.write(new Text(key), bean);
}
}
6.如果我们想将所有手机号前三位为135或者136的手机号令存放一个文件,代表的是同一个归属地的手机号,那么我们要用到partitioner分割器
map -- suffer -- reduce
map的输出是suffer的输入
public class TCPartitioner extends Partitioner<Text, TelBean>{
@Override
public int getPartition(Text key, TelBean bean, int arg2) {
// TODO Auto-generated method stub
//生成的文件part-r-00000 part的编号的结尾就是这个int类型的返回值
//根据不同的电话号码 ,划分到不同的区里面
String tel = bean.getTel();
String subTel = tel.substring(0, 3);
if ("135".equals(subTel)||"136".equals(subTel)) {
return 1;//part-r-00001里面
}
return 0;
}
}
7.创建job
public class TCAPP {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 获取job
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 指定job使用的类
job.setJarByClass(TCAPP.class);
// 将partitioner添加到job里面
job.setPartitionerClass(TCPartitioner.class);
// 设置reduceTasks的数量 有几个分区设置几个任务
job.setNumReduceTasks(2);
// 设置mapper的类以及属性
job.setMapperClass(TCMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(TelBean.class);
// 设置reduce的类以及属性
job.setReducerClass(TCReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(TelBean.class);
// 设置输入文件 在调用的时候动态的传递参数
FileInputFormat.setInputPaths(job, new Path(args[0]));
// 设置输出目录
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 提交任务
job.waitForCompletion(true);
}
}
8.将你当前的项目打包
9.启动hadoop集群
10.上传要处理的文件和打好的包到Linux下
11.将要处理的文件上传到HDFS根目录下
hadoop fs -put /root/要处理的文件名+后缀 / (/代表的是hdfs根目录)
12.将hadoop的前端页面打开,观察是否上传成功
13.运行架包
hadoop jar 架包名 我们住方法绝对路径 我们上传在HDFS里面要处理的文件绝对路径 处理结果存放在HDFS里面的绝对路径
实际开发中要确保处理文件的时间大于HDFS启动的时间,要记住hadoop不适合处理小文件
14.前端页面查看是否成功
_SUCCESS只是一个标识,代表运行成功
part-r-000001则是我们要求的手机号码为135或者136开头的存放在一个文件中
注意:hadoop不适合处理小文件,实际开发中要避免多个小文件的产生,在源头进行处理,将小文件合并,或者是在map阶段将小的分区内容进行适当合并,减少reduce阶段的处理压力。