• MapReduce实战:统计不同工作年限的薪资水平


    1.薪资数据集

        我们要写一个薪资统计程序,统计数据来自于互联网招聘hadoop岗位的招聘网站,这些数据是按照记录方式存储的,因此非常适合使用 MapReduce 程序来统计。

    2.数据格式

        我们使用的数据来自互联网招聘网站,其中每一行是一条记录。

        下面我们展示一行数据,其中重要的字段被突出显示。该行数据被分割成很多行以突出每个字段,但在实际文件中,这些字段被整合成一行

        美团

        3-5年经验 # 工作年限

        15-30k # 薪资

        北京

        【够牛就来】hadoop高级工程

    3.分析

        在这里,map阶段的输入是原始数据。我们选择文本格式作为输入格式,将数据集的每一行作为文本输入。键是某一行起始位置相对于文件起始位置的偏移量,不过我们不需要这个信息,所以将其忽略。

        我们的map函数很简单。由于我们只对工作年限和薪资感兴趣,所以只需要取出这两个字段数据。在本实战中,map 函数只是一个数据提取阶段,通过这种方式来提取数据,使 reducer 函数继续对它进行处理:即统计不同工作年限的薪资水平

        为了全面了解 map 的工作方式,输入以下数据作为演示

        美团 3-5年经验 15-30k 北京 【够牛就来】hadoop高级工程...

        北信源 3-5年经验 15-20k 北京 Java高级工程师(有Hadoo...

        蘑菇街 3-5年经验 10-24k 杭州 hadoop开发工程师

        晶赞科技 1-3年经验 10-30k 上海 hadoop研发工程师

        秒针系统 3-5年经验 10-20k 北京 hadoop开发工程师

        搜狐 1-3年经验 18-23k 北京 大数据平台开发工程师(Hadoo...

        执御 1-3年经验 8-14k 杭州 hadoop工程师

        KK唱响 3-5年经验 15-30k 杭州 高级hadoop开发工程师

        晶赞科技 1-3年经验 12-30k 上海 高级数据分析师(hadoop)

        亿玛在线 3-5年经验 18-30k 北京 hadoop工程师

        酷讯 1-3年经验 10-20k 北京 hadoop Engineer/...

        游族网络 5-10年经验 20-35k 上海 hadoop研发工程师

        易车公司 3-5年经验 15-30k 北京 hadoop工程师

        爱图购 1-3年经验 8-15k 杭州 hadoop开发工程师

        晶赞科技 3-5年经验 15-33k 上海 hadoop研发工程师

    这些数据,以键/值对的方式作为map函数的输入,如下所示

        0, 美团 3-5年经验 15-30k 北京 【够牛就来】hadoop高级工程...

        84, 北信源 3-5年经验 15-20k 北京 Java高级工程师(有Hadoo...

        163, 蘑菇街 3-5年经验 10-24k 杭州 hadoop开发工程师

        231 ,晶赞科技 1-3年经验 10-30k 上海 hadoop研发工程师

        303, 秒针系统 3-5年经验 10-20k 北京 hadoop开发工程师

        375, 搜狐 1-3年经验 18-23k 北京 大数据平台开发工程师

        461, 执御 1-3年经验 8-14k 杭州 hadoop工程师

        521, KK唱响 3-5年经验 15-30k 杭州 高级hadoop开发工程师

        593, 晶赞科技 1-3年经验 12-30k 上海 高级数据分析师(hadoop)

        677, 亿玛在线 3-5年经验 18-30k 北京 hadoop工程师

        774, 酷讯 1-3年经验 10-20k 北京 hadoop Engineer/...

        838, 游族网络 5-10年经验 20-35k 上海 hadoop研发工程师

        910, 易车公司 3-5年经验 15-30k 北京 hadoop工程师

        976, 爱图购 1-3年经验 8-15k 杭州 hadoop开发工程师

        1043,晶赞科技 3-5年经验 15-33k 上海 hadoop研发工程师

    键(key)是文件中的偏移量,这里不需要这个信息,所以将其忽略。map 函数的功能仅限于提取工作经验和薪资,并将它们输出;map 函数的输出经由 MapReduce 框架处理后,最后发送到reduce函数。这个处理过程基于键来对键值对进行排序和分组。因此在这个示例中,reduce 函数看到的是如下输入:

        1-3年经验,[12-30k, 10-30k, 8-14k, 18-23k, 10-20k, 8-15k]

        3-5年经验,[15-33k, 15-30k, 18-30k, 15-30k, 10-20k, 10-24k, 15-20k, 15-30k]

        5-10年经验,[20-35k]

        每个工作年限后面紧跟着一系列薪资数据,reduce 函数现在要做的是遍历整个列表并统计薪资:

        1-3年经验    8-30k

        3-5年经验    10-33k

        5-10年经验    20-35k

        上面就是最终输出结果即统计不同工作年限的薪资水平

    4.实现

    上面已经分析完毕,下面我们就着手实现它。这里需要编写三块代码内容

    1、map 函数、

    2、reduce函数

    3、一些用来运行作业的代码。

     

    1、map 函数

    下面我们来编写 Mapper 类,实现 map() 函数,提取工作年限和薪资

     1 /*
     2 
     3 * 提取数据
     4 
     5 */
     6 
     7 public static class SalaryMapper extends Mapper<LongWritable, Text, Text, Text>{
     8 
     9     public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException
    10 
    11     {
    12 
    13         String[] valArr = value.toString().split("\s+");
    14 
    15         
    16 
    17         if(valArr.length > 2){
    18 
    19             /*
    20 
    21              * 美团 3-5年经验 15-30k 北京 【够牛就来】hadoop高级工程
    22 
    23              *
    24 
    25              * 查看原始数据,我们只需提取第二字段、第三个字段
    26 
    27              */
    28 
    29             // 工作年限
    30 
    31             String workAge = valArr[1];
    32 
    33             // 薪资
    34 
    35             String salary = valArr[2];
    36 
    37             
    38 
    39             context.write(new Text(workAge), new Text(salary));
    40 
    41         }
    42 
    43     }
    44 
    45 }

    这个 Mapper 类是一个泛型类型,它有四个形参类型,分别指定 map 函数的输入键、输入值、输出键和输出值的类型。 就本示例来说,输入键是一个长整数偏移量,输入值是一行文本,输出键是工作年限,输出值是薪资。Hadoop 本身提供了一套可优化网络序列化传输的基本类型,而不是使用 java 内嵌的类型。这些类型都在 org.apache.hadoop.io 包中。 这里使用 LongWritable 类型(相当于 Java 的 Long 类型)、Text 类型(相当于 Java 中的 String 类型)

    map() 方法的输入是一个键(key)和一个值(value),我们首先将 Text 类型的 value 转换成 Java 的 String 类型, 之后使用 split方法分割数据,然后提取我们需要的值。map() 方法还提供了 Context 实例用于将输出内容进行写入。 在这种情况下,我们将工作年限、薪资封装成Text对象,并将它们进行写入。

    2、reduce函数

    下面我们来编写 Reducer类,实现reduce函数,统计不同工作年限的薪资水平。

     

     1 /*
     2 
     3 * 统计数据
     4 
     5 */
     6 
     7 public static class SalaryReduce extends Reducer<Text, Text, Text, Text>{
     8 
     9     public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException{
    10 
    11         // 最低工薪
    12 
    13         int low = 0;
    14 
    15         // 最高薪资
    16 
    17         int hight = 0;
    18 
    19         // 计数功能
    20 
    21         int count = 0;
    22 
    23         
    24 
    25         for(Text value : values){
    26 
    27             String text = value.toString();
    28 
    29             /*
    30 
    31              * 15-30k
    32 
    33              * 薪资中的分割符是"-"
    34 
    35              */
    36 
    37             String[] strArr = text.split("-");
    38 
    39             
    40 
    41             int tlow = filterSalary(strArr[0]);
    42 
    43             int thight = filterSalary(strArr[1]);
    44 
    45             
    46 
    47             if(count == 0 || low > tlow){
    48 
    49                 low = tlow;
    50 
    51             }
    52 
    53             
    54 
    55             if(count == 0 || hight < thight){
    56 
    57                 hight = thight;
    58 
    59             }
    60 
    61             
    62 
    63             count ++;
    64 
    65         }
    66 
    67         
    68 
    69         context.write(key, new Text(low + "-" + hight + "k"));
    70 
    71     }
    72 
    73       
    74 
    75     private int filterSalary(String salary){
    76 
    77         String sal = Pattern.compile("[^0-9]").matcher(salary).replaceAll("");
    78 
    79         return Integer.parseInt(sal);
    80 
    81     }
    82 
    83 }

    同样,reduce 函数也有四个形式参数类型用于指定输入和输出类型。reduce 函数的输入类型必须匹配 map 函数的输出类型:即 Text 类型和Text类型。 在这种情况下,reduce 函数的输出类型也是 Text 和 Text 类型,分别是工作年限和薪资。在 map 的输出结果中,所有相同的工作年限(key)被分配到同一个reduce执行,这个薪资就是针对同一个工作年限(key),通过循环所有薪资(values)来求匹配所有薪资的薪资水平。

    3、一些用来运行作业的代码

     1 public int run(String[] arg0) throws Exception {
     2 
     3     // 读取配置文件
     4 
     5     Configuration conf = new Configuration();
     6 
     7       
     8 
     9     // 如果输出目录存在,将其删除
    10 
    11     Path path = new Path(arg0[1]);
    12 
    13     FileSystem fileSystem = path.getFileSystem(conf);
    14 
    15     if(fileSystem.isDirectory(path)){
    16 
    17         fileSystem.delete(path, true);
    18 
    19     }
    20 
    21       
    22 
    23     // 创建Job对象
    24 
    25     Job job = new Job(conf,"salary");
    26 
    27     job.setJarByClass(Salary.class);
    28 
    29       
    30 
    31     // 设置输入路径、输出路径
    32 
    33     FileInputFormat.addInputPath(job, new Path(arg0[0]));
    34 
    35     FileOutputFormat.setOutputPath(job, new Path(arg0[1]));
    36 
    37       
    38 
    39     // 设置mapper、reduce
    40 
    41     job.setMapperClass(SalaryMapper.class);
    42 
    43     job.setReducerClass(SalaryReduce.class);
    44 
    45       
    46 
    47     // 设置mapper、reduce的输出类型
    48 
    49     job.setOutputKeyClass(Text.class);
    50 
    51     job.setOutputValueClass(Text.class);
    52 
    53       
    54 
    55     return job.waitForCompletion(true) ? 0 : 1;
    56 
    57 }
     1 /**
     2 
     3 * main 方法
     4 
     5 *
     6 
     7 * @param args
     8 
     9 * @throws Exception
    10 
    11 */
    12 
    13 public static void main(String[] args) throws Exception {
    14 
    15     // 数据输入路径和输出路径
    16 
    17     String[] args0 = {
    18 
    19             "hdfs://ljc:9000/buaa/salary/",
    20 
    21             "hdfs://ljc:9000/buaa/salary/out/"
    22 
    23     };
    24 
    25       
    26 
    27     int ec = ToolRunner.run(new Configuration(), new Salary(), args0);
    28 
    29       
    30 
    31     System.exit(ec);
    32 
    33 }

    Configuration 类读取 Hadoop 的配置文件,如 site-core.xml、mapred-site.xml、hdfs-site.xml 等。

    Job 对象指定作业执行规范,我们可以用它来控制整个作业的运行。我们在 Hadoop 集群上运行这个作业时,要把代码打包成一个JAR文件(Hadoop在集群上发布这个文件)。 不必明确指定 JAR 文件的名称,在 Job 对象的 setJarByClass 方法中传递一个类即可,Hadoop 利用这个类来查找包含它的 JAR 文件,进而找到相关的 JAR 文件。

    构造 Job 对象之后,需要指定输入和输出数据的路径。

    • 调用 FileInputFormat 类的静态方法 addInputPath() 来定义输入数据的路径,这个路径可以是单个的文件、一个目录(此时,将目录下所有文件当作输入)或符合特定文件模式的一系列文件。由函数名可知,可以多次调用 addInputPath() 来实现多路径的输入。
    • 调用 FileOutputFormat 类中的静态方法 setOutputPath() 来指定输出路径(只能有一个输出路径)。这个方法指定的是 reduce 函数输出文件的写入目录。 在运行作业前该目录是不应该存在的,否则 Hadoop 会报错并拒绝运行作业。这种预防措施的目的是防止数据丢失(长时间运行的作业如果结果被意外覆盖,肯定是件可怕的事情)。
    • 通过 setMapperClass() 和 setReducerClass() 指定 map 类型和reduce 类型。
    • 通过setOutputKeyClass() 和 setOutputValueClass() 控制 map 和 reduce 函数的输出类型,正如本例所示,这两个输出类型一般都是相同的。如果不同,则通过 setMapOutputKeyClass()和setMapOutputValueClass()来设置 map 函数的输出类型。
    • 输入的类型通过 InputFormat 类来控制,我们的例子中没有设置,因为使用的是默认的 TextInputFormat(文本输入格式)。
    • Job 中的 waitForCompletion() 方法提交作业并等待执行完成。该方法中的布尔参数是个详细标识,所以作业会把进度写到控制台。 waitForCompletion() 方法返回一个布尔值,表示执行的成(true)败(false),这个布尔值被转换成程序的退出代码 0 或者 1。

    5.结果

    6.其他问题

    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
    如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
    如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【刘超★ljc】。

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    火狐浏览器插件开发工具
    MyEclipse设置JSP页面默认编码方式
    如何让editplus保存时不生成.bak备份文件
    Sublime Text 2 使用心得
    火狐浏览器的一些常用设置
    能帮你提高工作效率的10个在线工具
    如何治疗颈椎病
    企业架构研究总结(2)——问题的由来和基本概念
    企业架构研究总结(1)——参考资料列表
    企业架构研究总结(4)——企业架构与企业架构框架概论
  • 原文地址:https://www.cnblogs.com/codeOfLife/p/5366954.html
Copyright © 2020-2023  润新知