• 如何使用Hadoop的Partitioner


    Hadoop里面的MapReduce编程模型,非常灵活,大部分环节我们都可以重写它的API,来灵活定制我们自己的一些特殊需求。 

    今天散仙要说的这个分区函数Partitioner,也是一样如此,下面我们先来看下Partitioner的作用: 
    对map端输出的数据key作一个散列,使数据能够均匀分布在各个reduce上进行后续操作,避免产生热点区。 
    Hadoop默认使用的分区函数是Hash Partitioner,源码如下: 

    Java代码  收藏代码
    1. /** 
    2.  * Licensed to the Apache Software Foundation (ASF) under one 
    3.  * or more contributor license agreements.  See the NOTICE file 
    4.  * distributed with this work for additional information 
    5.  * regarding copyright ownership.  The ASF licenses this file 
    6.  * to you under the Apache License, Version 2.0 (the 
    7.  * "License"); you may not use this file except in compliance 
    8.  * with the License.  You may obtain a copy of the License at 
    9.  * 
    10.  *     http://www.apache.org/licenses/LICENSE-2.0 
    11.  * 
    12.  * Unless required by applicable law or agreed to in writing, software 
    13.  * distributed under the License is distributed on an "AS IS" BASIS, 
    14.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    15.  * See the License for the specific language governing permissions and 
    16.  * limitations under the License. 
    17.  */  
    18.   
    19. package org.apache.hadoop.mapreduce.lib.partition;  
    20.   
    21. import org.apache.hadoop.mapreduce.Partitioner;  
    22.   
    23. /** Partition keys by their {@link Object#hashCode()}. */  
    24. public class HashPartitioner<K, V> extends Partitioner<K, V> {  
    25.   
    26.   /** Use {@link Object#hashCode()} to partition. */  
    27.   public int getPartition(K key, V value,  
    28.                           int numReduceTasks) {  
    29.       //默认使用key的hash值与上int的最大值,避免出现数据溢出 的情况  
    30.     return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;  
    31.   }  
    32.   
    33. }  

    大部分情况下,我们都会使用默认的分区函数,但有时我们又有一些,特殊的需求,而需要定制Partition来完成我们的业务,案例如下: 
    对如下数据,按字符串的长度分区,长度为1的放在一个,2的一个,3的各一个。 

    Java代码  收藏代码
    1. 河南省;1  
    2. 河南;2  
    3. 中国;3  
    4. 中国人;4  
    5. 大;1  
    6. 小;3  
    7. 中;11  

    这时候,我们使用默认的分区函数,就不行了,所以需要我们定制自己的Partition,首先分析下,我们需要3个分区输出,所以在设置reduce的个数时,一定要设置为3,其次在partition里,进行分区时,要根据长度具体分区,而不是根据字符串的hash码来分区。核心代码如下: 
    Java代码  收藏代码
    1. /** 
    2.  * Partitioner 
    3.  *  
    4.  *  
    5.  * */  
    6.  public static class PPartition extends Partitioner<Text, Text>{   
    7.     @Override  
    8.     public int getPartition(Text arg0, Text arg1, int arg2) {  
    9.          /** 
    10.           * 自定义分区,实现长度不同的字符串,分到不同的reduce里面 
    11.           *  
    12.           * 现在只有3个长度的字符串,所以可以把reduce的个数设置为3 
    13.           * 有几个分区,就设置为几 
    14.           * */  
    15.           
    16.         String key=arg0.toString();  
    17.         if(key.length()==1){  
    18.             return 1%arg2;  
    19.         }else if(key.length()==2){  
    20.             return 2%arg2;  
    21.         }else if(key.length()==3){  
    22.             return 3%arg2;  
    23.         }  
    24.           
    25.             
    26.           
    27.         return  0;  
    28.     }  
    29.        
    30.        
    31.        
    32.        
    33.  }  


    全部代码如下: 
    Java代码  收藏代码
    1. package com.partition.test;  
    2.   
    3. import java.io.IOException;  
    4.   
    5. import org.apache.hadoop.fs.FileSystem;  
    6. import org.apache.hadoop.fs.Path;  
    7. import org.apache.hadoop.io.LongWritable;  
    8. import org.apache.hadoop.io.Text;  
    9. import org.apache.hadoop.mapred.JobConf;  
    10. import org.apache.hadoop.mapreduce.Job;  
    11. import org.apache.hadoop.mapreduce.Mapper;  
    12. import org.apache.hadoop.mapreduce.Partitioner;  
    13. import org.apache.hadoop.mapreduce.Reducer;  
    14. import org.apache.hadoop.mapreduce.lib.db.DBConfiguration;  
    15. import org.apache.hadoop.mapreduce.lib.db.DBInputFormat;  
    16. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
    17. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
    18. import org.apache.hadoop.mapreduce.lib.output.MultipleOutputs;  
    19. import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;  
    20.   
    21. import com.qin.operadb.PersonRecoder;  
    22. import com.qin.operadb.ReadMapDB;  
    23.    
    24.   
    25. /** 
    26.  * @author qindongliang 
    27.  *  
    28.  * 大数据交流群:376932160 
    29.  *  
    30.  *  
    31.  * **/  
    32. public class MyTestPartition {  
    33.       
    34.     /** 
    35.      * map任务 
    36.      *  
    37.      * */  
    38.     public static class PMapper extends Mapper<LongWritable, Text, Text, Text>{  
    39.               
    40.         @Override  
    41.         protected void map(LongWritable key, Text value,Context context)  
    42.                 throws IOException, InterruptedException {  
    43.             // System.out.println("进map了");  
    44.             //mos.write(namedOutput, key, value);  
    45.             String ss[]=value.toString().split(";");  
    46.               
    47.             context.write(new Text(ss[0]), new Text(ss[1]));  
    48.               
    49.               
    50.               
    51.         }  
    52.           
    53.           
    54.     }  
    55.       
    56.     /** 
    57.      * Partitioner 
    58.      *  
    59.      *  
    60.      * */  
    61.      public static class PPartition extends Partitioner<Text, Text>{   
    62.         @Override  
    63.         public int getPartition(Text arg0, Text arg1, int arg2) {  
    64.              /** 
    65.               * 自定义分区,实现长度不同的字符串,分到不同的reduce里面 
    66.               *  
    67.               * 现在只有3个长度的字符串,所以可以把reduce的个数设置为3 
    68.               * 有几个分区,就设置为几 
    69.               * */  
    70.               
    71.             String key=arg0.toString();  
    72.             if(key.length()==1){  
    73.                 return 1%arg2;  
    74.             }else if(key.length()==2){  
    75.                 return 2%arg2;  
    76.             }else if(key.length()==3){  
    77.                 return 3%arg2;  
    78.             }  
    79.               
    80.                 
    81.               
    82.             return  0;  
    83.         }  
    84.            
    85.            
    86.            
    87.            
    88.      }  
    89.        
    90.    
    91.      /*** 
    92.       * Reduce任务 
    93.       *  
    94.       * **/  
    95.      public static class PReduce extends Reducer<Text, Text, Text, Text>{  
    96.          @Override  
    97.         protected void reduce(Text arg0, Iterable<Text> arg1, Context arg2)  
    98.                 throws IOException, InterruptedException {  
    99.                
    100.               String key=arg0.toString().split(",")[0];  
    101.              System.out.println("key==> "+key);  
    102.              for(Text t:arg1){  
    103.                  //System.out.println("Reduce:  "+arg0.toString()+"   "+t.toString());  
    104.                  arg2.write(arg0, t);  
    105.              }  
    106.                  
    107.                
    108.         }  
    109.            
    110.        
    111.            
    112.      }  
    113.        
    114.        
    115.      public static void main(String[] args) throws Exception{  
    116.          JobConf conf=new JobConf(ReadMapDB.class);  
    117.          //Configuration conf=new Configuration();  
    118.          conf.set("mapred.job.tracker","192.168.75.130:9001");  
    119.         //读取person中的数据字段  
    120.          conf.setJar("tt.jar");  
    121.         //注意这行代码放在最前面,进行初始化,否则会报  
    122.        
    123.        
    124.         /**Job任务**/  
    125.         Job job=new Job(conf, "testpartion");  
    126.         job.setJarByClass(MyTestPartition.class);  
    127.         System.out.println("模式:  "+conf.get("mapred.job.tracker"));;  
    128.         // job.setCombinerClass(PCombine.class);  
    129.          job.setPartitionerClass(PPartition.class);  
    130.            
    131.          job.setNumReduceTasks(3);//设置为3  
    132.          job.setMapperClass(PMapper.class);  
    133.         // MultipleOutputs.addNamedOutput(job, "hebei", TextOutputFormat.class, Text.class, Text.class);  
    134.         // MultipleOutputs.addNamedOutput(job, "henan", TextOutputFormat.class, Text.class, Text.class);  
    135.          job.setReducerClass(PReduce.class);  
    136.          job.setOutputKeyClass(Text.class);  
    137.          job.setOutputValueClass(Text.class);  
    138.           
    139.         String path="hdfs://192.168.75.130:9000/root/outputdb";  
    140.         FileSystem fs=FileSystem.get(conf);  
    141.         Path p=new Path(path);  
    142.         if(fs.exists(p)){  
    143.             fs.delete(p, true);  
    144.             System.out.println("输出路径存在,已删除!");  
    145.         }  
    146.         FileInputFormat.setInputPaths(job, "hdfs://192.168.75.130:9000/root/input");  
    147.         FileOutputFormat.setOutputPath(job,p );  
    148.         System.exit(job.waitForCompletion(true) ? 0 : 1);    
    149.            
    150.            
    151.     }  
    152.       
    153.       
    154.   
    155. }  

    运行情况如下: 

    Java代码  收藏代码
    1. 模式:  192.168.75.130:9001  
    2. 输出路径存在,已删除!  
    3. WARN - JobClient.copyAndConfigureFiles(746) | Use GenericOptionsParser for parsing the arguments. Applications should implement Tool for the same.  
    4. INFO - FileInputFormat.listStatus(237) | Total input paths to process : 1  
    5. WARN - NativeCodeLoader.<clinit>(52) | Unable to load native-hadoop library for your platform... using builtin-java classes where applicable  
    6. WARN - LoadSnappy.<clinit>(46) | Snappy native library not loaded  
    7. INFO - JobClient.monitorAndPrintJob(1380) | Running job: job_201404101853_0005  
    8. INFO - JobClient.monitorAndPrintJob(1393) |  map 0% reduce 0%  
    9. INFO - JobClient.monitorAndPrintJob(1393) |  map 100% reduce 0%  
    10. INFO - JobClient.monitorAndPrintJob(1393) |  map 100% reduce 11%  
    11. INFO - JobClient.monitorAndPrintJob(1393) |  map 100% reduce 22%  
    12. INFO - JobClient.monitorAndPrintJob(1393) |  map 100% reduce 55%  
    13. INFO - JobClient.monitorAndPrintJob(1393) |  map 100% reduce 100%  
    14. INFO - JobClient.monitorAndPrintJob(1448) | Job complete: job_201404101853_0005  
    15. INFO - Counters.log(585) | Counters: 29  
    16. INFO - Counters.log(587) |   Job Counters   
    17. INFO - Counters.log(589) |     Launched reduce tasks=3  
    18. INFO - Counters.log(589) |     SLOTS_MILLIS_MAPS=7422  
    19. INFO - Counters.log(589) |     Total time spent by all reduces waiting after reserving slots (ms)=0  
    20. INFO - Counters.log(589) |     Total time spent by all maps waiting after reserving slots (ms)=0  
    21. INFO - Counters.log(589) |     Launched map tasks=1  
    22. INFO - Counters.log(589) |     Data-local map tasks=1  
    23. INFO - Counters.log(589) |     SLOTS_MILLIS_REDUCES=30036  
    24. INFO - Counters.log(587) |   File Output Format Counters   
    25. INFO - Counters.log(589) |     Bytes Written=61  
    26. INFO - Counters.log(587) |   FileSystemCounters  
    27. INFO - Counters.log(589) |     FILE_BYTES_READ=93  
    28. INFO - Counters.log(589) |     HDFS_BYTES_READ=179  
    29. INFO - Counters.log(589) |     FILE_BYTES_WRITTEN=218396  
    30. INFO - Counters.log(589) |     HDFS_BYTES_WRITTEN=61  
    31. INFO - Counters.log(587) |   File Input Format Counters   
    32. INFO - Counters.log(589) |     Bytes Read=68  
    33. INFO - Counters.log(587) |   Map-Reduce Framework  
    34. INFO - Counters.log(589) |     Map output materialized bytes=93  
    35. INFO - Counters.log(589) |     Map input records=7  
    36. INFO - Counters.log(589) |     Reduce shuffle bytes=93  
    37. INFO - Counters.log(589) |     Spilled Records=14  
    38. INFO - Counters.log(589) |     Map output bytes=61  
    39. INFO - Counters.log(589) |     Total committed heap usage (bytes)=207491072  
    40. INFO - Counters.log(589) |     CPU time spent (ms)=2650  
    41. INFO - Counters.log(589) |     Combine input records=0  
    42. INFO - Counters.log(589) |     SPLIT_RAW_BYTES=111  
    43. INFO - Counters.log(589) |     Reduce input records=7  
    44. INFO - Counters.log(589) |     Reduce input groups=7  
    45. INFO - Counters.log(589) |     Combine output records=0  
    46. INFO - Counters.log(589) |     Physical memory (bytes) snapshot=422174720  
    47. INFO - Counters.log(589) |     Reduce output records=7  
    48. INFO - Counters.log(589) |     Virtual memory (bytes) snapshot=2935713792  
    49. INFO - Counters.log(589) |     Map output records=7  

    运行后的结果文件如下: 
     

    其中,part-r-000000里面的数据 
    Java代码  收藏代码
    1. 中国人 4  
    2. 河南省 1  


    其中,part-r-000001里面的数据 
    Java代码  收藏代码
    1. 中   11  
    2. 大   1  
    3. 小   3  



    其中,part-r-000002里面的数据
    Java代码  收藏代码
    1. 中国  3  
    2. 河南  2  

    至此,我们使用自定义的分区策略完美的实现了,数据分区了。 


    总结:引用一段话 

       (Partition)分区出现的必要性,如何使用Hadoop产生一个全局排序的文件?最简单的方法就是使用一个分区,但是该方法在处理大型文件时效率极低,因为一台机器必须处理所有输出文件,从而完全丧失了MapReduce所提供的并行架构的优势。事实上我们可以这样做,首先创建一系列排好序的文件;其次,串联这些文件(类似于归并排序);最后得到一个全局有序的文件。主要的思路是使用一个partitioner来描述全局排序的输出。比方说我们有1000个1-10000的数据,跑10个ruduce任务, 如果我们运行进行partition的时候,能够将在1-1000中数据的分配到第一个reduce中,1001-2000的数据分配到第二个reduce中,以此类推。即第n个reduce所分配到的数据全部大于第n-1个reduce中的数据。这样,每个reduce出来之后都是有序的了,我们只要cat所有的输出文件,变成一个大的文件,就都是有序的了 

    基本思路就是这样,但是现在有一个问题,就是数据的区间如何划分,在数据量大,还有我们并不清楚数据分布的情况下。一个比较简单的方法就是采样,假如有一亿的数据,我们可以对数据进行采样,如取10000个数据采样,然后对采样数据分区间。在Hadoop中,patition我们可以用TotalOrderPartitioner替换默认的分区。然后将采样的结果传给他,就可以实现我们想要的分区。在采样时,我们可以使用hadoop的几种采样工具,RandomSampler,InputSampler,IntervalSampler。 

           这样,我们就可以对利用分布式文件系统进行大数据量的排序了,我们也可以重写Partitioner类中的compare函数,来定义比较的规则,从而可以实现字符串或其他非数字类型的排序,也可以实现二次排序乃至多次排序。 
  • 相关阅读:
    HD200703
    并查集——合作网络D306
    并查集——易爆物D305
    NOIP初赛知识点
    离散化
    wsl2启动时无法添加自启动和加载环境变量的解决办法
    wsl2上用docker部署运行环境,mvn构建镜像找不到dockerhost
    docker 创建自己的镜像仓库
    单机多容器管理(已转k8s工具,仅参考) docker compose简介+使用+遇到的坑
    ubuntu下载安装jdk+maven,配置ssh
  • 原文地址:https://www.cnblogs.com/jamesf/p/4751527.html
Copyright © 2020-2023  润新知