清明刚过,该来学习点新的知识点了。

上次说到关于MapReduce对于文本中词频的统计使用WordCount。如果还有同学不熟悉的可以参考博文大数据系列之分布式计算批处理引擎MapReduce实践

博文发表后很多同学私下反映对于MapReduce的处理原理没有了解到。在这篇博文中楼主与大家交流下MapReduce的数据处理原理及MR中各角色的职责。

文末还有示例代码讲解。。

1.MapReduce中的数据流动

  • 最简单的过程: map - reduce
  • 定制了partitioner以将map的结果送往指定reducer的过程: map - partition - reduce
  • 增加了在本地先进行一次reduce(优化)的过程: map - combine - partition - reduce

2.Partition的概念和使用

得到map产生的记录后,他们该分配给哪些reducer来处理呢?hadoop默认是根据散列值来派发,但是实际中,这并不能很高效或者按照我们要求的去执行任务。例如,经过partition处理后,一个节点的reducer分配到了20条记录,另一个却分配到了10W万条,试想,这种情况效率如何。又或者,我们想要处理后得到的文件按照一定的规律进行输出,假设有两个reducer,我们想要最终结果中part-00000中存储的是”h”开头的记录的结果,part-00001中存储其他开头的结果,这些默认的partitioner是做不到的。所以需要我们自己定制partition来选择reducer。自定义partitioner很简单,只要自定义一个类,并且继承Partitioner类,重写其getPartition方法就好了,在使用的时候通过调用Job的setPartitionerClass指定一下即可。

3.MapReduce基于key的全排序的原理

如何使用mapreduce来做全排序?最简单的方法就是使用一个partition,因为一个partition对应一个reduce的task,然而reduce的输入本来就是对key有序的,所以很自然地就产生了一个全排序文件。但是这种方法在处理大型文件时效率极低,因为一台机器必须处理所有输出文件,从而完全丧失了mapreduce所提供的并行架构的优势。

如果是分多个partition呢,则只要确保partition是有序的就行了。首先创建一系列排好序的文件;其次,串联这些文件(类似于归并排序);最后得到一个全局有序的文件。比如有1000个1-10000的数据,跑10个ruduce任务,如果进行partition的时候,能够将在1-1000中数据的分配到第一个reduce中,1001-2000的数据分配到第二个reduce中,以此类推。即第n个reduce所分配到的数据全部大于第n-1个reduce中的数据。这样,每个reduce出来之后都是有序的了,我们只要concat所有的输出文件,变成一个大的文件,就都是有序的了。

这时候可能会有一个疑问,虽然各个reduce的数据是按照区间排列好的,但是每个reduce里面的数据是乱序的啊?当然不会,不要忘了排序是MapReduce的天然特性 — 在数据达到reducer之前,mapreduce框架已经对这些数据按key排序了。

但是这里又有另外一个问题,就是在定义每个partition的边界的时候,可能会导致每个partition上分配到的记录数相差很大,这样数据最多的partition就会拖慢整个系统。我们期望的是每个partition上分配的数据量基本相同,hadoop提供了采样器帮我们预估整个边界,以使数据的分配尽量平均。

在Hadoop中,patition我们可以用TotalOrderPartitioner替换默认的分区,然后将采样的结果传给他,就可以实现我们想要的分区。在采样时,可以使用hadoop的几种采样工具,如RandomSampler,InputSampler,IntervalSampler。

关于上述过程,在《Hadoop权威指南》中有具体的讲解,其中一张图可以帮助我们更好地理解在排序操作中hadoop在map和reduce阶段所做的事:

以上文字取自网上某些博文内容。。仅供参考。。。

4.下面介绍下一个代码示例。

MapReduceExample

一组数据按照年龄分区,区内按照成绩倒序排序
#数据内容见data/person.csv,如下
#编号,姓名,年龄,性别,成绩
1,Alice,23,female,45
2,Bob,34,male,89
3,Chris,67,male,97
4,Kristine,38,female,53
5,Connor,25,male,27
6,Daniel,78,male,95
7,James,34,male,79
8,Alex,52,male,69
9,Nancy,7,female,98
10,Adam,9,male,37
11,Jacob,7,male,23
12,Mary,6,female,93
13,Clara,87,female,72
14,Monica,56,female,92
#项目要求#
1.将数据按照年龄段分区
0至20岁为第一区,
20至50岁为第二区,
50以上为第三区。
2.将各区的数据按照分数倒序排序输出
#输出结果如下#
分区1:part-r-00000
9,Nancy,female,7 98
12,Mary,female,6 93
10,Adam,male,9 37
11,Jacob,male,7 23
分区2:part-r-00001
2,Bob,male,34 89
7,James,male,34 79
4,Kristine,female,38 53
1,Alice,female,23 45
5,Connor,male,25 27
分区3:part-r-00002
3,Chris,male,67 97
6,Daniel,male,78 95
14,Monica,female,56 92
13,Clara,female,87 72
8,Alex,male,52 69

  4.1 解决思路

  描述一下MapReduce处理数据的大概简单流程:首先,MapReduce框架通过getSplit方法实现对原始文件的切片之后,每一个切片对应着一个map task,inputSplit输入到Map函数进行处理,中间结果经过环形缓冲区的排序compareTo(T),然后分区、自定义二次排序(如果有的话)和合并,再通过shuffle操作将数据传输到reduce task端,reduce端也存在着缓冲区,数据也会在缓冲区和磁盘中进行合并排序等操作,然后对数据按照Key值进行分组,然后每次处理完一个分组之后就会去调用一次reduce函数,最终输出结果。

   4.2 具体解决思路

    A.Map端处理:单行数据拆分,对于拆分后的数据按成绩分数进行排序,MapReduce框架不管是默认排序或者是自定义排序都只是对Key值进行排序,现在的情况是这些数据不是key值,怎么办?其实我们可以将原始数据的Key值和其对应的数据组合成一个新的Key值,然后新的Key值对应的还是之前的数字。那么我们就可以将原始数据的map输出变成类似下面的数据结构:

{[1,Alice,23,female], 45}
{[2,Bob,34,male], 89}
{[3,Chris,67,male], 97}
{[4,Kristine,38,female],53}
{[5,Connor,25,male],27}
{[6,Daniel,78,male],95}
{[7,James,34,male],79}
{[8,Alex,52,male],69}
{[9,Nancy,7,female],98}
{[10,Adam,9,male],37}
{[11,Jacob,7,male],23}
{[12,Mary,6,female],93}
{[13,Clara,87,female],72}
{[14,Monica,56,female],92}

   B.Partition分区操作:项目要求按照年龄进行分区,这里我们需要自定义一个分区处理器,因为我的目标不是想将所有的数据传到同一个reduce中,而是想将年龄分区后的数据放在同一个reduce中进行分组合并,所以我们需要根据新key值中的第三个字段来自定义一个分区处理器。通过分区操作后,得到的数据流如下:    

partition1: 0~20
{[9,Nancy,7,female],98}
{[10,Adam,9,male],37}
{[11,Jacob,7,male],23}
{[12,Mary,6,female],93} partition2:20~50
{[1,Alice,23,female], 45}
{[2,Bob,34,male], 89}
{[4,Kristine,38,female],53}
{[5,Connor,25,male],27}
{[7,James,34,male],79} partition3:50~
{[3,Chris,67,male], 97}
{[6,Daniel,78,male],95}
{[8,Alex,52,male],69}
{[13,Clara,87,female],72}
{[14,Monica,56,female],92}

  C.自定义排序操作:分区操作完成之后,我调用自己的自定义排序器对新的Key值按照成绩分数进行排序。 排序后的数据流结果如下:

partition1: 0~20
{[9,Nancy,7,female],98}
{[12,Mary,6,female],93}
{[10,Adam,9,male],37}
{[11,Jacob,7,male],23} partition2:20~50
{[2,Bob,34,male], 89}
{[7,James,34,male],79}
{[4,Kristine,38,female],53}
{[1,Alice,23,female], 45}
{[5,Connor,25,male],27} partition3:50~
{[3,Chris,67,male], 97}
{[6,Daniel,78,male],95}
{[14,Monica,56,female],92}
{[13,Clara,87,female],72}
{[8,Alex,52,male],69}

  D.Reducer操作:经过Shuffle处理之后,数据传输到Reducer端输出。

  4.2 代码

   A.代码结构如下

  pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.</modelVersion> <groupId>mapReduceDemo</groupId>
<artifactId>mapReduceDemo</artifactId>
<version>1.0-SNAPSHOT</version> <repositories>
<repository>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</repository>
</repositories> <dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-yarn-client</artifactId>
<version>2.7.</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-jobclient</artifactId>
<version>2.7.</version>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<excludeScope>provided</excludeScope>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

  B.Main.java  入口

package com.m.mr;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
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;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner; /**
* 操作person.csv文件
*/
public class Main extends Configured implements Tool { public int run(String[] args) throws Exception {
if (args.length != ) {
System.err.println("Usage: AgePartition <input> <output>");
ToolRunner.printGenericCommandUsage(System.out);
System.exit();
} Configuration conf = getConf();
//conf.set(RegexMapper.GROUP,"female");
Job job = Job.getInstance(conf); FileInputFormat.addInputPath(job, new Path(args[]));
Path output = new Path(args[]);
FileSystem fs = FileSystem.get(conf);
if (fs.exists(output)) {
fs.delete(output, true);
}
FileOutputFormat.setOutputPath(job, output);
job.setJarByClass(Main.class);
job.setMapperClass(DefinedMap.class);
//设置map的输出key和value类型
job.setMapOutputKeyClass(DefinedCombinationKey.class);
job.setMapOutputValueClass(IntWritable.class);
job.setReducerClass(DefinedReducer.class);
//设置reduce的输出key和value类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//自定义分区策略
job.setPartitionerClass(DefinedPartitioner.class);
//自定义排序策略,在自定义组合键重写方法compareTo时若自定义排序策略与之相同可以省略自定义排序策略。最终结果以自定义排序策略为主
job.setSortComparatorClass(DefinedSort.class);
job.setOutputFormatClass(TextOutputFormat.class);
job.setNumReduceTasks();//reducer num = partition num return job.waitForCompletion(true) ? : ;
} public static void main(String[] args) throws Exception {
int res = ToolRunner.run(new Configuration(), new Main(), args);
System.exit(res);
}
}

  C. DefinedMap.java  {自定义分区器 class DefinedPartitioner,class Map, class Reducer}

package com.m.mr;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Partitioner;
import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException;
import java.util.Iterator; /**
* @author mengfanzhu
* @Package com.m.mr
* @Description: 自定义 map处理
* @date 17/4/7 14:04
*/
public class DefinedMap extends Mapper<Object, Text, DefinedCombinationKey, IntWritable> {
DefinedCombinationKey combinationKey=new DefinedCombinationKey();
Text sortName = new Text();
IntWritable score = new IntWritable(); @Override
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
// no,name, age, gender, score
String[] arr = value.toString().split(",");
score.set(Integer.parseInt(arr[]));
sortName.set(arr[]+","+arr[]+","+arr[]+","+arr[]);
combinationKey.setFirstKey(sortName);
combinationKey.setSecondKey(score);
context.write(combinationKey, score);
}
} /**
* 自定义分区 按照年龄段分区
*/
class DefinedPartitioner extends Partitioner<DefinedCombinationKey,IntWritable> {
@Override
public int getPartition(DefinedCombinationKey key, IntWritable value, int n) {
if (n == ) {
return ;
}
String[] arr = key.getFirstKey().toString().split(",");
int age = Integer.parseInt(arr[]);
if (age <= ) {
return ;
} else if (age <= ) {
return % n;
} else {
return % n;
}
}
}
/**
* 自定义reducer处理
*/
class DefinedReducer extends Reducer<DefinedCombinationKey, IntWritable, Text, Text> { StringBuffer sb=new StringBuffer();
Text sore=new Text(); @Override
protected void reduce(DefinedCombinationKey key, Iterable<IntWritable> values, Context context)
throws IOException, InterruptedException {
sb.delete(, sb.length());
Iterator<IntWritable> it=values.iterator(); while (it.hasNext()) {
sb.append(it.next()+",");
} if (sb.length()>) {
sb.deleteCharAt(sb.length()-);
}
sore.set(sb.toString());
context.write(key.getFirstKey(),sore);
}
}

    D . 自定义二次排序策略 DefinedSort

package com.m.mr;

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* @author mengfanzhu
* @Description: 自定义排序策略
* @date 17/4/7 13:04
*/
public class DefinedSort extends WritableComparator {
private static final Logger logger = LoggerFactory.getLogger(DefinedSort.class);
public DefinedSort() {
super(DefinedCombinationKey.class,true);
}
@Override
public int compare(WritableComparable combinationKeyOne,
WritableComparable CombinationKeyOther) {
logger.info("---------enter DefinedComparator flag---------"); DefinedCombinationKey c1 = (DefinedCombinationKey) combinationKeyOne;
DefinedCombinationKey c2 = (DefinedCombinationKey) CombinationKeyOther; logger.info("---------out DefinedComparator flag---------");
return c2.getSecondKey().get()-c1.getSecondKey().get();//0,负数,正数
}
}

  E.  DefinedCombinationKey.java 自定义组合键

package com.m.mr;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException; /**
* @author mengfanzhu
* @Description:
* @date 17/4/7 13:01
*/
public class DefinedCombinationKey implements WritableComparable<DefinedCombinationKey> {
private static final Logger logger = LoggerFactory.getLogger(DefinedCombinationKey.class);
private Text firstKey;
private IntWritable secondKey;
public DefinedCombinationKey() {
this.firstKey = new Text();
this.secondKey = new IntWritable();
}
public Text getFirstKey() {
return this.firstKey;
}
public void setFirstKey(Text firstKey) {
this.firstKey = firstKey;
}
public IntWritable getSecondKey() {
return this.secondKey;
}
public void setSecondKey(IntWritable secondKey) {
this.secondKey = secondKey;
}
public void readFields(DataInput dateInput) throws IOException {
// TODO Auto-generated method stub
this.firstKey.readFields(dateInput);
this.secondKey.readFields(dateInput);
}
public void write(DataOutput outPut) throws IOException {
this.firstKey.write(outPut);
this.secondKey.write(outPut);
}
/**
* 自定义比较策略
* 注意:该比较策略用于mapreduce的第一次默认排序,也就是发生在map阶段的sort小阶段,
* 发生地点为环形缓冲区(可以通过io.sort.mb进行大小调整)
*/
public int compareTo(DefinedCombinationKey definedCombinationKey) {
logger.info("-------CombinationKey flag-------");
return this.secondKey.compareTo(definedCombinationKey.getSecondKey()); } }

说明:
1.在自定义组合键的时候,我们需要特别注意,一定要实现WritableComparable接口,并且实现compareTo方法的比较策略。这个用于mapreduce的第一次默认排序,也就是发生在map阶段的sort小阶段,发生地点为环形缓冲区(可以通过io.sort.mb进行大小调整),但是其对我们最终的二次排序结果是没有影响的。我们二次排序的最终结果是由我们的自定义比较器决定的。  
2.在此示例代码中写了自定义组合键的compareTo对于score进行正序,在自定义比较器中对score进行倒序。用来分析MR的工作原理。

 

 F.打包运行。maven :mvn clean package

 hadoop jar mapReduceDemo-1.0-SNAPSHOT.jar  com.m.mr.Main /person.csv /out     

完~

数据及代码包见

代码示例已上传至GitHub,https://github.com/fzmeng/MapReduceExample

大数据系列之分布式计算批处理引擎MapReduce实践-排序的更多相关文章

  1. 大数据系列之分布式计算批处理引擎MapReduce实践

    关于MR的工作原理不做过多叙述,本文将对MapReduce的实例WordCount(单词计数程序)做实践,从而理解MapReduce的工作机制. WordCount: 1.应用场景,在大量文件中存储了 ...

  2. 大数据系列4:Yarn以及MapReduce 2

    系列文章: 大数据系列:一文初识Hdfs 大数据系列2:Hdfs的读写操作 大数据谢列3:Hdfs的HA实现 通过前文,我们对Hdfs的已经有了一定的了解,本文将继续之前的内容,介绍Yarn与Yarn ...

  3. 大数据系列之数据仓库Hive命令使用及JDBC连接

    Hive系列博文,持续更新~~~ 大数据系列之数据仓库Hive原理 大数据系列之数据仓库Hive安装 大数据系列之数据仓库Hive中分区Partition如何使用 大数据系列之数据仓库Hive命令使用 ...

  4. 大数据系列之并行计算引擎Spark介绍

    相关博文:大数据系列之并行计算引擎Spark部署及应用 Spark: Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎. Spark是UC Berkeley AMP lab ( ...

  5. 大数据系列之并行计算引擎Spark部署及应用

    相关博文: 大数据系列之并行计算引擎Spark介绍 之前介绍过关于Spark的程序运行模式有三种: 1.Local模式: 2.standalone(独立模式) 3.Yarn/mesos模式 本文将介绍 ...

  6. 批处理引擎MapReduce编程模型

    批处理引擎MapReduce编程模型 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. MapReduce是一个经典的分布式批处理计算引擎,被广泛应用于搜索引擎索引构建,大规模数据处理 ...

  7. 批处理引擎MapReduce内部原理

    批处理引擎MapReduce内部原理 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MapReduce作业生命周期 MapReduce作业作为一种分布式应用程序,可直接运行在H ...

  8. 大数据系列之数据仓库Hive原理

    Hive系列博文,持续更新~~~ 大数据系列之数据仓库Hive原理 大数据系列之数据仓库Hive安装 大数据系列之数据仓库Hive中分区Partition如何使用 大数据系列之数据仓库Hive命令使用 ...

  9. 批处理引擎MapReduce应用案例

    批处理引擎MapReduce应用案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. MapReduce能够解决的问题有一个共同特点:任务可以被分解为多个子问题,且这些子问题相对独立 ...

随机推荐

  1. android开发心得之知识的量变到质变

    随着身边越来越多的人开始了尝试android开发,看着他们一点点学期 从nodepad++写代码 cmd 执行,到安装eclipse 和android SDK,仿佛看到了昨天的我一样,一样勤勤恳恳的学 ...

  2. Shell命令——文件目录

    Linux只有一个文件系统树,不同的硬件设备可以挂载在不同目录下. 文件或目录有两种表示方式:  - 绝对路径:从根目录”/”开始  - 相对路径:从工作目录开始,使用”..”指向父目录,”.”指向当 ...

  3. Nginx upstream 配置

    1.轮询(默认)每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除. 2.weight指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况.例如:u ...

  4. BZOJ2729 HNOI2012排队(组合数学+高精度)

    组合入门题.高精度入门题. #include<iostream> #include<cstdio> #include<cstdlib> #include<cs ...

  5. css后代选择器 .属性 元素 与 元素.属性的区别

    经常看见css的后代选择器是这样的写法: div.class   和 .class div 的形式两者的区别: div.class  是选中的类名为class 的div元素,与直接使用类选择器.cla ...

  6. 如何解决每次打开office 都会出现正在配置的问题

    原因:安装offiece的时候直接选择以前安装过的office文件夹,导致文件冲突 解决方法:卸载,然后对准备要安装的文件夹清空或者重新建个新文件夹安装

  7. 洛谷P4234 最小差值生成树(LCT,生成树)

    洛谷题目传送门 和魔法森林有点像,都是动态维护最小生成树(可参考一下Blog的LCT总结相关部分) 至于从小到大还是从大到小当然无所谓啦,我是从小到大排序,每次枚举边,还没连通就连,已连通就替换环上最 ...

  8. Libre 6003 「网络流 24 题」魔术球 (网络流,最大流)

    Libre 6003 「网络流 24 题」魔术球 (网络流,最大流) Description 假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为 1,2,3,4......的球. (1)每次只 ...

  9. POJ 1062 昂贵的聘礼(图论,最短路径)

    POJ 1062 昂贵的聘礼(图论,最短路径) Description 年轻的探险家来到了一个印第安部落里.在那里他和酋长的女儿相爱了,于是便向酋长去求亲.酋长要他用10000个金币作为聘礼才答应把女 ...

  10. debian8.4 ibus中文输入法

    安装IBus: # apt-get install ibus ibus-sunpinyin ibus-table-wubi 导入输入法: 在Activities->Applications-&g ...