MapReduce之WritableComparable排序

孫晨c發表於2020-07-29

@

排序概述

  • 排序是MapReduce框架中最重要的操作之一。
  • Map Task和ReduceTask均會預設對資料按照key進行排序。該操作屬於Hadoop的預設行為。任何應用程式中的資料均會被排序,而不管邏輯上是否需要
  • 黑預設排序是按照字典順序排序,且實現該排序的方法是快速排序
  • 對於MapTask,它會將處理的結果暫時放到一個緩衝區中,當緩衝區使用率達到一定閾值後,再對緩衝區中的資料進行一次排序,並將這些有序資料寫到磁碟上,而當資料處理完畢後,它會對磁碟上所有檔案進行一次合併,以將這些檔案合併成一個大的有序檔案
  • 對於ReduceTask,它從每個MapTak上遠端拷貝相應的資料檔案,如果檔案大小超過一定闌值,則放到磁碟上,否則放到記憶體中。如果磁碟上檔案數目達到一定閾值,則進行一次合併以生成一個更大檔案;如果記憶體中檔案大小或者數目超過一定閾值,則進行一次合併後將資料寫到磁碟上。當所有資料拷貝完畢後,ReduceTask統一對記憶體和磁碟上的所有資料進行一次歸併排序
  • 排序器:排序器影響的是排序的速度(效率,對什麼排序?),QuickSorter
  • 比較器:比較器影響的是排序的結果(按照什麼規則排序)

獲取Mapper輸出的key的比較器(原始碼)

public RawComparator getOutputKeyComparator() {

// 從配置中獲取mapreduce.job.output.key.comparator.class的值,必須是RawComparator型別,如果沒有配置,預設為null
    Class<? extends RawComparator> theClass = getClass(JobContext.KEY_COMPARATOR, null, RawComparator.class);

// 一旦使用者配置了此引數,例項化一個使用者自定義的比較器例項
    if (theClass != null){
      return ReflectionUtils.newInstance(theClass, this);
   }
   
//使用者沒有配置,判斷Mapper輸出的key的型別是否是WritableComparable的子類,如果不是,就拋異常,如果是,系統會自動為我們提供一個key的比較器
    return WritableComparator.get(getMapOutputKeyClass().asSubclass(WritableComparable.class), this);
  }

案例實操(區內排序)

需求
對每個手機號按照上行流量和下行流量的總和進行內部排序。

在這裡插入圖片描述
思考
因為Map Task和ReduceTask均會預設對資料按照key進行排序,所以需要把流量總和設定為Key,手機號等其他內容設定為value

FlowBeanMapper.java

public class FlowBeanMapper extends Mapper<LongWritable, Text, LongWritable, Text>{
	
	private LongWritable out_key=new LongWritable();
	private Text out_value=new Text();
	
	@Override
	protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
		String[] words = value.toString().split("\t");
		
		//封裝總流量為key
		out_key.set(Long.parseLong(words[3]));//切分後,流量和的下標為3
		
		//封裝其他內容為value
		out_value.set(words[0]+"\t"+words[1]+"\t"+words[2]);
		
		context.write(out_key, out_value);
	}

}

FlowBeanReducer.java

public class FlowBeanReducer extends Reducer<LongWritable, Text, Text, LongWritable>{
	
	@Override
	protected void reduce(LongWritable key, Iterable<Text> values,
			Reducer<LongWritable, Text, Text, LongWritable>.Context context) throws IOException, InterruptedException {
		
		for (Text value : values) {
			context.write(value, key);
		}
	}
	
}

FlowBeanDriver.java

public class FlowBeanDriver {
	
	public static void main(String[] args) throws Exception {
		
		Path inputPath=new Path("E:\\mroutput\\flowbean");
		Path outputPath=new Path("e:/mroutput/flowbeanSort1");
		
		//作為整個Job的配置
		Configuration conf = new Configuration();
		
		//保證輸出目錄不存在
		FileSystem fs=FileSystem.get(conf);
		
		if (fs.exists(outputPath)) {
			fs.delete(outputPath, true);
		}
		
		// ①建立Job
		Job job = Job.getInstance(conf);
		
		// ②設定Job
		// 設定Job執行的Mapper,Reducer型別,Mapper,Reducer輸出的key-value型別
		job.setMapperClass(FlowBeanMapper.class);
		job.setReducerClass(FlowBeanReducer.class);
		
		// Job需要根據Mapper和Reducer輸出的Key-value型別準備序列化器,通過序列化器對輸出的key-value進行序列化和反序列化
		// 如果Mapper和Reducer輸出的Key-value型別一致,直接設定Job最終的輸出型別
		//由於Mapper和Reducer輸出的Key-value型別不一致(maper輸出型別是long-text,而reducer是text-value)
		//所以需要額外設定
		job.setMapOutputKeyClass(LongWritable.class);
		job.setMapOutputValueClass(Text.class);
		
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(LongWritable.class);
		
		// 設定輸入目錄和輸出目錄
		FileInputFormat.setInputPaths(job, inputPath);
		FileOutputFormat.setOutputPath(job, outputPath);
		
		// 預設升序排,可以設定使用自定義的比較器
		//job.setSortComparatorClass(DecreasingComparator.class);
		
		// ③執行Job
		job.waitForCompletion(true);
			
	}
}

執行結果(預設升序排)
在這裡插入圖片描述

自定義排序器,使用降序

  • 方法一:自定義類,這個類必須是RawComparator型別,通過設定mapreduce.job.output.key.comparator.class自定義的類的型別。
    自定義類時,可以繼承WriableComparator類,也可以實現RawCompartor
    呼叫方法時,先呼叫RawCompartor. compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2),再呼叫RawCompartor.compare()

  • 方法二:定義Mapper輸出的key,讓key實現WritableComparable,實現CompareTo()

MyDescComparator.java

public class MyDescComparator extends WritableComparator{
	
	@Override
    public int compare(byte[] b1, int s1, int l1,byte[] b2, int s2, int l2) {
      long thisValue = readLong(b1, s1);
      long thatValue = readLong(b2, s2);
      //這裡把第一個-1改成1,把第二個1改成-1,就是降序排
      return (thisValue<thatValue ? 1 : (thisValue==thatValue ? 0 : -1));
    }

}

執行結果
在這裡插入圖片描述

Key實現Comparable進行比較

思路二:把map輸出時的key封裝為一個bean,這個key包含上行流量、下行流量、總流量,value只有手機號

FlowBean.java

public class FlowBean implements WritableComparable<FlowBean>{
	
	private long upFlow;
	private long downFlow;
	private Long sumFlow;
	
	public FlowBean() {
		
	}

	public long getUpFlow() {
		return upFlow;
	}

	public void setUpFlow(long upFlow) {
		this.upFlow = upFlow;
	}

	public long getDownFlow() {
		return downFlow;
	}

	public void setDownFlow(long downFlow) {
		this.downFlow = downFlow;
	}

	public long getSumFlow() {
		return sumFlow;
	}

	public void setSumFlow(long sumFlow) {
		this.sumFlow = sumFlow;
	}

	// 序列化   在寫出屬性時,如果為引用資料型別,屬性不能為null
	@Override
	public void write(DataOutput out) throws IOException {
		
		out.writeLong(upFlow);
		out.writeLong(downFlow);
		out.writeLong(sumFlow);
		
		
	}

	//反序列化   序列化和反序列化的順序要一致
	@Override
	public void readFields(DataInput in) throws IOException {
		upFlow=in.readLong();
		downFlow=in.readLong();
		sumFlow=in.readLong();
		
	}

	@Override
	public String toString() {
		return  upFlow + "\t" + downFlow + "\t" + sumFlow;
	}

	// 系統封裝的比較器在對比key時,呼叫key的compareTo進行比較
	// 降序比較總流量
	@Override
	public int compareTo(FlowBean o) {
		return -this.sumFlow.compareTo(o.getSumFlow());
	}
	
}

FlowBeanMapper.java

public class FlowBeanMapper extends Mapper<LongWritable, Text, FlowBean, Text>{
	
	private FlowBean out_key=new FlowBean();
	private Text out_value=new Text();
	
	
	@Override
	protected void map(LongWritable key, Text value, Context context)
			throws IOException, InterruptedException {
		
		String[] words = value.toString().split("\t");
		
		//封裝總流量為key
		out_key.setUpFlow(Long.parseLong(words[1]));
		out_key.setDownFlow(Long.parseLong(words[2]));
		out_key.setSumFlow(Long.parseLong(words[3]));
		
		out_value.set(words[0]);
		
		context.write(out_key, out_value);
	
	}

}

FlowBeanReducer.java

public class FlowBeanReducer extends Reducer<FlowBean, Text, Text, FlowBean>{
	
	@Override
	protected void reduce(FlowBean key, Iterable<Text> values,
			Reducer<FlowBean, Text, Text, FlowBean>.Context context) throws IOException, InterruptedException {
		
		for (Text value : values) {
			context.write(value, key);
		}
	}
	
}

FlowBeanDriver.java

public class FlowBeanDriver {
	
	public static void main(String[] args) throws Exception {
		
		Path inputPath=new Path("E:\\mroutput\\flowbean");
		Path outputPath=new Path("e:/mroutput/flowbeanSort2");
		
		//作為整個Job的配置
		Configuration conf = new Configuration();
		
		//保證輸出目錄不存在
		FileSystem fs=FileSystem.get(conf);
		
		if (fs.exists(outputPath)) {
			
			fs.delete(outputPath, true);
			
		}
		
		// ①建立Job
		Job job = Job.getInstance(conf);
		
		// ②設定Job
		// 設定Job執行的Mapper,Reducer型別,Mapper,Reducer輸出的key-value型別
		job.setMapperClass(FlowBeanMapper.class);
		job.setReducerClass(FlowBeanReducer.class);
		
		// Job需要根據Mapper和Reducer輸出的Key-value型別準備序列化器,通過序列化器對輸出的key-value進行序列化和反序列化
		// 如果Mapper和Reducer輸出的Key-value型別一致,直接設定Job最終的輸出型別
		
		job.setMapOutputKeyClass(FlowBean.class);
		job.setMapOutputValueClass(Text.class);
		
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(FlowBean.class);
		
		// 設定輸入目錄和輸出目錄
		FileInputFormat.setInputPaths(job, inputPath);
		FileOutputFormat.setOutputPath(job, outputPath);
		
		
		// ③執行Job
		job.waitForCompletion(true);
		
	}

}

相關文章