[MapReduce_7] MapReduce 中的排序


 

0. 说明

  部分排序 && 全排序 && 采样 && 二次排序

 

 

 


1. 介绍 

 

  sort 是根据 Key 进行排序

 

  【部分排序】

  在每个分区中,分别进行排序,默认排序即部分排序

 

  【全排序】

  在所有的分区中,整体有序

 

  实现全排序的方案:

  1. 使用一个 reduce

  2. 自定义分区函数

  3. 采样

    【3.1 随机采样】

    对于纯文本数据支持不友好
    0. 纯文本建议使用 KeyValueTextInputFormat
    1. 设置分区类 TotalOrderPartition(MR中存在此类 )
    2. 初始化采样器 (RandomSampler) => InputSampler.RandomSampler<Text,Text> sampler = new InputSampler.RandomSampler<Text,Text>(0.01,10);
    3. 设置采样数据地址 => TotalOrderPartitioner.setPartitionFile(job.getConfiguration(),new Path("E:/test/wc/out3"));
    4. 写入采样数据 => InputSampler.writePartitionFile(job,sampler);
    5. 注意1-4步必须写在配置文件之后,job 执行之前

    // new InputSampler.RandomSampler<Text,Text>(0.01,10);
    // 0.01(freq) 每个 Key 被选中的概率
    // 对于每个key都会产生一个0-1之间的浮点数,小于此浮点数的key会被选中
    // 10(numSamples) 样本个数
    // 定义一个10长度的数组,被选中的 Key 回到此数组中
    // 最终从数组中随机选择2个样本

 

 

    【3.2 切片采样】

 

    对每个数据切片取前n个值
    first numSamples / numSplits
    10 / 3

 

 

    【3.3 间隔采样】

    每隔一段间隔采样数据 => new InputSampler.IntervalSampler<Text,Text>(0.01);
    对于每个切片样本,当保留的记录数与总记录计数之比小于指定频率时发出

 

 

  【二次排序】

  在对 Key 进行排序的基础上,对 Value 进行排序
  1. 重写组合 Key (Compkey) Year+Temp               //在对key进行排序的基础上,对 Value 进行排序
  2. 重写分组对比器,使得在 year 相等的情况下则证明 Compkey 相等  //GroupComparator

  流程图如下

  

 

 

 


 

2. 全排序(自定义分区函数)

  [2.1 PassMapper.java]

package hadoop.mr.sort.total;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/**
 * mapper 类
 * 对原始数据进行预处理
 */
public class PassMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        // 将 value 变为 String 格式
        String line = value.toString();

        // 将一行文本进行截串
        String[] arr = line.split("\t");

        // 过滤不符合规范的数据
        if (arr.length >= 3) {

            String pass = arr[2];
            if (pass != null) {
                context.write(new Text(pass), new IntWritable(1));
            }
        }
    }
}

 

  [2.2 PassReducer.java]

package hadoop.mr.sort.total;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class PassReducer extends Reducer<Text,IntWritable,Text,IntWritable> {

    /**
     * 通过迭代所有的key进行聚合
     */
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {

        int sum = 0;
        for(IntWritable value : values){
            sum += value.get();
        }
        context.write(key,new IntWritable(sum));
    }
}

 

  [2.3 PassPartition.java]

package hadoop.mr.sort.total;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;


/**
 * 自定义分区实现全排序
 */
public class PassPartition extends Partitioner<Text, IntWritable> {
    @Override
    public int getPartition(Text text, IntWritable intWritable, int numPartitions) {

        String key = text.toString();
        if (key.compareTo("9") < 0) {
            return 0;
        }
        if (key.compareTo("f") < 0) {
            return 1;
        }
        else return 2;

    }
}

 

  [2.4 PassApp.java]

package hadoop.mr.sort.total;

import org.apache.hadoop.conf.Configuration;
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;

/**
 * 对密码进行全排序
 * 通过自定义分区实现全排序
 */
public class PassApp {
    public static void main(String[] args) throws Exception {
        // 初始化配置文件
        Configuration conf = new Configuration();

        // 仅在本地开发时使用
        conf.set("fs.defaultFS", "file:///");

        // 初始化文件系统
        FileSystem fs = FileSystem.get(conf);

        // 通过配置文件初始化 job
        Job job = Job.getInstance(conf);

        // 设置 job 名称
        job.setJobName("pass count");

        // job 入口函数类
        job.setJarByClass(PassApp.class);

        // 设置 mapper 类
        job.setMapperClass(PassMapper.class);

        // 设置 reducer 类
        job.setReducerClass(PassReducer.class);

        // 设置 partition 类
        job.setPartitionerClass(PassPartition.class);

        // 设置 combiner 类
//        job.setCombinerClass(PassReducer.class);

        // 设置分区数量
        job.setNumReduceTasks(3);

        // 设置 map 的输出 K-V 类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        // 设置 reduce 的输出 K-V 类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 设置输入路径和输出路径
        Path pin = new Path("E:/file/duowan_user.txt");
        Path pout = new Path("E:/test/wc/out");
//        Path pin = new Path(args[0]);
//        Path pout = new Path(args[1]);
        FileInputFormat.addInputPath(job, pin);
        FileOutputFormat.setOutputPath(job, pout);

        // 判断输出路径是否已经存在,若存在则删除
        if (fs.exists(pout)) {
            fs.delete(pout, true);
        }

        // 执行 job
        job.waitForCompletion(true);


    }
}

 


 

3. 采样 (随机采样、切片采样、间隔采样)

  [3.1 PassMapper.java]

package hadoop.mr.sort.sampling;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/**
 * mapper 类
 * 对原始数据进行预处理
 */
public class PassMapper extends Mapper<Text, Text, Text, IntWritable> {
    @Override
    protected void map(Text key, Text value, Context context) throws IOException, InterruptedException {

        context.write(key, new IntWritable(Integer.parseInt(value.toString())));

    }
}

 

  [3.2 PassReducer.java]

package hadoop.mr.sort.sampling;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class PassReducer extends Reducer<Text,IntWritable,Text,IntWritable> {

    /**
     * 通过迭代所有的key进行聚合
     */
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {

        int sum = 0;
        for(IntWritable value : values){
            sum += value.get();
        }
        context.write(key,new IntWritable(sum));
    }
}

 

  [3.3 PassApp.java]

package hadoop.mr.sort.sampling;

import org.apache.hadoop.conf.Configuration;
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.input.KeyValueTextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.partition.InputSampler;
import org.apache.hadoop.mapreduce.lib.partition.TotalOrderPartitioner;

/**
 * 对密码进行全排序
 * 通过自定义分区实现全排序
 * <p>
 * 先通过部分排序得到数据
 * 将输入路径指向部分排序结果输出路径
 */
public class PassApp {
    public static void main(String[] args) throws Exception {
        // 初始化配置文件
        Configuration conf = new Configuration();

        // 仅在本地开发时使用
        conf.set("fs.defaultFS", "file:///");

        // 初始化文件系统
        FileSystem fs = FileSystem.get(conf);

        // 通过配置文件初始化 job
        Job job = Job.getInstance(conf);

        // 设置 job 名称
        job.setJobName("pass count");

        // job 入口函数类
        job.setJarByClass(PassApp.class);

        // 设置 mapper 类
        job.setMapperClass(PassMapper.class);

        // 设置 reducer 类
        job.setReducerClass(PassReducer.class);

        // 设置 combiner 类
//        job.setCombinerClass(PassReducer.class);

        // 设置全排序采样类 TotalOrderPartitioner.class
        job.setPartitionerClass(TotalOrderPartitioner.class);

        // 设置分区数量
        job.setNumReduceTasks(3);

        // 设置 map 的输出 K-V 类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        // 设置 reduce 的输出 K-V 类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 设置输入格式
        job.setInputFormatClass(KeyValueTextInputFormat.class);

        // 设置输入路径和输出路径
        Path pin = new Path("E:/test/wc/out");
        Path pout = new Path("E:/test/wc/out2");
//        Path pin = new Path(args[0]);
//        Path pout = new Path(args[1]);
        FileInputFormat.addInputPath(job, pin);
        FileOutputFormat.setOutputPath(job, pout);

        /**
         * 随机采样,比较浪费性能,耗费资源
         * @param freq 每个key被选择的概率 ,大于采样数(2) / 所有key数量(n)
         * @param numSamples 所有切片中需要选择的key数量
         */
        // 设置采样器类型,随机采样
//        InputSampler.RandomSampler<Text, Text> sampler = new InputSampler.RandomSampler<Text, Text>(0.01, 10);

        // 设置采样器类型,切片采样,对有序的数据不友好
//        InputSampler.SplitSampler<Text, Text> sampler = new InputSampler.SplitSampler<Text, Text>(10, 3);

        // 设置采样器类型,间隔采样
        InputSampler.IntervalSampler<Text,Text> sampler = new InputSampler.IntervalSampler<Text, Text>(0.01,3);

        // 设置采样数据地址
        TotalOrderPartitioner.setPartitionFile(job.getConfiguration(), new Path("E:/test/wc/out3"));

        // 写入采样数据
        InputSampler.writePartitionFile(job, sampler);

        // 判断输出路径是否已经存在,若存在则删除
        if (fs.exists(pout)) {
            fs.delete(pout, true);
        }

        // 执行 job
        job.waitForCompletion(true);

    }
}

 

 

 


 

4. 二次排序

  [4.1 CompKey.java]

package hadoop.mr.sort.secondary;

import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

/**
 * 组合 Key , 包含自定义的排序规则 && 序列反序列化
 */
public class CompKey implements WritableComparable<CompKey> {

    private String year;
    private int temp;

    // 定义排序规则
    public int compareTo(CompKey o) {
        String oyear = o.getYear();
        String tyear = this.getYear();
        int otemp = o.getTemp();
        int ttemp = this.getTemp();

        // 如果传入的参数 year 和本身的 year 相同,则比较温度
        if (oyear.equals(tyear)) {
            return otemp - ttemp;
        }
        // 年份不同,则返回两个 year 的比较值
        return oyear.compareTo(tyear);
    }

    // 串行化
    public void write(DataOutput out) throws IOException {
        out.writeUTF(year);
        out.writeInt(temp);
    }

    // 反串行化
    public void readFields(DataInput in) throws IOException {
        this.setYear(in.readUTF());
        this.setTemp(in.readInt());
    }

    @Override
    public String toString() {
        return "CompKey{" +
                "year='" + year + '\'' +
                ", temp=" + temp +
                '}';
    }

    public CompKey() {
    }

    public CompKey(String year, int temp) {
        this.year = year;
        this.temp = temp;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public int getTemp() {
        return temp;
    }

    public void setTemp(int temp) {
        this.temp = temp;
    }
}

 

  [4.2 MyHashPartition.java]

package hadoop.mr.sort.secondary;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Partitioner;

/**
 * 自定义 hash 分区
 */
public class MyHashPartition extends Partitioner<CompKey, NullWritable> {
    public int getPartition(CompKey compKey, NullWritable nullWritable, int numPartitions) {
        String year = compKey.getYear();

        return (year.hashCode() & Integer.MAX_VALUE) % numPartitions;
    }
}

 

  [4.3 SortMapper.java]

package hadoop.mr.sort.secondary;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/**
 * Mapper 程序
 */
public class SortMapper extends Mapper<LongWritable, Text, CompKey, NullWritable> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        String[] arr = value.toString().split("\t");
        String year = arr[0];
        int temp = Integer.parseInt(arr[1]);

        CompKey ck = new CompKey(year, temp);
        context.write(ck, NullWritable.get());
    }
}

 

  [4.4 SortReducer.java]

package hadoop.mr.sort.secondary;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

/**
 * Reducer 程序
 */
public class SortReducer extends Reducer<CompKey, NullWritable, Text, IntWritable> {
    @Override
    protected void reduce(CompKey key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {

        for (NullWritable value : values) {
            String year = key.getYear();
            int temp = key.getTemp();

            context.write(new Text(year), new IntWritable(temp));
        }
    }
}

 

  [4.5 MyGroupComparator.java]

package hadoop.mr.sort.secondary;

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;

/**
 * 分组对比器,自定义 key 业务逻辑,将 1902 20  1902 30 识别为同一个 key
 */
public class MyGroupComparator extends WritableComparator {

    // 必须写,创建实例必须写 true
    protected MyGroupComparator() {
        super(CompKey.class, true);
    }

    // 比较算法,只要 year 相等则证明 key 相等
    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        CompKey ck1 = (CompKey) a;
        CompKey ck2 = (CompKey) b;

        return ck1.getYear().compareTo(ck2.getYear());
    }
}

 

  [4.6 SortApp.java]

package hadoop.mr.sort.secondary;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
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;

/**
 * 二次排序 App
 */
public class SortApp {
    public static void main(String[] args) throws Exception {
        // 初始化配置文件
        Configuration conf = new Configuration();

        // 仅在本地开发时使用
        conf.set("fs.defaultFS", "file:///");

        // 初始化文件系统
        FileSystem fs = FileSystem.get(conf);

        // 通过配置文件初始化 job
        Job job = Job.getInstance(conf);

        // 设置 job 名称
        job.setJobName("Secondary Sort");

        // job 入口函数类
        job.setJarByClass(SortApp.class);

        // 设置 mapper 类
        job.setMapperClass(SortMapper.class);

        // 设置 reducer 类
        job.setReducerClass(SortReducer.class);

        // 设置自定义分区
        job.setPartitionerClass(MyHashPartition.class);

        // 设置分区数量
        job.setNumReduceTasks(3);

        // 设置分组对比器
        job.setGroupingComparatorClass(MyGroupComparator.class);

        // 设置 map 的输出 K-V 类型
        job.setMapOutputKeyClass(CompKey.class);
        job.setMapOutputValueClass(NullWritable.class);

        // 设置 reduce 的输出 K-V 类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 新建输入输出路径
        Path pin = new Path("E:/file/kv.txt");
        Path pout = new Path("E:/test/wc/out");

        // 打包后自定义输入输出路径
//        Path pin = new Path(args[0]);
//        Path pout = new Path(args[1]);

        // 设置输入路径和输出路径
        FileInputFormat.addInputPath(job, pin);
        FileOutputFormat.setOutputPath(job, pout);

        // 判断输出路径是否已经存在,若存在则删除
        if (fs.exists(pout)) {
            fs.delete(pout, true);
        }

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

 

 

 


 

posted @ 2018-11-09 10:46  山间一棵松  阅读(268)  评论(0编辑  收藏  举报