Partitioner :是用来对 mapper 输出的数据进行分区的,partitioner通过哈希函数对Key 或者 Key 的子集求哈希值,哈希值相同的键值对将在同一个分区里面。在MapReduce中,默认的分区机制是基于键(key)的哈希分区(Hash Partitioning)。MapReduce框架将Map阶段产生的键值对按照键进行哈希运算,然后将结果对分区数取模,从而确定每个键值对应的分区。这样可以确保具有相同键的键值对会被分配到相同的分区,方便后续的Reduce阶段进行处理
当MapReduce中有多个reduce task执行的时候,此时map task的输出就会面临一个问题:究竟将自己的输出数据交给哪一个reducetask来处理,这就是数据分区(partition)
默认情况下,MapReduce是只有一个reducetask来进行数据的处理。这就使得不管输入的数据量多大,最终的结果都是输出到一个文件中
当改变reducetask个数的时候,作为maptask就会涉及到分区的问题,即:MapTask输出的结果如何分配给各个ReduceTask来处理
Map的数量由数据文件大小决定,即map的数量=数据文件大小(M)/128M。
在MapReduce中,通过我们指定分区,会将同一个分区的数据发送到同一个Reduce当中进行处理。例如:为了数据的统计,可以把一批类似的数据发送到同一个Reduce当中,在同一个Reduce当中统计相同类型的数据,就可以实现类似的数据分区和统计等。其实就是相同类型的数据,有共性的数据,送到一起去处理。MapReduce默认是只有一个分区(一个分区就是对应一个输出文件)
总结一下
分区的作用是对数据进行分类处理
在分区的时候,需要对分区进行编号,编号从0开始
在MapReduce中,默认只有1个分区,也只有一个1个ReduceTask来处理数据,最后只会产生一个结果文件
分区的数量决定ReduceTask的数量
在MapReduce中,如果不指定Partitioner,那么默认使用的是HashPartitioner,
在MapReduce中,需要对分区进行编号,编号从0开始依次往上递增 在MapReduce中,如果不指定,那么默认只有1个ReduceTask,每一个ReduceTask会对应一个结果文件。也因此,如果设置了Partitioner,那么需要给定对应数量的ReduceTask - 分区数决定了ReduceTask的数量
案例1:
数据,partion.txt,按第二列地名分类,统计各地区每个人的销售金额 ,先将上传到hdfs中
202412 sh zhangsan 10000
202412 js lisi 10000
202412 sh liming 20000
202401 sh zhangsan 3000
202401 js lisi 10000
代码:
Mapper
package partion;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
// 用于完成Map阶段
// 再MapReduce中,要求被处理的数据能够被序列化
// MApReduce提供了一套单独的序列化机制
// KEYIN-输入的键的类型。如果不指定,那么默认情况下,表示行的字节偏移量
// VALUEIN-输入值得类型。如果不指定,那么默认情况下,表示的读取到的一行数据
// KEYOUT-输出的键的类型。当前案例中,输出的键表示的是姓名
// VALUEOUT-输出的值的类型。当前案例,输出的值表示的是money
public class CityFlowMapper extends Mapper<LongWritable, Text, Text, CityPeopleInfo> {
// 覆盖map方法,将处理逻辑写到这个方法中
// key:键。表示的是行的字节偏移量
// value:值。表示读取到的一行数据
// context:配置参数
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] array = value.toString().split(" ");
CityPeopleInfo p = new CityPeopleInfo();
//202412 sh zhangsan 10000
p.setCity(array[1]);//sh
p.setMoney(Integer.parseInt(array[3]));//10000
//name zhangsan
context.write(new Text(array[2]),p);
}
}
Reducer
package partion;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
// KEYIN、VALUEIN输入的键的类型。
// Reducer的数据从Mapper来的
// 所以Mapper的输出就是Reducer的输入
// KEYOUT、VALUEOUT-输出的值的类型。当前案例中,要输出每一个人的金额
public class CityFlowReducer extends Reducer<Text, CityPeopleInfo,Text, IntWritable> {
protected void reduce(Text key, Iterable<CityPeopleInfo> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for (CityPeopleInfo p : values) {
sum+= p.getMoney();
}
//3、写入上下文
context.write(key,new IntWritable(sum));
}
}
Partion
package partion;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class CityPartion extends Partitioner<Text,CityPeopleInfo> {
public int getPartition(Text text,CityPeopleInfo flow,int i){
String city=flow.getCity();
if(city.equals("sh"))return 0;
else if(city.equals("sx"))return 1;
else return 2;
}
}
实体类
package partion;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class CityPeopleInfo implements Writable {
private int money;
private String city="";
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
private int downFlow;
// 需要将有必要的属性依次序列化写出即可
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(getCity());
out.writeInt(getMoney());
}
@Override
public void readFields(DataInput in) throws IOException {
setCity(in.readUTF());
setMoney(in.readInt());
}
}
main
package partion;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import java.io.IOException;
public class CityJobMain {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//一、初始化Job
Configuration configuration = new Configuration();
//获取运行命令的参数,参数一:输入文件路径,参数二:输出文件路径
//如果输入路径是一个文件,那么只处理这个文件,如果指定的路径是目录,则处理这个目录下的所有文件
//输出路径只能是不存在的目录名
String [] otherArgs = new GenericOptionsParser(configuration,args).getRemainingArgs();
if(otherArgs.length < 2){
System.err.println("必须提供输入文件路径和输出文件路径");
System.exit(2);
}
Job job = Job.getInstance(configuration, "mr");
job.setJarByClass(CityJobMain.class);
//二、设置Job的相关信息 8个小步骤
//1、设置输入路径
job.setInputFormatClass(TextInputFormat.class);
//本地运行
//TextInputFormat.addInputPath(job,new Path("/tmp/input/mr1.txt"));
TextInputFormat.addInputPath(job,new Path(args[0]));
//特别注意,有几个分区要写到这
job.setNumReduceTasks(3);
job.setPartitionerClass(CityPartion.class);
//2、设置Mapper类型,并设置输出键和输出值
job.setMapperClass(CityFlowMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(CityPeopleInfo.class);
//shuffle阶段,使用默认的
//3、设置Reducer类型,并设置输出键和输出值
job.setReducerClass(CityFlowReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//4、设置输出路径
job.setOutputFormatClass(TextOutputFormat.class);
//本地运行
//TextOutputFormat.setOutputPath(job,new Path("/tmp/output/mr"));
TextOutputFormat.setOutputPath(job,new Path(args[1]));
//三、等待完成
boolean b = job.waitForCompletion(true);
System.out.println(b==true?"MapReduce 任务执行成功!":"MapReduce 任务执行失败!");
System.exit(b ? 0 : 1);
}
}
打成jar包上传到linux上
执行
hadoop jar /hadoopmapreduce-1.0-SNAPSHOT.jar partion.CityJobMain /partion.txt /mypeopleinfo2
使用hdfs查看,生成的结果文件有三个,三个分区
分别查看三个文件的计算结果