@

问题引出

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

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

默认Partitioner分区

public class HashPartitioner<K,V> extends Partitioner<K,V>{
public int getPartition(K key,V value, int numReduceTasks){
return (key.hashCode() & Integer.MAX VALUE) & numReduceTasks;
}
}
  • 默认分区是根据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;
}
}
  1. 在Job驱动类中,设置自定义Partitioner
job.setPartitionerClass(CustomPartitioner.class)
  1. 自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask
 job.setNumReduceTask(5);//假设需要分5个区

Partition分区案例实操

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

输入数据:

期望输出数据:

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

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

MyPartitioner.java

/*
* KEY, VALUE: Mapper输出的Key-value类型
*/
public class MyPartitioner extends Partitioner<Text, FlowBean>{ // 计算分区 numPartitions为总的分区数,reduceTask的数量
// 分区号必须为int型的值,且必须符合 0<= partitionNum < numPartitions
@Override
public int getPartition(Text key, FlowBean value, int numPartitions) { String suffix = key.toString().substring(0, 3);//前开后闭,取手机号前三位数 int partitionNum=0;//分区编号 switch (suffix) {
case "136":
partitionNum=numPartitions-1;//由于分区编号不能大于分区总数,所以用这种方法比较好
break;
case "137":
partitionNum=numPartitions-2;
break;
case "138":
partitionNum=numPartitions-3;
break;
case "139":
partitionNum=numPartitions-4;
break; default:
break;
} return partitionNum;
} }

FlowBeanDriver.java

public class FlowBeanDriver {

	public static void main(String[] args) throws Exception {

		Path inputPath=new Path("e:/mrinput/flowbean");
Path outputPath=new Path("e:/mroutput/partitionflowbean"); //作为整个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.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class); // 设置输入目录和输出目录
FileInputFormat.setInputPaths(job, inputPath);
FileOutputFormat.setOutputPath(job, outputPath); // 设置ReduceTask的数量为5
job.setNumReduceTasks(5); // 设置使用自定义的分区器
job.setPartitionerClass(MyPartitioner.class); // ③运行Job
job.waitForCompletion(true); }
}

FlowBeanMapper.java

/*
* 1. 统计手机号(String)的上行(long,int),下行(long,int),总流量(long,int)
*
* 手机号为key,Bean{上行(long,int),下行(long,int),总流量(long,int)}为value
*
*
*
*
*/
public class FlowBeanMapper extends Mapper<LongWritable, Text, Text, FlowBean>{ private Text out_key=new Text();
private FlowBean out_value=new FlowBean(); // (0,1 13736230513 192.196.100.1 www.atguigu.com 2481 24681 200)
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, FlowBean>.Context context)
throws IOException, InterruptedException { String[] words = value.toString().split("\t"); //封装手机号
out_key.set(words[1]);
// 封装上行
out_value.setUpFlow(Long.parseLong(words[words.length-3]));
// 封装下行
out_value.setDownFlow(Long.parseLong(words[words.length-2])); context.write(out_key, out_value);
}
}

FlowBeanReducer.java

public class FlowBeanReducer extends Reducer<Text, FlowBean, Text, FlowBean>{

	private FlowBean out_value=new FlowBean();

	@Override
protected void reduce(Text key, Iterable<FlowBean> values, Reducer<Text, FlowBean, Text, FlowBean>.Context context)
throws IOException, InterruptedException { long sumUpFlow=0;
long sumDownFlow=0; for (FlowBean flowBean : values) { sumUpFlow+=flowBean.getUpFlow();
sumDownFlow+=flowBean.getDownFlow(); } out_value.setUpFlow(sumUpFlow);
out_value.setDownFlow(sumDownFlow);
out_value.setSumFlow(sumDownFlow+sumUpFlow); context.write(key, out_value); }
}

FlowBean.java

public class FlowBean implements Writable{

	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;
}
}

输出结果:

总共五个文件



一号区:



二号区:



三号区:

四号区:

其他号码为第五号区:

分区总结

  • 如果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,程序会正常运行,会产生空文件

MapReduce之自定义分区器Partitioner的更多相关文章

  1. spark自定义分区器实现

    在spark中,框架默认使用的事hashPartitioner分区器进行对rdd分区,但是实际生产中,往往使用spark自带的分区器会产生数据倾斜等原因,这个时候就需要我们自定义分区,按照我们指定的字 ...

  2. kafka 自定义分区器

    package cn.xiaojf.kafka.producer; import org.apache.kafka.clients.producer.Partitioner; import org.a ...

  3. 关于MapReduce中自定义分区类(四)

    MapTask类 在MapTask类中找到run函数 if(useNewApi){       runNewMapper(job, splitMetaInfo, umbilical, reporter ...

  4. Parallel中分区器Partitioner的简单使用

    Partitioner.Create(1,10,4).GetDynamicPartitions() 为长度为10的序列创建分区,每个分区至多4个元素,分区方法及结果:Partitioner.Creat ...

  5. Spark源码分析之分区器的作用

    最近因为手抖,在Spark中给自己挖了一个数据倾斜的坑.为了解决这个问题,顺便研究了下Spark分区器的原理,趁着周末加班总结一下~ 先说说数据倾斜 数据倾斜是指Spark中的RDD在计算的时候,每个 ...

  6. 玩转Kafka的生产者——分区器与多线程

    上篇文章学习kafka的基本安装和基础概念,本文主要是学习kafka的常用API.其中包括生产者和消费者, 多线程生产者,多线程消费者,自定义分区等,当然还包括一些避坑指南. 首发于个人网站:链接地址 ...

  7. kafka producer partitions分区器(七)

    消息在经过拦截器.序列化后,就需要确定它发往哪个分区,如果在ProducerRecord中指定了partition字段,那么就不再需要partitioner分区器进行分区了,如果没有指定,那么会根据k ...

  8. RDD(六)——分区器

    RDD的分区器 Spark目前支持Hash分区和Range分区,用户也可以自定义分区,Hash分区为当前的默认分区,Spark中分区器直接决定了RDD中分区的个数.RDD中每条数据经过Shuffle过 ...

  9. Spark分区器浅析

    分区器作用:决定该数据在哪个分区 概览: 仅仅只有pairRDD才可能持有分区器,普通RDD的分区器为None 在分区器为None时RDD分区一般继承至父RDD分区 初始RDD分区数: 由集合创建,R ...

随机推荐

  1. 33_栈程序演示.swf

    pBottom执行栈底有效元素的前一个节点,该节点没有存储有效数据,这样设计是便于栈的管理,向链表一样pHead指向链表的第一个节点,该节点是不存储有效数据的 pTop执行栈顶最新的节点 如果pTop ...

  2. netty解决TCP的拆包和粘包的解决办法

    TCP粘包.拆包问题 熟悉tcp编程的可能知道,无论是服务端还是客户端,当我们读取或者发送数据的时候,都需要考虑TCP底层的粘包个拆包机制. tcp是一个“流”协议,所谓流就是没有界限的传输数据,在业 ...

  3. ubuntu添加新的分辨率选项(干货)

    ubuntu默认可选的分辨率不能够满足我的需求,在这里记录增加1440*900分辨率的过程 1. 终端输入: cvt 1440 900 2. 修改配置文件: vim /etc/profile xran ...

  4. Hexo快速构建个人小站-Fulid主题下添加Valine评论系统(三)

    Hexo目录: Hexo快速构建个人小站-Hexo初始化和将项目托管在Github(一) Hexo快速构建个人小站-自定义域名和自定义主题(二) 背景交代: 前面两章完成了Hexo的初始化和部分自定义 ...

  5. Logstash7.6.2同步Mysql数据到ElasticSearch

    1.准备工作:存在的mysql的数据库表.创建好的elasticsearch索引 2.下载mysql-connector 下载地址:https://dev.mysql.com/downloads/co ...

  6. Netty 源码解析(八): 回到 Channel 的 register 操作

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  7. Yarn的安装和全局配置(源/缓存位置/全局安装位置)

    本文安装环境: Win10 64位 前置条件: 已安装好Node环境(参考Node安装与环境配置) 下载和安装 Yarn安装包下载地址 全局配置 控制台输入命令, 正常显示版本表示安装成功 $ yar ...

  8. 微信h5页面下拉露出网页来源的解决办法

    微信h5页面下拉露出网页来源的解决办法:将document的touchmove事件禁止掉 //禁止页面拖动 document.addEventListener('touchmove', functio ...

  9. 总结几个移动端H5软键盘的大坑

    1.部分机型软键盘弹起挡住原来的视图 解决方法:可以通过监听移动端软键盘弹起 Element.scrollIntoView() 方法让当前的元素滚动到浏览器窗口的可视区域内.参数如下. true,表示 ...

  10. postman设置页面详解

    设置详解/General Trim keys and values in request body:如果使用form-data或者url-encoded的方式向服务器发送数据:将该选项设置为ON,则会 ...