课程回顾:
wordcount程序:
单机版:
统计的6个文件
定义一个方法------读取每一个小文件进行统计
这个方法调用了6次
定义了一个最终统计的方法
这个方法调用了1次
最大值 最小值 平均值
map-----分
reduce----汇总
mapreduce的编程:
mapper<输入的key---默认是每一行的偏移量,输入的value---每一行的内容,
输出的key,输出的value>{
map(偏移量,一行内容,context){
}
}
reducer<输入key,输入value,输出key,输出value>{
reduce(一组的中的相同的key,一组中的所有value的迭代器,上下文对象){
}
}
driver-----main
mapreduce的运行模式:
1.打jar包的方式运行
生产用的 缺点:不便于本地调试
2.本地模式
本地的代码调试
1.路径 全路径
2.权限
System.setProperty("HADOOP_USER_NAME", "hadoop");
3.本地模式运行的时候提交到yarn集群上
不用 配置太麻烦---环境 修改源代码
mapreduce----分布式的并行计算
maptask的并行度:map阶段的分布式运行
整个map阶段的任务被分成了几个小任务运行 每一个小任务就称为一个maptask任务
maptask的概念:运行mapper的任务
并行度:同时运行的任务数量
同时可以运行的maptask的个数
wordcount-----只运行一个maptask 运行多个maptask
注意:一个maptask任务只能运行在一个节点上,一个节点上可以运行多个maptask任务的
整个任务被分成几个maptask任务,取决于 节点 数据量
一个maptask--------计算的任务量
4个文件进行数据统计
大小1.42kb
3个从节点 datanode nodemanager
maptask任务数量---3? 4? ----mapper---java---jps
运行wordcount的时候 发现 map阶段执行的时候 4个yarnchild
yarnchild-----maptask任务----mapper类
1个yarnchild-----1个maptask任务
maptask任务和数据有关的 和数据的文件个数有关?
4份数据:1T --- 假设和数据的文件个数有关----4maptask----1T
节点:10个 最多运行在4个节点上 剩下的6个节点 没有事情做--负载不均衡
1)负载不均衡
2)数据存储的 分块存储 1t---8192块 分散存储在10个节点上
每一个maptask要访问8192个数据块 跨节点访问数据
maptask任务和文件的个数无关
maptask任务应该数据量有关
一个maptask任务对应多少数据量?
wc---1个文件 500M
实际存储:
blk01:0-127M
blk02:128-255M
blk03:256-383M
blk04:384-500M
对应100M数据量---一个maptask任务 处理100M的数据
maptask01:0-99M blk01
maptask02:100-199M blk01 blk02
maptask03:200-299M blk02 blk03
.......
有极大的可能跨数据块 跨节点访问数据 性能低 不合理的
对应的数据量200M---一个maptask任务 处理200M数据量
maptask01:0-199M blk01 blk02
....
有极大的可能跨数据块 跨节点访问数据 不合理
分析 maptask对应的数据量128M的时候最合理的
maptask01:blk01
maptask02:blk02
maptask03:blk03
maptask04:blk04
底层实现是否是这样的?
事实上一个maptask任务对应的数据量 一个切片大小---split
切片:逻辑上的概念(逻辑切块) 逻辑切片 仅仅是一个偏移量的划分
一个maptask任务对应数据量就是一个逻辑切片
一个逻辑切片假设100M 意思就是这个逻辑切片对应的数据0-99M数据 不会真正的切分数据
理论上切片的大小就是数据块的大小
文件输入类:
FileInputFormat
getSplits()
//获取最终的切片大小
//128M 1 long_max
protected long computeSplitSize(long blockSize, long minSize,
long maxSize) {
//Math.min(maxSize====long_max, blockSize==128*1024*1024M) ====blocksize
//Math.max(minSize===1,blocksize===128M) ======== blocksize
return Math.max(minSize, Math.min(maxSize, blockSize));
}
}
修改切片大小:
小于128M-----修改max
大于128M-----修改min
修改的方式:
1.修改配置文件 mapred-site.xml
增加以下的两项配置
max---
mapreduce.input.fileinputformat.split.maxsize
min----
mapreduce.input.fileinputformat.split.minsize
一般不使用
2.代码中修改
FileInputFormat.setMaxInputSplitSize(job, 1*1024);
总结:maptask的并行度 和切片大小有关
一个切片----1个maptask任务
默认的时候1个切片的大小就是一个数据块的大小
block split的区别?
block:hdfs的数据存储的切分单元 默认128M 物理切分
split:mapreduce中maptask任务的时候 进行数据划分的单元 逻辑上的概念 没有真正的切分数据
理论上默认的split的大小===blocksize
当前的文件 不够128M 则单独成一个逻辑切片
最后一个切片 最大大小 blockSize/splitSize*1.1 128M*1.1=140.8M
maptask任务在哪一个节点执行---yarn资源分配决定的
maptask任务 执行MyMapper的类的代码
自定义的类---Writable接口
实现了自己的一套 序列化反序列化的规则 数值 Writable
hadoop内置的序列化 反序列化的类型:
intwritable
longwritable
text
nullwritable
bytewritable
doublewritable
floatwritable
booleanwritable
用户自定义的类型 作为mapreduce的传输的类怎么办?
具备序列化 和 反序列的能力 实现Writable接口
案例:
1、 统计每一个用户(手机号)所耗费的总上行流量、总下行流量,总流量
关键字:每一----分组
分组:手机号
map端:
取到每一行的内容 拆分每一个字段 发送
key:手机号
value:上行流量+下行流量
两种发送方式:
1)拼接字符串---练习
2)自定义一个类 FlowBean
reduce端:
循环遍历values 求和
mapreduce程序中 输出的key -- value的分隔
实现Writable 接口
重写:write() readFields方法
mapreduce的排序:shuffle(快速排序 归并排序)
mapreduce:中会默认按照map输出的key进行排序 默认升序
如果map输出key是数值类型 按照大小排序的 升序排序的
如果map输出的key是字符串类型的 按照顺序进行排序的 升序排序的
需求:
对wordcount的结果进行排序 按照单词出现的次数进行排序
shuffle过程中 可以进行排序的 利用这个排序为我们排序
hadoop 256
hadoophello 4
hello 164
hive 176
lily 84
spark 168
word 84
ww 84
想要利用shuffle排序 将需要排序的字段 放在map的输出的key上 输出结果还想要 单词--次数
reduce端输出的时候 将map的结果进行一个反转
map端:
获取一行数据 切分
key:次数
value:单词
reduce端:
将map输过来的数据 进行对调之后 输出就可以
降序排序:
原始类型:
hadoop中默认实现的类型 8个 都实现WritableComparable 具备比较能力 也具备序列化的能力
intwritable----
public class IntWritable implements WritableComparable<IntWritable>
public interface WritableComparable<T> extends Writable, Comparable<T> {
}
需要排序的 类型是自定义的类型 flowbean
自定义的类型 具备以下两点:
1)序列化和反序列功能 ---Writable(write readFields)
2)具备比较的能力 --- comparable(comparaTo)
自定义的类型必须同时实现两个接口
实现以下的接口:
WritableComparable-----(write readFields comparaTo)
需求:
2.得出上题结果的基础之上再加一个需求:将统计结果按照总流量倒序排序 再按手机号排序
借助shuffle排序---map的key 将排序字段放在map的key中
排序字段:总流量 手机号
Text---字典顺序排序的时候
1000000 13552785495
80 13552785494
map端:
key:总流量+手机号 --Text
value:剩下的字段
reduce:
key:手机号---text
value:上+下+总---text
-----练习
缺点:1)排序的性能 Text--String---字典顺序 一个字符一个字符比较的
流量---数字
2)结果有问题的
自定义的类实现:FlowBean---手机号 上行流量 下行流量 总流量
map:
key:fb
value:nullwritable
reduce:
输出
对于自定义类的排序要想借助shuffle过程排序 自定义的类必须放在map的输出的key
这个类必须实现writablecomparable接口
实现write readFields comparaTo
比较规则定义在comparaTo方法中的
如果自定义的类没有排序需求 放在map的key上了 一定要实现的writablecomparable
只要自定义的类放在map的key位置 就会默认按照他进行排序
map的key的自定义类---慎重
reducetask的并行度----分区
package com.ghgj.cn.testmapreduce;
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.mapreduce.Mapper;
public class MyMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
Text mk = new Text();
IntWritable mv = new IntWritable();
@Override
protected void map(LongWritable key, Text value,Context context)
throws IOException, InterruptedException {
//获取每一行内容
String line = value.toString();
//切分
String[] num = line.split("\t");
//循环遍历 发送
for(String n:num){
mk.set("hzh");
mv.set(Integer.parseInt(n));
context.write(mk, mv);
}
}
}
package com.ghgj.cn.testmapreduce;
import java.io.IOException;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
/**
* reduce输出
* key :输出最大最小
* value:值
*
*/
public class MyReducer extends Reducer<Text,IntWritable, Text, DoubleWritable>{
Text rk = new Text();
DoubleWritable rv = new DoubleWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values,
Context context) throws IOException, InterruptedException {
//循环遍历values
int max =0;
int min =10000;
int sum =0;
int count =0;
for(IntWritable v:values){
int data = v.get();
count++;
sum+=data;
//最大
if(max<data){
max=data;
}
//最小
if(min>data){
min=data;
}
//平均
double avg = (double)sum/count;
rk.set("最大值");
rv.set(max);
//context.write()可以调用多次,调用一次输出一条数据
context.write(rk, rv);
rk.set("最小值");
rv.set(min);
context.write(rk, rv);
rk.set("平均值");
rv.set(avg);
context.write(rk, rv);
}
}
}
package com.ghgj.cn.testmapreduce;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class Driver {
public static void main(String[] args) {
//将mapper 和 reducer 类进行一个封装 封装为一个任务job(作业)
//加载配置文件
Configuration conf = new Configuration();
//启动一个job【创建一个job对象】
try {
Job job = Job.getInstance(conf);
//设定job
//先设置整个job的主函数入口
job.setJarByClass(Driver.class);
//设置job的mapper的类
job.setMapperClass(MyMapper.class);
//设置job的reducer的类
job.setReducerClass(MyReducer.class);
//设置map输出key value的类型
//指定了泛型这里为什么还要设置一次 泛型的作用范围 编译的时候生效 运行的时候泛型擦除
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//设置reduce的输出的key value类型 以下设置的方法设置的是mr的最终输出
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(DoubleWritable.class);
//指定需要统计的文件的输入路径 FileInputFormat文件输入类
// Path inpath = new Path("/wcin");
Path inpath = new Path(args[0]);
FileInputFormat.addInputPath(job, inpath);
//指定输出目录 输出路径不能存在 否则会报错,默认输出是覆盖式的输出 如果目录存在,有可能造成元数据丢失
// Path outpath = new Path("/wc_out01");
Path outpath = new Path(args[1]);
FileOutputFormat.setOutputPath(job, outpath);
//提交job 执行这一句时候job才会提交 上面做的一系列的工作都是设置job
//job.submit
job.waitForCompletion(true);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//hadoop fs -cat /dataout01/part-r-00000
}
}