转载:http://www.cnblogs.com/sharpxiajun/p/5205496.html

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

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

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

1
2
3
4
5
6
guanggu,1;90
hongshan,1;80
xinzhou,1;70
wuchang,1;95
hankou,1;85
hanyang,1;75

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

guanggu,2;66
hongshan,2;68
xinzhou,2;88
wuchang,2;59
hankou,2;56
hanyang,2;38

  这里每行第一列的字段就是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();
}
} }

  好了,本文就此结束了。

使用2个MR计算的更多相关文章

  1. facebook Presto SQL分析引擎——本质上和spark无异,分解stage,task,MR计算

    Presto 是由 Facebook 开源的大数据分布式 SQL 查询引擎,适用于交互式分析查询,可支持众多的数据源,包括 HDFS,RDBMS,KAFKA 等,而且提供了非常友好的接口开发数据源连接 ...

  2. 大数据计算新贵Spark在腾讯雅虎优酷成功应用解析

    http://www.csdn.net/article/2014-06-05/2820089 摘要:MapReduce在实时查询和迭代计算上仍有较大的不足,目前,Spark由于其可伸缩.基于内存计算等 ...

  3. hive高阶1--sql和hive语句执行顺序、explain查看执行计划、group by生成MR

    hive语句执行顺序 msyql语句执行顺序 代码写的顺序: select ... from... where.... group by... having... order by.. 或者 from ...

  4. Hadoop 实现 TF-IDF 计算

    学习Hadoop 实现TF-IDF 算法,使用的是CDH5.13.1 VM版本,Hadoop用的是2.6.0的jar包,Maven中增加如下即可 <dependency> <grou ...

  5. Hadoop-MR[会用]MR程序的运行模式

    1.简介 现在很少用到使用MR计算框架来实现功能,通常的做法是使用hive等工具辅助完成.但是对于其底层MR的原理还是有必要做一些了解. 2.MR客户端程序实现套路 这一小节总结归纳编写mr客户端程序 ...

  6. 一点做用户画像的人生经验(一):ID强打通

    1. 背景 在构建精准用户画像时,面临着这样一个问题:日志采集不能成功地收集用户的所有ID,且每条业务线有各自定义的UID用来标识用户,从而造成了用户ID的零碎化.因此,为了做用户标签的整合,用户ID ...

  7. hadoop1.2.1配置文件

    1)core-site.xml <?xml version="1.0"?> <?xml-stylesheet type="text/xsl" ...

  8. InAction-MR的topK

    本来只是想拿搜狗的数据练练手的,却无意踏足MR的topK问题.经过几番波折,虽然现在看起来很简单,但是摸爬滚打中也学到了不少 数据是搜狗实验室下的搜索日志,格式大概为: 00:00:00 298219 ...

  9. Azure HDInsight与Hadoop周边系统集成

     Sunwei 9 Dec 2014 1:54 AM 传统的Hadoop系统提供给用户2个非常优秀的框架,MR计算框架和HDFS存储框架,尽管MR已经显得有些老迈而缓慢,但是HDFS还是很多应用系统的 ...

随机推荐

  1. MongoDB资料汇总

    MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. 它的特点是高性能.易部署.易使用,存储数据非常方便.主要功能特性有: 面向集合存 ...

  2. iOS - Swift NSDate 时间

    前言 NSDate public class NSDate : NSObject, NSCopying, NSSecureCoding NSDate 用来表示公历的 GMT 时间(格林威治时间).是独 ...

  3. Redis核心知识之—— 时延问题分析及应对、性能问题和解决方法【★★★★★】

    参考网址: Redis时延问题分析及应对:http://www.cnblogs.com/me115/p/5032177.html Redis常见的性能问题和解决方法:http://www.search ...

  4. CentOS用yum快速安装nginx

    增加nginx源 vim  /etc/yum.repos.d/nginx.repo [nginx] name=nginx repo baseurl=http://nginx.org/packages/ ...

  5. android模拟器中文乱码

    问题:在xml文件中设置的中文能正确输出,但是在java文件中设置的中文会在模拟器上呈现乱码 解决方案:在build.gradle文件中添加一行代码 android {compileOptions.e ...

  6. iOS开发之用Xcode 在真机上截屏与模拟器截屏

    一.真机截屏 1.打开Xcode 6 2.在xcode 选择模拟器或者真机设备的地方选中你的真机 3.Debug-->View Debugging-->Take Screenshot of ...

  7. B树索引

    在SQL Server中,索引是一种增强式的存在,这意味着,即使没有索引,SQL Server仍然可以实现应有的功能.但索引可以在大多数情况下大大提升查询性能高.在OLAP中尤其明显,要完全理解索引的 ...

  8. 转:copy initialization

    转自: http://en.cppreference.com/w/cpp/language/copy_initialization copy initialization   C++   C++ la ...

  9. MyBatis学习笔记(二) 关联关系

    首先给大家推荐几个网页: http://blog.csdn.net/isea533/article/category/2092001 没事看看 - MyBatis工具:www.mybatis.tk h ...

  10. this和call

    function foo(x){ console.log(x);} foo.call(this,'abc');console.log(this); ---- abc