您的位置:首页 > 其它

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

2016-04-08 10:56 295 查看
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() 函数,提取工作年限和薪资

/*

* 提取数据

*/

public static class SalaryMapper extends Mapper<LongWritable, Text, Text, Text>{

public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException

{

String[] valArr = value.toString().split("\\s+");

if(valArr.length > 2){

/*

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

*

* 查看原始数据,我们只需提取第二字段、第三个字段

*/

// 工作年限

String workAge = valArr[1];

// 薪资

String salary = valArr[2];

context.write(new Text(workAge), new Text(salary));

}

}

}


这个 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函数,统计不同工作年限的薪资水平。

/*

* 统计数据

*/

public static class SalaryReduce extends Reducer<Text, Text, Text, Text>{

public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException{

// 最低工薪

int low = 0;

// 最高薪资

int hight = 0;

// 计数功能

int count = 0;

for(Text value : values){

String text = value.toString();

/*

* 15-30k

* 薪资中的分割符是"-"

*/

String[] strArr = text.split("-");

int tlow = filterSalary(strArr[0]);

int thight = filterSalary(strArr[1]);

if(count == 0 || low > tlow){

low = tlow;

}

if(count == 0 || hight < thight){

hight = thight;

}

count ++;

}

context.write(key, new Text(low + "-" + hight + "k"));

}

private int filterSalary(String salary){

String sal = Pattern.compile("[^0-9]").matcher(salary).replaceAll("");

return Integer.parseInt(sal);

}

}


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

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

public int run(String[] arg0) throws Exception {

// 读取配置文件

Configuration conf = new Configuration();

// 如果输出目录存在,将其删除

Path path = new Path(arg0[1]);

FileSystem fileSystem = path.getFileSystem(conf);

if(fileSystem.isDirectory(path)){

fileSystem.delete(path, true);

}

// 创建Job对象

Job job = new Job(conf,"salary");

job.setJarByClass(Salary.class);

// 设置输入路径、输出路径

FileInputFormat.addInputPath(job, new Path(arg0[0]));

FileOutputFormat.setOutputPath(job, new Path(arg0[1]));

// 设置mapper、reduce

job.setMapperClass(SalaryMapper.class);

job.setReducerClass(SalaryReduce.class);

// 设置mapper、reduce的输出类型

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(Text.class);

return job.waitForCompletion(true) ? 0 : 1;

}


/**

* main 方法

*

* @param args

* @throws Exception

*/

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

// 数据输入路径和输出路径

String[] args0 = {

"hdfs://ljc:9000/buaa/salary/",

"hdfs://ljc:9000/buaa/salary/out/"

};

int ec = ToolRunner.run(new Configuration(), new Salary(), args0);

System.exit(ec);

}


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.其他问题

hadoop环境配置

基于Eclipse的hadoop开发环境配置

项目代码及数据

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。

如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。

如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【刘超★ljc】。

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: