最近做了一个小的mapreduce程序,主要目的是计算环比值最高的前5名,本来打算使用spark计算,可是本人目前spark还只是简单看了下,因此就先改用mapreduce计算了,今天和大家分享下这个例子,也算是对自己写的程序的总结了。

  首先解释下环比,例如我们要算本周的环比,那么计算方式就是本周的数据和上周数字的差值除以上周数值就是环比了,如果是月的环比就是本月和上月数据的差值除以上月数字就是本月环比了。不过本mapreduce实例不会直接算出比值,只是简单求出不同时间段数值的差值,最终环比结果由业务系统进行运算了。

  下面看看本人构造的测试数据了,测试数据分成两个文件,文件一的内容如下所示:

guanggu,1;90
hongshan,1;80
xinzhou,1;70
wuchang,1;95
hankou,1;85
hanyang,1;75

  第二个文件的测试数据如下:

guanggu,;
hongshan,;
xinzhou,;
wuchang,;
hankou,;
hanyang,;

  这里每行第一列的字段就是key了,key和value使用逗号分割,1;90是value值,value值包含两个内容,1为时间段标记,90就是数值,大家可以看到同一个key会有两个不同的时间段(使用1和2来标记)。

  Mapreduce的运算逻辑如下:首先第一步我们要求出环比数值,第二步就是排序了,做这个算法我曾考虑许久就是想把求环比值和排序两个过程合并,但是最后发现很难做到,只好将整个运算过程拆分成两个不同mapreduce,第一个mapreduce计算环比,第二个进行排序,二者是迭代关系。这里解释下分成两个mapreduce原因吧,主要原因就是最原始数据很难把两个不同时间段的数据按照key合并在一起变成一行数据,因此mapreduce计算时候必须有一个过程就是执行相同key合并操作,因此不得不分成两个步骤完成计算。

  接下来就是具体代码了,首先是第一个mapreduce,用来计算环比值的mapreduce了,它的map实现代码如下:

import java.io.IOException;

import org.apache.hadoop.io.Text;
// 使用输入为object,text,输出为Text,Text的数据结构,Object其实是行号,在本计算里意义不大,Text就是每行的内容
public class MrByAreaHBMap extends org.apache.hadoop.mapreduce.Mapper<Object, Text, Text, Text>{ private static String firstSeparator = ",";//每行的key和value值使用逗号分割 @Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
/* 本map的逻辑非常简单,就是从行里拆分key和value,对于有些初学者可能疑惑,我们到底如何让相同的key合并在一起了?这个就要看reduce计算了*/
Text areaKey = new Text();// reduce输入是Text类型
Text areaVal = new Text();// reduce输入是Text类型
String line = value.toString();
if (line != null && !line.equals("")){
String[] arr = line.split(firstSeparator); areaKey.set(arr[0]);
areaVal.set(arr[1]); context.write(areaKey, areaVal);
} } }

  下面是reduce代码了,具体如下:

import java.io.IOException;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer; public class MrByAreaHBReduce extends Reducer<Text, Text, Text, Text>{ private static String firstSeparator = ";";
private static String preFlag = "1";
private static String nextFlag = "2"; /*reduce的输入也是key,value的形式,不过这个输入是会将map里相同的key的值进行合并,合并形式就是一个数组形式,不过reduce方法里是通过迭代器进行数值处理*/
@Override
protected void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
int num1 = 0,num2 = 0,hbNum = 0;
for(Text value : values){
String inVal = value.toString();
String[] arr = inVal.split(firstSeparator);
// 下面的逻辑是通过不同时间段标记获取不同时间段数值
if (arr[0].equals(preFlag)){
num1 = Integer.valueOf(arr[1]);
}
if (arr[0].equals(nextFlag)){
num2 = Integer.valueOf(arr[1]);
}
}
hbNum = num1 - num2;// 这里计算环比
Text valueText = new Text();
valueText.set(hbNum + "");
Text retKey = new Text();
/* 对reduce的key进行了修改,将原来key和各个时间段的数值合并在一起,这样读取计算结果时候就可以读取到原始计算数据了,这是key,value计算模式可以简陋的无奈之举*/
retKey.set(key.toString() + firstSeparator + num1 + firstSeparator + num2);
context.write(valueText,retKey);
} }

  求环比的mapredue代码介绍结束了,下面是排序的算法,排序算法更加简单,在计算环比的mapreduce输出里我将环比值和原始key进行了互换,然后输出到结果文件里,这个结果文件就是第二个mapreduce的输入了,下面我们就要对这个新key进行排序,mapredcue计算模型里从map到reduce有默认的排序机制,如果map输出的key是字符类型那么排序规则就是按照字典进行排序,如果key是数字,那么就会按照数字由小到大进行排序,下面就是排序mapreduce的具体算法,map代码如下:

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper; public class MrByAreaSortMap extends
Mapper<LongWritable, Text, IntWritable, Text> {
/* 我们需要的排序是按照key的数值排序,不过这个排序是map的输出才做的,因此代码里输出的key使用了IntWritable类型
其实排序的map逻辑非常简单就是保证map的输出key是数字类型即可
*/
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String line = value.toString();
/*reduce的输出结果文件格式是按照空格分隔的,不过也搞不清有几个空格,或者是tab分割了,这里使用正则表达式s+就不怕多少空格和tab了*/
String[] arr = line.split("\\s+");
IntWritable outputKey = new IntWritable(Integer.valueOf(arr[0]));
Text outputValue = new Text();
outputValue.set(arr[1]);
context.write(outputKey, outputValue);
}
}

  reduce代码如下:

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer; /* reduce代码很让人吃惊吧,就是把map结果原样输出即可 */
public class MrByAreaSortReduce extends
Reducer<IntWritable, Text, IntWritable, Text> { @Override
protected void reduce(IntWritable key, Iterable<Text> values,
Context context) throws IOException, InterruptedException {
for (Text text : values){
context.write(key, text);
}
} }

  代码里的注释对代码逻辑进行了详细的解释,这里就不累述了。

  下面就是调用两个mapreduce的main函数了,也就是我们该如何执行mapreduce的方式,这个main函数还是非常有特点的,特点一就是两个mapreduce有迭代关系,具体就是第一个mapredcue执行完毕后第二个mapredcue才能执行,或者说第一个mapredcue的输出就是第二个mapredcue的输入,特点二就是排序计算里我们使用了map到reduce过程及shuffle过程里的默认排序机制,那么该机制运用可不是像mapreduce代码那么简单了,其实背后需要我们更加深入理解mapreduce的原理,这里我们直接看代码了,代码如下:

mport java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.jobcontrol.ControlledJob;
import org.apache.hadoop.mapreduce.lib.jobcontrol.JobControl;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class MrByAreaJob {
public static void main(String[] args) throws IOException {
// 一个mapreduce就是一个job 一个job需要一个单独的Configuration,我开始让两个job公用Configuration,最后mr报错
Configuration conf01 = new Configuration();
ControlledJob conJobHB = new ControlledJob(conf01); // 下面代码很多文章里都会提到这里就不多说了
Job jobHB = new Job(conf01,"hb");
jobHB.setJarByClass(MrByAreaJob.class);
jobHB.setMapperClass(MrByAreaHBMap.class);
jobHB.setReducerClass(MrByAreaHBReduce.class);
jobHB.setMapOutputKeyClass(Text.class);
jobHB.setMapOutputValueClass(Text.class);
jobHB.setOutputKeyClass(Text.class);
jobHB.setOutputValueClass(Text.class); conJobHB.setJob(jobHB); FileInputFormat.addInputPath(jobHB, new Path(args[0]));
FileOutputFormat.setOutputPath(jobHB, new Path(args[1])); Configuration conf02 = new Configuration();
Job jobSort = new Job(conf02,"sort");
jobSort.setJarByClass(MrByAreaJob.class);
jobSort.setMapperClass(MrByAreaSortMap.class);
jobSort.setReducerClass(MrByAreaSortReduce.class);
// Partitioner是shuffle的一个步骤,一个Partitioner对应一个reduce
// 假如这个mapredue有多个reduce,我们如何保证排序的全局一致性,因此这里需要进行处理
jobSort.setPartitionerClass(PartitionByArea.class);
// map对数值排序默认是由小到大,但是需求是由大到小,因此需要我们改变这种排序
jobSort.setSortComparatorClass(IntKeyComparator.class);
jobSort.setMapOutputKeyClass(IntWritable.class);
jobSort.setMapOutputValueClass(Text.class); jobSort.setOutputKeyClass(IntWritable.class);
jobSort.setOutputValueClass(Text.class); ControlledJob conJobSort = new ControlledJob(conf02);
conJobSort.setJob(jobSort); // 这里添加job的依赖关系
conJobSort.addDependingJob(conJobHB); // 可以看到第一个mapreduce的输出就是第二个的输入
FileInputFormat.addInputPath(jobSort, new Path(args[1]));
FileOutputFormat.setOutputPath(jobSort, new Path(args[2])); // 主控制job
JobControl mainJobControl = new JobControl("mainHBSort"); mainJobControl.addJob(conJobHB);
mainJobControl.addJob(conJobSort); Thread t = new Thread(mainJobControl);
t.start(); while(true){
if (mainJobControl.allFinished()){
System.out.println(mainJobControl.getSuccessfulJobList());
mainJobControl.stop();
break;
}
}
}
}

  这里有两个类还没有介绍,一个是IntKeyComparator,这是为了保证排序的mapreduce结果是按数字由大到小排序,代码如下:

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator; public class IntKeyComparator extends WritableComparator { protected IntKeyComparator() {
super(IntWritable.class,true);
} @Override
public int compare(WritableComparable a, WritableComparable b) {
return -super.compare(a, b);
} }

  另一个类就是PartitionByArea,这个是保证排序不会因为reduce设置的个数而不能保证排序的全局一致性,代码具体如下:

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner; public class PartitionByArea<IntWritable, Text> extends Partitioner<IntWritable, Text> { @Override
public int getPartition(IntWritable key, Text value, int numReduceTasks) {
int maxValue = 50;
int keySection = 0; // numReduceTasks就是默认的reduce任务个数
if (numReduceTasks > 1 && key.hashCode() < maxValue){
int sectionValue = maxValue / (numReduceTasks - 1);
int count = 0;
while((key.hashCode() - sectionValue * count) > sectionValue){
count++;
}
keySection = numReduceTasks - 1 - count;
} return keySection;
} }

  这里特别要讲解的是PartitionByArea,这个原理我花了好一段时间才理解过来,partition是map输出为reduce对应做的分区,一般一个partition对应一个reduce,如果我们将reduce任务槽设置为一个那么就不用更改Partition类,但是实际生产情况下reduce往往会配置多个,这个时候保证数据的整体排序就十分重要了,那么我们如何保证其数据的整体有序了,这个时候我们要找到输入数据的最大值,然后让最大值除以partition的数量的商值作为分割数据的边界,这样等分就可以保证数据的整体排序的有效性了。

  现在所有的代码都介绍完毕了,下面就是我们该如何让这个代码运行了,我在写本代码时候使用的是ide是eclipse,不过我没有使用mapreduce插件,而是直接放在服务器上运行,下面我来描述下运行该mr的方式,具体如下:

  首先我在装有hadoop服务的服务器上使用root用户创建一个属于我自己的文件夹,这里文件夹的名字叫做xiajun,我通过ftp将源文件传递到xiajun目录下的javafile文件夹,执行如下命令:

mkdir /xiajun/javafile
javac –classpath /home/hadoop/hadoop/hadoop-core-0.20.2-cdh3u4.jar –d /xiajun/javaclass /xiajun/ javafile/*.java

  以上命令是编译源文件,将javafile文件夹的java代码编译到javaclass目录下。

  

Jar –cvf /xiajun/mymr.jar –C /xiajun/javaclass/ .

  这里将javaclass目录下class文件打成jar包,放在xiajun目录下。

  接下来我们使用hadoop用户登录:

su – hadoop

  之所以使用root用户编译,打jar包原因是我的hadoop用户没有权限上传文件不得已而为之了。

  我们首先将测试数据上传到HDFS上,接下来执行如下命令:

cd /hadoop/bin

  切换目录到bin目录下,然后执行:

hadoop jar mymr.jar cn.com.TestMain  输入目录  输出目录

  这里输入可以是具体文件也可以是目录,输出目录在HDFS上要不存在,如果存在hadoop会无法确认任务是否已经执行完毕,就会强制终止任务。

  两个mapreduce迭代执行日志非常让人失望,因此如果我们发现任务无法正常执行,我现在都是一个个mapredcue执行查看错误日志。

  最后我们看看应用服务应该如何调用这个mapreduce程序,这里我使用远程调用shell 的方式,代码如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session; public class TestMain { /**
* @param args
* @throws IOException
* @throws ClassNotFoundException
* @throws InterruptedException
*/
public static void main(String[] args) {
String hostname = "192.168.1.200";
String username = "hadoop";
String pwd = "hadoop"; Connection conn = new Connection(hostname);
Session sess = null;
long begin = System.currentTimeMillis();
try {
conn.connect();
boolean isAuthenticated = conn.authenticateWithPassword(username, pwd);
sess = conn.openSession();
sess.execCommand("cd hadoop/bin && hadoop jar /xiajun/mymr.jar com.test.mr.MrByAreaJob /xiajun/areaHBinput /xiajun/areaHBoutput58 /xiajun/areaHBoutput68"); InputStream stdout = sess.getStdout();
BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
StringBuilder sb = new StringBuilder(); while(true){
String line = br.readLine();
if (line == null) break;
sb.append(line);
} System.out.println(sb.toString()); long end = System.currentTimeMillis();
System.out.println("耗时:" + (begin - end)/1000 + "秒");
} catch (IOException e) {
e.printStackTrace();
}finally{
sess.close();
conn.close();
}
} }

  好了,本文就此结束了。

使用mapreduce计算环比的实例的更多相关文章

  1. MapReduce——计算温度最大值 (基于全新2.2.0API)

    MapReduce——计算温度最大值 (基于全新2.2.0API) deprecated: Job类的所有Constructors, 新的API用静态方法getInstance(conf)来去的Job ...

  2. MapReduce工作机制——Word Count实例(一)

    MapReduce工作机制--Word Count实例(一) MapReduce的思想是分布式计算,也就是分而治之,并行计算提高速度. 编程思想 首先,要将数据抽象为键值对的形式,map函数输入键值对 ...

  3. MapReduce计算模型的优化

    MapReduce 计算模型的优化涉及了方方面面的内容,但是主要集中在两个方面:一是计算性能方面的优化:二是I/O操作方面的优化.这其中,又包含六个方面的内容. 1.任务调度 任务调度是Hadoop中 ...

  4. MapReduce计算模型二

    之前写过关于Hadoop方面的MapReduce框架的文章MapReduce框架Hadoop应用(一) 介绍了MapReduce的模型和Hadoop下的MapReduce框架,此文章将进一步介绍map ...

  5. MapReduce计算模型

    MapReduce计算模型 MapReduce两个重要角色:JobTracker和TaskTracker. ​ MapReduce Job 每个任务初始化一个Job,没个Job划分为两个阶段:Map和 ...

  6. (第4篇)hadoop之魂--mapreduce计算框架,让收集的数据产生价值

    摘要: 通过前面的学习,大家已经了解了HDFS文件系统.有了数据,下一步就要分析计算这些数据,产生价值.接下来我们介绍Mapreduce计算框架,学习数据是怎样被利用的. 博主福利 给大家赠送一套ha ...

  7. 简述MapReduce计算框架原理

    1. MapReduce基本编程模型和框架 1.1 MapReduce抽象模型 大数据计算的核心思想是:分而治之.如下图所示.把大量的数据划分开来,分配给各个子任务来完成.再将结果合并到一起输出.注: ...

  8. 组合式MapReduce计算作业

    1)迭代MapReduce计算任务,就是在一个循环内多次执行一个MapReduce. 2)顺序组合式MapReduce作业的执行 MapReduce1—>MapReduce2—>MapRe ...

  9. MapReduce计算每年最大值测试样例生成程序

    Demo.java package com.java; import java.io.BufferedWriter; import java.io.File; import java.io.FileW ...

随机推荐

  1. 【小程序分享篇 一 】开发了个JAVA小程序, 用于清除内存卡或者U盘里的垃圾文件非常有用

    有一种场景, 手机内存卡空间被用光了,但又不知道哪个文件占用了太大,一个个文件夹去找又太麻烦,所以我开发了个小程序把手机所有文件(包括路径下所有层次子文件夹下的文件)进行一个排序,这样你就可以找出哪个 ...

  2. [C#] C# 基础回顾 - 匿名方法

    C# 基础回顾 - 匿名方法 目录 简介 匿名方法的参数使用范围 委托示例 简介 在 C# 2.0 之前的版本中,我们创建委托的唯一形式 -- 命名方法. 而 C# 2.0 -- 引进了匿名方法,在 ...

  3. ASP.NET从零开始学习EF的增删改查

           ASP.NET从零开始学习EF的增删改查           最近辞职了,但是离真正的离职还有一段时间,趁着这段空档期,总想着写些东西,想来想去,也不是很明确到底想写个啥,但是闲着也是够 ...

  4. [内核笔记1]内核文件结构与缓存——inode和对应描述

    由来:公司内部外网记录日志的方式现在都是通过Nginx模块收到数据发送到系统消息队列,然后由另外一个进程来从消息队列读取然后写回磁盘这样的操作,尽量的减少Nginx的阻塞. 但是由于System/V消 ...

  5. 从零自学Hadoop(22):HBase协处理器

    阅读目录 序 介绍 Observer操作 示例下载 系列索引 本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 文章是哥(mephisto)写的,Sour ...

  6. POJ3693 Maximum repetition substring [后缀数组 ST表]

    Maximum repetition substring Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 9458   Acc ...

  7. MyEclipse对Maven的安装

    好记性不如烂笔头,记录一下. 操作系统:windows 7 MyEclipse2015 JDK1.7 maven的下载链接,点这里下载apache-maven-3.0.4-bin.tar.gz. 下载 ...

  8. requests源码阅读学习笔记

    0:此文并不想拆requests的功能,目的仅仅只是让自己以后写的代码更pythonic.可能会涉及到一部分requests的功能模块,但全看心情. 1.另一种类的初始化方式 class Reques ...

  9. 学习笔记:发现一个IE版本判断的好方法

    web开发就不得不面对浏览器兼容性问题,特别是IE的兼容问题.在前端代码中经常要处理一些兼容格式,为了解决这个问题网上找了找识别浏览器版本的方法.   常规js方法 找到一个方法,还不错,可以识别出各 ...

  10. Hyper-V上运行的Linux虚拟机验证是否安装了集成服务

    Hyper-V上运行的Linux虚拟机验证是否安装了集成服务 ps aux|grep "hv"root       311  0.0  0.0      0     0 ?     ...