MapReduce之自定义分区器Partitioner

@

目录

  • 问题引出
  • 默认Partitioner分区
  • 自定义Partitioner步骤
  • Partition分区案例实操
  • 分区总结

问题引出

要求将统计结果按照条件输出到不同文件中(分区)。

比如:将统计结果按照手机归属地不同省份输出到不同文件中(分区)

默认Partitioner分区

1public class HashPartitioner<K,V> extends Partitioner<K,V>{ 2 public int getPartition(K key,V value, int numReduceTasks){ 3 return (key.hashCode() & Integer.MAX VALUE) & numReduceTasks; 4 } 5}
  • 默认分区是根据keyhashCodeReduceTasks个数取模得到的。
  • 用户没法控制哪个key存储到哪个分区。

自定义Partitioner步骤

  1. 自定义类继承Partitioner,重写getPartition()方法

    public class CustomPartitioner extends Partitioner<Text,FlowBea>{ @Override public int getPartition(Text key,FlowBean value,int numPartitions){ //控制分区代码逻辑 …… return partition; } }

  2. 在Job驱动类中,设置自定义Partitioner

    job.setPartitionerClass(CustomPartitioner.class)

  3. 自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask

    job.setNumReduceTask(5);//假设需要分5个区

Partition分区案例实操

将统计结果按照手机归属地不同省份输出到不同文件中(分区)

输入数据:
在这里插入图片描述

期望输出数据:
手机号136、137、138、139开头都分别放到一个独立的4个文件中,其他开头的放到一个文件中。所以总共分为5个文件,也就是五个区。

相比于之前的自定义flowbean,这次自定义分区,只需要多编写一个分区器,以及在job驱动类中设置分区器,mapper和reducer类不改变

MyPartitioner.java

1/* 2 * KEY, VALUE: Mapper输出的Key-value类型 3 */ 4public class MyPartitioner extends Partitioner<Text, FlowBean>{ 5 6 // 计算分区 numPartitions为总的分区数,reduceTask的数量 7 // 分区号必须为int型的值,且必须符合 0<= partitionNum < numPartitions 8 @Override 9 public int getPartition(Text key, FlowBean value, int numPartitions) { 10 11 String suffix = key.toString().substring(0, 3);//前开后闭,取手机号前三位数 12 13 int partitionNum=0;//分区编号 14 15 16 switch (suffix) { 17 case "136": 18 partitionNum=numPartitions-1;//由于分区编号不能大于分区总数,所以用这种方法比较好 19 break; 20 case "137": 21 partitionNum=numPartitions-2; 22 break; 23 case "138": 24 partitionNum=numPartitions-3; 25 break; 26 case "139": 27 partitionNum=numPartitions-4; 28 break; 29 30 default: 31 break; 32 } 33 34 return partitionNum; 35 } 36 37}

FlowBeanDriver.java

1public class FlowBeanDriver { 2 3 public static void main(String[] args) throws Exception { 4 5 Path inputPath=new Path("e:/mrinput/flowbean"); 6 Path outputPath=new Path("e:/mroutput/partitionflowbean"); 7 8 //作为整个Job的配置 9 Configuration conf = new Configuration(); 10 11 //保证输出目录不存在 12 FileSystem fs=FileSystem.get(conf); 13 14 if (fs.exists(outputPath)) { 15 fs.delete(outputPath, true); 16 } 17 18 // ①创建Job 19 Job job = Job.getInstance(conf); 20 21 // ②设置Job 22 // 设置Job运行的Mapper,Reducer类型,Mapper,Reducer输出的key-value类型 23 job.setMapperClass(FlowBeanMapper.class); 24 job.setReducerClass(FlowBeanReducer.class); 25 26 // Job需要根据Mapper和Reducer输出的Key-value类型准备序列化器,通过序列化器对输出的key-value进行序列化和反序列化 27 // 如果Mapper和Reducer输出的Key-value类型一致,直接设置Job最终的输出类型 28 job.setOutputKeyClass(Text.class); 29 job.setOutputValueClass(FlowBean.class); 30 31 // 设置输入目录和输出目录 32 FileInputFormat.setInputPaths(job, inputPath); 33 FileOutputFormat.setOutputPath(job, outputPath); 34 35 // 设置ReduceTask的数量为5 36 job.setNumReduceTasks(5); 37 38 // 设置使用自定义的分区器 39 job.setPartitionerClass(MyPartitioner.class); 40 41 // ③运行Job 42 job.waitForCompletion(true); 43 44 } 45}

FlowBeanMapper.java

1/* 2 * 1. 统计手机号(String)的上行(long,int),下行(long,int),总流量(long,int) 3 * 4 * 手机号为key,Bean{上行(long,int),下行(long,int),总流量(long,int)}为value 5 * 6 * 7 * 8 * 9 */ 10public class FlowBeanMapper extends Mapper<LongWritable, Text, Text, FlowBean>{ 11 12 private Text out_key=new Text(); 13 private FlowBean out_value=new FlowBean(); 14 15 // (0,1 13736230513 192.196.100.1 www.atguigu.com 2481 24681 200) 16 @Override 17 protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, FlowBean>.Context context) 18 throws IOException, InterruptedException { 19 20 String[] words = value.toString().split("\t"); 21 22 //封装手机号 23 out_key.set(words[1]); 24 // 封装上行 25 out_value.setUpFlow(Long.parseLong(words[words.length-3])); 26 // 封装下行 27 out_value.setDownFlow(Long.parseLong(words[words.length-2])); 28 29 context.write(out_key, out_value); 30 } 31}

FlowBeanReducer.java

1public class FlowBeanReducer extends Reducer<Text, FlowBean, Text, FlowBean>{ 2 3 private FlowBean out_value=new FlowBean(); 4 5 @Override 6 protected void reduce(Text key, Iterable<FlowBean> values, Reducer<Text, FlowBean, Text, FlowBean>.Context context) 7 throws IOException, InterruptedException { 8 9 long sumUpFlow=0; 10 long sumDownFlow=0; 11 12 for (FlowBean flowBean : values) { 13 14 sumUpFlow+=flowBean.getUpFlow(); 15 sumDownFlow+=flowBean.getDownFlow(); 16 17 } 18 19 out_value.setUpFlow(sumUpFlow); 20 out_value.setDownFlow(sumDownFlow); 21 out_value.setSumFlow(sumDownFlow+sumUpFlow); 22 23 context.write(key, out_value); 24 25 } 26}

FlowBean.java

1public class FlowBean implements Writable{ 2 3 private long upFlow; 4 private long downFlow; 5 private long sumFlow; 6 7 public FlowBean() { 8 9 } 10 11 public long getUpFlow() { 12 return upFlow; 13 } 14 15 public void setUpFlow(long upFlow) { 16 this.upFlow = upFlow; 17 } 18 19 public long getDownFlow() { 20 return downFlow; 21 } 22 23 public void setDownFlow(long downFlow) { 24 this.downFlow = downFlow; 25 } 26 27 public long getSumFlow() { 28 return sumFlow; 29 } 30 31 public void setSumFlow(long sumFlow) { 32 this.sumFlow = sumFlow; 33 } 34 35 // 序列化 在写出属性时,如果为引用数据类型,属性不能为null 36 @Override 37 public void write(DataOutput out) throws IOException { 38 39 out.writeLong(upFlow); 40 out.writeLong(downFlow); 41 out.writeLong(sumFlow); 42 43 44 } 45 46 //反序列化 序列化和反序列化的顺序要一致 47 @Override 48 public void readFields(DataInput in) throws IOException { 49 upFlow=in.readLong(); 50 downFlow=in.readLong(); 51 sumFlow=in.readLong(); 52 53 } 54 55 @Override 56 public String toString() { 57 return upFlow + "\t" + downFlow + "\t" + sumFlow; 58 } 59}

输出结果:
总共五个文件
在这里插入图片描述
一号区:
在这里插入图片描述
二号区:
在这里插入图片描述
三号区:
在这里插入图片描述

四号区:
在这里插入图片描述

其他号码为第五号区:
在这里插入图片描述

分区总结

  • 如果ReduceTask的数量 > getPartition的结果数,则会多产生几个空的输出文件part-r-000xx
  • 如果Reduceask的数量 < getPartition的结果数,则有一部分分区数据无处安放,会Exception
  • 如果ReduceTask的数量 = 1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个ReduceTask,最终也就只会产生一个结果文件partr-00000

以刚才的案例分析:
例如:假设自定义分区数为5,则

  • job.setlNlurmReduce Task(1);会正常运行,只不过会产生一个输出文件
  • job.setlNlunReduce Task(2),会报错
  • job.setNumReduceTasks(6);大于5,程序会正常运行,会产生空文件
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Linux VPS新硬盘分区与挂载教程

通过fdiskl我们可以看到/dev/xvdb(此名称因系统而异)容量有23.6G,而且没有分区,接下来我们对它进行分区和挂载,(按照如下方式挂载,重装系统不丢失挂载分区硬盘的数据)注:挂载硬盘过程请按照下面代码“蓝色”标记复制执行既可,不同Linux系统,只需途径名称修改对应系统名称。1.fdiskl

Linux和Uboot下eMMC boot分区读写

 关键词:eMMCboot、PARTITION\_CONFIG、force\_ro等。1\.eMMC的分区大部分eMMC都有类似如下的分区,其中BOOT、RPMB和UDA一般是默认存在的,gpp分区需要手动创建。!(https://oscimg.oschina.net/oscnet/bb03777529d529b2

Apache Hudi重磅RFC解读之记录级别全局索引

1\.摘要Hudi表允许多种类型操作,包括非常常用的upsert,当然为支持upsert,Hudi依赖索引机制来定位记录在哪些文件中。当前Hudi支持分区和非分区的数据集。分区数据集是将一组文件(数据)放在称为分区的桶中的数据集。一个Hudi数据集可能由N个分区和M个文件组成,这种组织结构也非常方便hive/presto/sp