Hadoop自定义排序、分区

实际中往往我们规定一种排序方法,并且为了避免数据倾斜情况,需要我们自定义分区。这里我们讨论将一个城市四年来的温度按年份升序排序,同一年份的温度按照降序排序。

  • 自定义排序
    • 定义一个封装对象
    • 定义排序方法
  • 自定义分区
    • 自定义分区
    • 自定义分组
  • 主函数
  • 分区与分组

自定义排序

定义一个封装对象

该对象需要实现WritableComparable接口

public class MyClass implements WritableComparable<MyClass>{
	private int year;
	private int temperature;
	public int getYear() {
		return year;
	}
	public void setYear(int year) {
		this.year = year;
	}
	public int getTemperature() {
		return temperature;
	}
	public void setHot(int temperature) {
		this.temperature = temperature;
	}
	@Override
	//反序列化过程
	public void readFields(DataInput in) throws IOException {
		this.year=in.readInt();
		this.temperature=in.readInt();
	}
	@Override
	//序列化过程
	public void write(DataOutput out) throws IOException {
		out.writeInt(year);
		out.writeInt(temperature);
	}
	@Override
	public int compareTo(MyClass o) {
		int result=Integer.compare(year, o.getYear());
		if(result!=0) {
			return result;
		}
		return Integer.compare(temperature, o.getTemperature());
	}
	@Override
	public String toString() {
		return year+"-"+temperature;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		long temp;
		temp = Double.doubleToLongBits(temperature);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		result = prime * result + year;
		return result;
	}
}

定义排序方法

需要继承WritableComparator父类

public class MySort extends WritableComparator{
	public MySort() {
		super(MyClass.class,true);
	}
	public int compare(WritableComparable a,WritableComparable b) {
		MyClass o1=(MyClass) a;
		MyClass o2=(MyClass) b;
		int result=Integer.compare(o1.getYear(), o2.getYear());
		if(result!=0) {
			return result;//年份升序排序
		}
		return -Integer.compare(o1.getTemperature(), o2.getTemperature());//若在同一年份,则温度降序排序
	}
}

自定义分区

自定义分区

需要继承Partitioner父类

public class MyPartioner extends Partitioner<MyClass,Text>{
//按年份进行分区,年份相同的一定在一个区里面
	@Override
	public int getPartition(MyClass key, Text value, int numReduce) {
		return (key.getYear()*Integer.MAX_VALUE)%numReduce;
	}
}

自定义分组

因为,这里的key为年份|温度,但是要求相同年份的分为一组。故需要重写分组方法。需要继承WritableComparator父类

public class MyGroup extends WritableComparator{
	public MyGroup() {
		super(MyClass.class,true);
	}
	public int compare(WritableComparable a,WritableComparable b) {
		MyClass o1=(MyClass) a;
		MyClass o2=(MyClass) b;
		return Integer.compare(o1.getYear(), o2.getYear());
	}
}

主函数

public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
		Configuration conf=new Configuration();
		Job job=Job.getInstance(conf);
		job.setJarByClass(RunJob.class);
		job.setMapperClass(HotMapper.class);
		job.setReducerClass(HotReduce.class);
		job.setOutputKeyClass(MyClass.class);
		job.setOutputValueClass(Text.class);
		job.setNumReduceTasks(4);
		job.setPartitionerClass(MyPartioner.class);
		job.setGroupingComparatorClass(MyGroup.class);
		job.setSortComparatorClass(MySort.class);
		FileInputFormat.addInputPath(job,new Path("/usr/city/input/"));
		FileOutputFormat.setOutputPath(job, new Path("/usr/city/output/"));
		job.waitForCompletion(true);
	}

———————————————————————————————————————

分区与分组

分区:如果不自定义分区类,而使用默认分区时,采取的是对键值进行哈希操作,并与reducetask任务数取模,根据得到的值进行分区。由于默认的reducetask任务数设置为1,因此默认情况下只有1个分区。如果自定义一个分区类,则会按照自定义的方式进行分区。

分组:分组和分区类似,也是用来划分数据集的,只不过更加细粒度。如果不自定义分组类而使用默认分组的话,跟默认分区相同,也是通过比较键值来进行分组。reduce()函数是按照组为操作对象进行统计的。

reducer类处理整个分区的数据,其操作对象是区,一个区调用一次reducer类。而reduce()函数的操作对象是组,也就是分区中有 几个分组就调用几次reduce()函数,reduce()函数对分组对应的集合进行处理。分组详解:分组前先对合并后的分区文件中的记录进行排序,排序后再进行分组。分组是通过对排序后的记录从上往下遍历比对进行的。如果上下两个比对结果为0,则分到同一个组,否则各分一个组。后面的分组与前面的分组无关,只与紧挨着的上一条记录有关。举个例子:就算前面有一个“美国”分组,但是中间隔了一个“日本”分组,则后面再出现“美国”时也不会合并到前面的分组中。因此,如果想按照国家分组的话,应该将国家作为组合键的第一个属性,这样在reduce端排序后得到的就是相同的国家上下挨着了。

你可能感兴趣的:(Hadoop)