大数据学习----------MapReduce----------(六)Shuffle机制,Partition 分区,WritableComparable 排序,Combiner 合并,数据输出

mapreduc详细工作流程
在这里插入图片描述
在这里插入图片描述
(1)MapTask 收集我们的 map()方法输出的 kv 对,放到内存缓冲区中
(2)从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
(3)多个溢出文件会被合并成大的溢出文件
(4)在溢出过程及合并的过程中,都要调用 Partitioner 进行分区和针对 key 进行排序
(5)ReduceTask 根据自己的分区号,去各个 MapTask 机器上取相应的结果分区数据
(6)ReduceTask 会抓取到同一个分区的来自不同 MapTask 的结果文件,ReduceTask 会将这些文件再进行合并(归并排序)
(7)合并成大文件后,Shuffle 的过程也就结束了,后面进入 ReduceTask 的逻辑运算过程(从文件中取出一个一个的键值对 Group,调用用户自定义的 reduce()方法

注意:
(1)Shuffle 中的缓冲区大小会影响到 MapReduce 程序的执行效率,原则上说,缓冲区越大,磁盘 io 的次数越少,执行速度就越快。
(2)缓冲区的大小可以通过参数调整,参数:mapreduce.task.io.sort.mb 默认 100M。

一、Shuffle 机制
在这里插入图片描述
Map 方法之后,Reduce 方法之前的数据处理过程称之为 Shuffle。
这里的shuffle为什么设定缓冲数据存储到80%后就开始溢出到磁盘中呢?
首先如果直接到100%开始开启线程把数据读取到磁盘中的话 Map方法的数据就无法读取到shuffle缓存中了(缓存已经满了),存在堵塞情况,所以设定百分之80开始反向把数据读取到磁盘中。这样Map可以继续输入数据到缓存钟,如果百分之20数据满了后会等溢出的数据读取到磁盘中才开始技术接收Map输入。

Partition 分区
要求将统计结果按照条件输出到不同文件中(分区)。比如:将统计结果按照手机归属地不同省份输出到不同文件中(分区)
实现方法为继承Partitioner然后在getPartition方法中设计你的逻辑
然后再driver指定分区器,再ReduceTask的数量
在这里插入图片描述
在这里插入图片描述
WritableComparable 排序
主要分为全排序和分区内排序(生产环境一般用分区排序)。
MapTask和ReduceTask均会对数据按 照key进行排序。该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。
默认排序是按照字典顺序(及a,b,c这类)排序,且实现该排序的方法是快速排序。

如果想要按照自己的规则排序呢?
可以实现WritableComparable接口然后重写compareTo方法实现

@Override
public int compareTo(FlowBean bean) {
	int result;
	// 按照总流量大小,倒序排列
	if (this.sumFlow > bean.getSumFlow()) {
		result = -1;
	}else if (this.sumFlow < bean.getSumFlow()) {
		result = 1;
	}else {
		result = 0;
	}
return result; }

这里的FlowBean为一个对象来实现WritableComparable接口,我想根据sunFlow的大小来排序
这里是是Mapper

public class FlowMapper extends Mapper<LongWritable, Text, FlowBean, Text> {
    private FlowBean outK = new FlowBean();
    private Text outV = new Text();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //1 获取一行数据
        String line = value.toString();
        //2 按照"\t",切割数据
        String[] split = line.split("\t");
        String phone = split[1];
        String up = split[split.length - 3];
        String down = split[split.length - 2];
        //3 封装 outK outV 这里注意了需要把你要排序的对象放在key上面,reduce阶段再把key和value倒转过来
        outK.setUpFlow(Long.parseLong(up));
        outK.setDownFlow(Long.parseLong(down));
        outK.setSumFlow();
        outV.set(phone);
        //4 写出 outK outV
        context.write(outK,outV);
    }
}

这里是reduce

public class FlowReducer extends Reducer<FlowBean, Text, Text, FlowBean>
{
    @Override
    protected void reduce(FlowBean key, Iterable<Text> values, Context
            context) throws IOException, InterruptedException {
        //遍历 values 集合,循环写出,避免总流量相同的情况
        for (Text value : values) {
            //调换 KV 位置,然后输出
            context.write(value,key);
        }
    }
}

这里是Driver

public class FlowDriver {
    public static void main(String[] args) throws IOException,ClassNotFoundException, InterruptedException{
        //1获取job对象
        Configuration entries = new Configuration();
        Job job = Job.getInstance(entries);
        //2关联driver类
        job.setJarByClass(FlowDriver.class);
        //3 关联 Mapper 和 Reducer
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);
        //4 设置 Map 端输出数据的 KV 类型
        job.setMapOutputKeyClass(FlowBean.class);
        job.setMapOutputValueClass(Text.class);
        //8 指定自定义分区器  这里使用分组排序的时候添加
        //job.setPartitionerClass(ProvincePartitioner.class);
        //9 同时指定相应数量的 ReduceTask  这里使用分组排序的时候添加
        //job.setNumReduceTasks(5);
        //5 设置程序最终输出的 KV 类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);
        //6 设置程序的输入输出路径
        FileInputFormat.setInputPaths(job,new Path("D:\\bigdatastudy\\inputflow"));
        FileOutputFormat.setOutputPath(job, new Path("D:\\bigdatastudy\\outputflow"));
        //7 提交 Job
        boolean b = job.waitForCompletion(true);
        System.exit(b ? 0 : 1);
    }
}

分区排序和全排序就差8、9两部

Combiner 合并
(1)Combiner是MR程序中Mapper和Reducer之外的一种组件。
(2)Combiner组件的父类就是Reducer。
(3)Combiner和Reducer的区别在于运行的位置
Combiner是在每一个MapTask所在的节点运行,运行在MapTask的内存 上的;
Reducer是接收全局所有Mapper的输出结果;
(4)Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减小网络传输量。
(5)Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟Reducer的输入kv类型要对应起来。
举个例子:
要实现多个数据求平均值
Mapper阶段(使用MapTask,两个节点分别相加求和)
3 5 7 ->(3+5+7)/3=5
2 6 ->(2+6)/2=4
Reducer
(3+5+7+2+6)/5=23/5 不等于 (5+4)/2=9/2
由此可见要根据业务来使用Combiner

自定义 Combiner 实现步骤
(a)自定义一个 Combiner 继承 Reducer,重写 Reduce 方法

public class WordCountCombiner extends Reducer<Text, IntWritable, Text, 
IntWritable> {
 private IntWritable outV = new IntWritable();
 @Override
 protected void reduce(Text key, Iterable<IntWritable> values, Context 
context) throws IOException, InterruptedException {
	 int sum = 0;
	 for (IntWritable value : values) {
	 sum += value.get();
	 }
 
 	outV.set(sum);
 
 	context.write(key,outV);
 	} 
 }

(b)在 Job 驱动类中设置:

job.setCombinerClass(WordCountCombiner.class);

这里其实也可以WordCountCombiner.class替换成你的ruduce的类

总结一下Combiner和Reduce的区别在于一个是再Map阶段运行的

总结下Combiner和Reduce的区别在于Combiner是再Map阶段运行的使用Map阶段的资源,可以并行处理,Redcue阶段虽说也能并行处理但是数据得先从Map阶段的磁盘那边copy过来,中间涉及到网络传输压力,根据业务妥善使用。
下面的图是使用Combiner前后数据传输的比对,明显可以看出shuffle阶段数据是不是变小了
在这里插入图片描述
在这里插入图片描述

OutputFormat数据输出
在这里插入图片描述
相关案例来理解
在这里插入图片描述
(1)编写 LogMapper 类

public class LogMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
 @Override
 protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
	 //不做任何处理,直接写出一行 log 数据
	 	context.write(value,NullWritable.get());
	 } 
 }

(2)编写 LogReducer 类

public class LogReducer extends Reducer<Text, NullWritable,Text, NullWritable> {
 @Override
	 protected void reduce(Text key, Iterable<NullWritable>values, Context context) throws IOException, InterruptedException {
		 // 防止有相同的数据,迭代写出
		 for (NullWritable value : values) {
		 	context.write(key,NullWritable.get());
		 }
	 } 
 }

(3)自定义一个 LogOutputFormat 类

public class LogOutputFormat extends FileOutputFormat<Text, NullWritable> 
{
 @Override
 public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {
	 //创建一个自定义的 RecordWriter 返回
	 LogRecordWriter logRecordWriter = new LogRecordWriter(job);
	 	return logRecordWriter;
	 } 
 }

(4)编写 LogRecordWriter 类

public class LogRecordWriter extends RecordWriter<Text,NullWritable> {
 private FSDataOutputStream atguiguOut;
 private FSDataOutputStream otherOut;
 public LogRecordWriter(TaskAttemptContext job) {
	 try {
	 //获取文件系统对象
	 	FileSystem fs = FileSystem.get(job.getConfiguration());
		 //用文件系统对象创建两个输出流对应不同的目录
		 atguiguOut = fs.create(new Path("d:/hadoop/atguigu.log"));
		 otherOut = fs.create(new Path("d:/hadoop/other.log"));
	 } catch (IOException e) {
	 	e.printStackTrace();
	 }
 }
 @Override
 public void write(Text key, NullWritable value) throws IOException, InterruptedException {
	 String log = key.toString();
	 //根据一行的 log 数据是否包含 atguigu,判断两条输出流输出的内容
	 if (log.contains("atguigu")) {
	 	atguiguOut.writeBytes(log + "\n");
	 } else {
	 	otherOut.writeBytes(log + "\n");
	 }
 }
 	@Override
 	public void close(TaskAttemptContext context) throws IOException, InterruptedException {
		 //关流
		 IOUtils.closeStream(atguiguOut);
		 IOUtils.closeStream(otherOut);
	} 
}
 

(5)编写 LogDriver 类

public class LogDriver {
 public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
	 Configuration conf = new Configuration();
	 Job job = Job.getInstance(conf);
	 job.setJarByClass(LogDriver.class);
	 job.setMapperClass(LogMapper.class);
	 job.setReducerClass(LogReducer.class);
	 job.setMapOutputKeyClass(Text.class);
	 job.setMapOutputValueClass(NullWritable.class);
	 job.setOutputKeyClass(Text.class);
	 job.setOutputValueClass(NullWritable.class);
	 //设置自定义的 outputformat
	 job.setOutputFormatClass(LogOutputFormat.class);
	 FileInputFormat.setInputPaths(job, new Path("D:\\input"));
	 // 虽 然 我 们 自 定 义 了 outputformat , 但 是 因 为 我 们 的outputformat 继承自
	fileoutputformat
	 //而 fileoutputformat 要输出一个_SUCCESS 文件,所以在这还得指定一个输出目录
	 FileOutputFormat.setOutputPath(job, new Path("D:\\logoutput"));
	 boolean b = job.waitForCompletion(true);
	 System.exit(b ? 0 : 1);
	 } 
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据分析—排序 排序数据分析中最常⽤的操作,没有之⼀。 单字段升序: 利⽤ mapreduce ⾃⾝的排序机制,把需要排序的字段作为 key 即可 其它排序: ⾃定义排序规则 ⽅案⼀:⾃定义 key 1. ⾃定义类实现 WritableComparable 接⼝,设置泛型为⾃定义类 2. 声明需要排序的字段,添加 setter && getter 3. 实现接⼝的 3 个⽅法 write:序列化属性值为⼆进制 readFields:从⼆进制反序列化属性值 compareTo:⾃定义⼤⼩⽐较规则 4. 使⽤⾃定义 key 作为 map 输出的 key 类型 ⽅案⼆:⾃定义排序规则 1. ⾃定义类继承 WritableComparator 2. 重写⽗类⽆参构造⽅法,指定 key 的类型 super(Text.class, true); 3. 重写⽗类 compare(WritableComparable a, WritableComparable b) ⽅法,实现⾃定义⽐较规则 4. 设置 job 使⽤⾃定义排序规则 job.setSortComparatorClass() ⼆次排序(多字段排序): 先按照 ip 排序,ip 相同按照 count 排序 1. 把所有需要排序的字段放⼊ key 中 2. ⾃定义排序规则 3. 在 compare 中设置⽐较的顺序 action 升序,count 降序 ⾃定义聚合规则: 默认是 key 相同的 value ⾃动聚合 1. ⾃定义类继承 WritableComparator 2. 重写⽗类⽆参构造⽅法,指定 key 的类型 super(Text.class, true); 3. 重写⽗类 compare(WritableComparable a, WritableComparable b) ⽅法,实现⾃定义聚合规则 返回 0 聚合,否则不聚合 4. 设置 job 使⽤⾃定义聚合规则 job.setGroupingComparatorClass() Java 中的排序: Java 中的所有排序都依赖 Comparable 进⾏⼤⼩⽐较的,并且都是升序排列 不能控制排序的⽅式,但是可以控制⼤⼩⽐较的结果 通过控制⼤⼩⽐较结果来影响排序结果 实现 Comparable 接⼝需要实现 compareTo ⽅法 返回值只看类型,不看数值 负数 this < o 0 this = o 正数 this > o ⾃定义排序,就是⾃定⽐较⼤⼩的规则,实现 compareTo ⽅法 正常的⼤⼩⽐较 == 升序 取反 == 降序

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值