Hadoop学习之路(四):Hadoop排序之全排序的原理及实现

Hadoop实现全排序

    • 一、全排序简介
    • 二、全排序的原理
    • 三、准备数据
    • 四、全排序的实现
        • 1.创建Java工程,添加Maven支持
        • 2.编写Map类
        • 3.编写Reduce类
        • 4.编写作业主类
        • 5.将代码打包提交到集群
        • 6.运行程序
    • 五、总结

一、全排序简介

全排序其实就是全局排序,就是使得所有数据按序排列输出,和我们平常做的给一个数组排序没有什么区别,唯一的区别就是数据量的不同,这里涉及的数据量是TB级别的,这就意味着不可能简单地把数据加载进内存进行排序,需要用到分布式计算,所以就产生了Hadoop的全排序,Hadoop的全排序在实际应用有着重要的作用。

二、全排序的原理

其实,实现Hadoop全排序有着一个很简单的方法,那就是只使用一个Reducer,因为Hadoop默认对Key升序排序,所以当只使用一个Reducer时,所有数据都会落在一台主机上,从而达到全排序的目的,但是这就失去了分布式的意义,造成了数据倾斜。为了充分使用集群资源,我们把Reducer设置为多个进行全排序,比如3个,如图:
Hadoop学习之路(四):Hadoop排序之全排序的原理及实现_第1张图片
每一个Reducer内的数据是有序的,但是Reducer与Reducer之间的数据是无序的,所以最后的结果不满足全排序的要求,这是因为数据是根据哈希值进入不同分区的,是随机的,为此我们可以重写分区类,使得数据有条件地进入各个分区,比如还是3个Reducer,划分三个条件,使得Reducer与Reducer之间的数据有序:
Hadoop学习之路(四):Hadoop排序之全排序的原理及实现_第2张图片
这是个办法,Reducer内的数据大小:Reducer1 < Reducer2 < Reducer3,把最后输出的三个文件按序整合就是全排序的结果了,但还存在一个问题:可能造成数据倾斜,这是因为划分分区区间的时候,无法估计区间内的数据量,假设小于1000的数据有500G,其他两个期间的数据加起有100G,这时候就会有热点问题,这是因为区间是人为划分的,无法估计各个区间的数据量

综合以上,Hadoop的全排序原理就是:Hadoop事先会对待排序的数据进行抽样,这就要求输入的Key必需具有可比较性,然后根据Reducer的个数科学地指定分区的条件,最后再进行运算

三、准备数据

数据的Key必须具备可比性才可以被Hadoop抽样,因而本次演示使用序列文件(SequenceFile),序列文件本质上是K-V对,结合本次演示,让Key为整数,Value为null即可,以下给出产生序列文件的代码 NumberProducer

public class NumberProducer {

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        //将Hadoop文件系统改为本地文件系统
        conf.set("fs.defaultFS", "file:///");
        FileSystem fs = FileSystem.get(conf);
        //序列文件存放的本地路径
        Path path = new Path("C:\\Users\\seq\\number.seq");
        //设置KV对为(int,null)
        SequenceFile.Writer writer = SequenceFile.createWriter(fs, conf, path, IntWritable.class, NullWritable.class);
        Random random = new Random();
        int number = 0;
        //一共产生5000个整数
        int count = 5000;
        for (int i = 1; i <= count; i++) {
            number = random.nextInt(10000) - 2000;
            //将整型数写入序列文件
            writer.append(new IntWritable(number),NullWritable.get());
        }
        //一定不可以漏
        writer.close();
    }
}

产生的序列文件number.seq为二进制文件,不可以直接查看,将其上传至HDFS,然后使用命令查看number.seq:hdfs dfs -text + 存放number.seq的HDFS路径,可以看到:
Hadoop学习之路(四):Hadoop排序之全排序的原理及实现_第3张图片

四、全排序的实现

本次演示使用的Hadoop版本是:2.6.0-cdh5.7.0
开发工具是IDEA2018

1.创建Java工程,添加Maven支持

完整的依赖如下:

<properties>
    <hadoop.version>2.6.0-cdh5.7.0</hadoop.version>
</properties>

  <repositories>
    <repository>
      <id>cloudera</id>
      <url>https://repository.cloudera.com/artifactory/cloudera-repos</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>org.apache.hadoop</groupId>
      <artifactId>hadoop-client</artifactId>
      <version>${hadoop.version}</version>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.7.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.20.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

2.编写Map类

Map类完整代码如下:

/**
 * 全排序Map类,直接接数据取出传递给Reducer即可
 */
public class Map extends Mapper<IntWritable, NullWritable, IntWritable, NullWritable> {

    @Override
    protected void map(IntWritable key, NullWritable value, Context context) throws IOException, InterruptedException {
        context.write(key,value);
    }
}

3.编写Reduce类

/**
 * 全排序Reducer类
 * 因为本次演示只是简单的排序,没有其他业务
 * 所以只需将数据输出即可
 */
public class Reduce extends Reducer<IntWritable, NullWritable,IntWritable,NullWritable> {

    @Override
    protected void reduce(IntWritable key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
       //获得values的迭代器
       Iterator<NullWritable> it =  values.iterator();
       //将数据输出即可
       while (it.hasNext()){
           context.write(key,it.next());
       }
    }
}

4.编写作业主类

public class AllSortApp {

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf,"AllSortApp");

        //文本输入格式
        job.setInputFormatClass(SequenceFileInputFormat.class);
        //设置作业主类
        job.setJarByClass(AllSortApp.class);
        //待排序数据的输入路径
        FileInputFormat.setInputPaths(job,new Path(args[0]));
        //排序结果存放路径
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        //设置Reducer的个数
        job.setNumReduceTasks(3);

        //设置Map类
        job.setMapperClass(Map.class);
        //设置Reducer类
        job.setReducerClass(Reduce.class);

        //设置Map类Key的输出类型
        job.setMapOutputKeyClass(IntWritable.class);
        //设置Map类Value的输出类型
        job.setMapOutputValueClass(NullWritable.class);
        //设置Reducer类Key的输出类型
        job.setOutputKeyClass(IntWritable.class);
        //设置Reducer类Value的输出类型
        job.setOutputValueClass(NullWritable.class);

        //创建随机采样器
        //freq:采样率
        //numSamples:样本总数
        //maxSplitsSampled:最大采样切片数
        InputSampler.Sampler sampler = new InputSampler.RandomSampler<IntWritable,NullWritable>
                (0.8,1000,3);

        //设置存放分区文件的HDFS路径
        TotalOrderPartitioner.setPartitionFile(job.getConfiguration(),new Path("/data/tmp/par.list"));

        //设置全排序分区类:TotalOrderPartitioner
        job.setPartitionerClass(TotalOrderPartitioner.class);

        //写入分区文件
        InputSampler.writePartitionFile(job,sampler);

        //等待执行
         job.waitForCompletion(true);
    }
}

5.将代码打包提交到集群

Hadoop学习之路(四):Hadoop排序之全排序的原理及实现_第4张图片
在该工程的目录下的target文件夹下有生成的jar包,把jar包提交到集群。

6.运行程序

执行命令:hadoop jar allsortapp1.0.0.jar com.hadoop.allsort.AllSortApp hdfs://hadoop00:/data/number.seq hdfs://hadoop00:/data/out,后面两个路径分别是序列文件路径和结果输出路径。作业结束后,我们先查看Hadoop抽样生成的分区文件(路径已在作业主类中设置):
使用命令:hdfs dfs -text /data/tmp/par.list
在这里插入图片描述
可以得出Hadoop将三个分区定为 x < 1237、1237 <= x < 4524 、 x >= 4524 。排序结果在三个文件中(一个Reducer输出一个文件),查看排序结果:hdfs dfs -cat /data/out/part-r-0000*
Hadoop学习之路(四):Hadoop排序之全排序的原理及实现_第5张图片
至此,全排序结束。

五、总结

本次演示了Hadoop的全排序,以及Hadoop全排序的原理和实现,其本质就是Hadoop事先会对待排序的数据进行抽样,然后根据Reducer的个数科学地指定分区的条件。其实除了本次演示所用的随机抽样器外,还有切片抽样、间隔抽样,随机抽样使用的最多。我是人间,感谢你的阅读!

你可能感兴趣的:(Hadoop生态,大数据生态)