一、概述

 
 
对于RDBMS中的join操作大伙一定非常熟悉,写sql的时候要十分注意细节,稍有差池就会耗时巨久造成很大的性能瓶颈,而在Hadoop中使用MapReduce框架进行join的操作时同样耗时,但是由于hadoop的分布式设计理念的特殊性,因此对于这种join操作同样也具备了一定的特殊性。本文主要对MapReduce框架对表之间的join操作的几种实现方式进行详细分析,并且根据我在实际开发过程中遇到的实际例子来进行进一步的说明。
 
 
二、实现原理

1、在Reudce端进行连接。
在Reudce端进行连接是MapReduce框架进行表之间join操作最为常见的模式,其具体的实现原理如下:
Map端的主要工作:为来自不同表(文件)的key/value对打标签以区别不同来源的记录。然后用连接字段作为key,其余部分和新加的标志作为value,最后进行输出。
reduce端的主要工作:在reduce端以连接字段作为key的分组已经完成,我们只需要在每一个分组当中将那些来源于不同文件的记录(在map阶段已经打标志)分开,最后进行笛卡尔只就ok了。原理非常简单,下面来看一个实例:
(1)自定义一个value返回类型:

  1. package com.mr.reduceSizeJoin;
  2. import java.io.DataInput;
  3. import java.io.DataOutput;
  4. import java.io.IOException;
  5. import org.apache.hadoop.io.Text;
  6. import org.apache.hadoop.io.WritableComparable;
  7. public class CombineValues implements WritableComparable<CombineValues>{
  8. //private static final Logger logger = LoggerFactory.getLogger(CombineValues.class);
  9. private Text joinKey;//链接关键字
  10. private Text flag;//文件来源标志
  11. private Text secondPart;//除了链接键外的其他部分
  12. public void setJoinKey(Text joinKey) {
  13. this.joinKey = joinKey;
  14. }
  15. public void setFlag(Text flag) {
  16. this.flag = flag;
  17. }
  18. public void setSecondPart(Text secondPart) {
  19. this.secondPart = secondPart;
  20. }
  21. public Text getFlag() {
  22. return flag;
  23. }
  24. public Text getSecondPart() {
  25. return secondPart;
  26. }
  27. public Text getJoinKey() {
  28. return joinKey;
  29. }
  30. public CombineValues() {
  31. this.joinKey =  new Text();
  32. this.flag = new Text();
  33. this.secondPart = new Text();
  34. }
  35. @Override
  36. public void write(DataOutput out) throws IOException {
  37. this.joinKey.write(out);
  38. this.flag.write(out);
  39. this.secondPart.write(out);
  40. }
  41. @Override
  42. public void readFields(DataInput in) throws IOException {
  43. this.joinKey.readFields(in);
  44. this.flag.readFields(in);
  45. this.secondPart.readFields(in);
  46. }
  47. @Override
  48. public int compareTo(CombineValues o) {
  49. return this.joinKey.compareTo(o.getJoinKey());
  50. }
  51. @Override
  52. public String toString() {
  53. // TODO Auto-generated method stub
  54. return "[flag="+this.flag.toString()+",joinKey="+this.joinKey.toString()+",secondPart="+this.secondPart.toString()+"]";
  55. }
  56. }

(2) map、reduce主体代码:

  1. package com.mr.reduceSizeJoin;
  2. import java.io.IOException;
  3. import java.util.ArrayList;
  4. import org.apache.hadoop.conf.Configuration;
  5. import org.apache.hadoop.conf.Configured;
  6. import org.apache.hadoop.fs.Path;
  7. import org.apache.hadoop.io.Text;
  8. import org.apache.hadoop.mapreduce.Job;
  9. import org.apache.hadoop.mapreduce.Mapper;
  10. import org.apache.hadoop.mapreduce.Reducer;
  11. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
  12. import org.apache.hadoop.mapreduce.lib.input.FileSplit;
  13. import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
  14. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
  15. import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
  16. import org.apache.hadoop.util.Tool;
  17. import org.apache.hadoop.util.ToolRunner;
  18. import org.slf4j.Logger;
  19. import org.slf4j.LoggerFactory;
  20. /**
  21. * @author zengzhaozheng
  22. * 用途说明:
  23. * reudce side join中的left outer join
  24. * 左连接,两个文件分别代表2个表,连接字段table1的id字段和table2的cityID字段
  25. * table1(左表):tb_dim_city(id int,name string,orderid int,city_code,is_show)
  26. * tb_dim_city.dat文件内容,分隔符为"|":
  27. * id     name  orderid  city_code  is_show
  28. * 0       其他        9999     9999         0
  29. * 1       长春        1        901          1
  30. * 2       吉林        2        902          1
  31. * 3       四平        3        903          1
  32. * 4       松原        4        904          1
  33. * 5       通化        5        905          1
  34. * 6       辽源        6        906          1
  35. * 7       白城        7        907          1
  36. * 8       白山        8        908          1
  37. * 9       延吉        9        909          1
  38. * -------------------------风骚的分割线-------------------------------
  39. * table2(右表):tb_user_profiles(userID int,userName string,network string,double flow,cityID int)
  40. * tb_user_profiles.dat文件内容,分隔符为"|":
  41. * userID   network     flow    cityID
  42. * 1           2G       123      1
  43. * 2           3G       333      2
  44. * 3           3G       555      1
  45. * 4           2G       777      3
  46. * 5           3G       666      4
  47. *
  48. * -------------------------风骚的分割线-------------------------------
  49. *  结果:
  50. *  1   长春  1   901 1   1   2G  123
  51. *  1   长春  1   901 1   3   3G  555
  52. *  2   吉林  2   902 1   2   3G  333
  53. *  3   四平  3   903 1   4   2G  777
  54. *  4   松原  4   904 1   5   3G  666
  55. */
  56. public class ReduceSideJoin_LeftOuterJoin extends Configured implements Tool{
  57. private static final Logger logger = LoggerFactory.getLogger(ReduceSideJoin_LeftOuterJoin.class);
  58. public static class LeftOutJoinMapper extends Mapper<Object, Text, Text, CombineValues> {
  59. private CombineValues combineValues = new CombineValues();
  60. private Text flag = new Text();
  61. private Text joinKey = new Text();
  62. private Text secondPart = new Text();
  63. @Override
  64. protected void map(Object key, Text value, Context context)
  65. throws IOException, InterruptedException {
  66. //获得文件输入路径
  67. String pathName = ((FileSplit) context.getInputSplit()).getPath().toString();
  68. //数据来自tb_dim_city.dat文件,标志即为"0"
  69. if(pathName.endsWith("tb_dim_city.dat")){
  70. String[] valueItems = value.toString().split("\\|");
  71. //过滤格式错误的记录
  72. if(valueItems.length != 5){
  73. return;
  74. }
  75. flag.set("0");
  76. joinKey.set(valueItems[0]);
  77. secondPart.set(valueItems[1]+"\t"+valueItems[2]+"\t"+valueItems[3]+"\t"+valueItems[4]);
  78. combineValues.setFlag(flag);
  79. combineValues.setJoinKey(joinKey);
  80. combineValues.setSecondPart(secondPart);
  81. context.write(combineValues.getJoinKey(), combineValues);
  82. }//数据来自于tb_user_profiles.dat,标志即为"1"
  83. else if(pathName.endsWith("tb_user_profiles.dat")){
  84. String[] valueItems = value.toString().split("\\|");
  85. //过滤格式错误的记录
  86. if(valueItems.length != 4){
  87. return;
  88. }
  89. flag.set("1");
  90. joinKey.set(valueItems[3]);
  91. secondPart.set(valueItems[0]+"\t"+valueItems[1]+"\t"+valueItems[2]);
  92. combineValues.setFlag(flag);
  93. combineValues.setJoinKey(joinKey);
  94. combineValues.setSecondPart(secondPart);
  95. context.write(combineValues.getJoinKey(), combineValues);
  96. }
  97. }
  98. }
  99. public static class LeftOutJoinReducer extends Reducer<Text, CombineValues, Text, Text> {
  100. //存储一个分组中的左表信息
  101. private ArrayList<Text> leftTable = new ArrayList<Text>();
  102. //存储一个分组中的右表信息
  103. private ArrayList<Text> rightTable = new ArrayList<Text>();
  104. private Text secondPar = null;
  105. private Text output = new Text();
  106. /**
  107. * 一个分组调用一次reduce函数
  108. */
  109. @Override
  110. protected void reduce(Text key, Iterable<CombineValues> value, Context context)
  111. throws IOException, InterruptedException {
  112. leftTable.clear();
  113. rightTable.clear();
  114. /**
  115. * 将分组中的元素按照文件分别进行存放
  116. * 这种方法要注意的问题:
  117. * 如果一个分组内的元素太多的话,可能会导致在reduce阶段出现OOM,
  118. * 在处理分布式问题之前最好先了解数据的分布情况,根据不同的分布采取最
  119. * 适当的处理方法,这样可以有效的防止导致OOM和数据过度倾斜问题。
  120. */
  121. for(CombineValues cv : value){
  122. secondPar = new Text(cv.getSecondPart().toString());
  123. //左表tb_dim_city
  124. if("0".equals(cv.getFlag().toString().trim())){
  125. leftTable.add(secondPar);
  126. }
  127. //右表tb_user_profiles
  128. else if("1".equals(cv.getFlag().toString().trim())){
  129. rightTable.add(secondPar);
  130. }
  131. }
  132. logger.info("tb_dim_city:"+leftTable.toString());
  133. logger.info("tb_user_profiles:"+rightTable.toString());
  134. for(Text leftPart : leftTable){
  135. for(Text rightPart : rightTable){
  136. output.set(leftPart+ "\t" + rightPart);
  137. context.write(key, output);
  138. }
  139. }
  140. }
  141. }
  142. @Override
  143. public int run(String[] args) throws Exception {
  144. Configuration conf=getConf(); //获得配置文件对象
  145. Job job=new Job(conf,"LeftOutJoinMR");
  146. job.setJarByClass(ReduceSideJoin_LeftOuterJoin.class);
  147. FileInputFormat.addInputPath(job, new Path(args[0])); //设置map输入文件路径
  148. FileOutputFormat.setOutputPath(job, new Path(args[1])); //设置reduce输出文件路径
  149. job.setMapperClass(LeftOutJoinMapper.class);
  150. job.setReducerClass(LeftOutJoinReducer.class);
  151. job.setInputFormatClass(TextInputFormat.class); //设置文件输入格式
  152. job.setOutputFormatClass(TextOutputFormat.class);//使用默认的output格格式
  153. //设置map的输出key和value类型
  154. job.setMapOutputKeyClass(Text.class);
  155. job.setMapOutputValueClass(CombineValues.class);
  156. //设置reduce的输出key和value类型
  157. job.setOutputKeyClass(Text.class);
  158. job.setOutputValueClass(Text.class);
  159. job.waitForCompletion(true);
  160. return job.isSuccessful()?0:1;
  161. }
  162. public static void main(String[] args) throws IOException,
  163. ClassNotFoundException, InterruptedException {
  164. try {
  165. int returnCode =  ToolRunner.run(new ReduceSideJoin_LeftOuterJoin(),args);
  166. System.exit(returnCode);
  167. catch (Exception e) {
  168. // TODO Auto-generated catch block
  169. logger.error(e.getMessage());
  170. }
  171. }
  172. }

其中具体的分析以及数据的输出输入请看代码中的注释已经写得比较清楚了,这里主要分析一下reduce join的一些不足。之所以会存在reduce join这种方式,我们可以很明显的看出原:因为整体数据被分割了,每个map task只处理一部分数据而不能够获取到所有需要的join字段,因此我们需要在讲join key作为reduce端的分组将所有join key相同的记录集中起来进行处理,所以reduce join这种方式就出现了。这种方式的缺点很明显就是会造成map和reduce端也就是shuffle阶段出现大量的数据传输,效率很低。

相关阅读:
Hadoop伪分布式搭建操作步骤指南》;
HADOOP的本地库(NATIVE LIBRARIES)简介》;
基于Hadoop大数据分析应用场景与项目实战演练

 

MapReduce多种join实现实例分析(一)的更多相关文章

  1. MapReduce多种join实现实例分析(二)

    上一篇<MapReduce多种join实现实例分析(一)>,大家可以点击回顾该篇文章.本文是MapReduce系列第二篇. 一.在Map端进行连接使用场景:一张表十分小.一张表很大.用法: ...

  2. hadoop中MapReduce多种join实现实例分析

    转载自:http://zengzhaozheng.blog.51cto.com/8219051/1392961 1.在Reudce端进行连接. 在Reudce端进行连接是MapReduce框架进行表之 ...

  3. python中列表元素连接方法join用法实例

    python中列表元素连接方法join用法实例 这篇文章主要介绍了python中列表元素连接方法join用法,实例分析了Python中join方法的使用技巧,非常具有实用价值,分享给大家供大家参考. ...

  4. Hive(六)hive执行过程实例分析与hive优化策略

    一.Hive 执行过程实例分析 1.join 对于 join 操作:SELECT pv.pageid, u.age FROM page_view pv JOIN user u ON (pv.useri ...

  5. Hadoop.2.x_高级应用_二次排序及MapReduce端join

    一.对于二次排序案例部分理解 1. 分析需求(首先对第一个字段排序,然后在对第二个字段排序) 杂乱的原始数据 排序完成的数据 a,1 a,1 b,1 a,2 a,2 [排序] a,100 b,6 == ...

  6. Mahout机器学习平台之聚类算法具体剖析(含实例分析)

    第一部分: 学习Mahout必需要知道的资料查找技能: 学会查官方帮助文档: 解压用于安装文件(mahout-distribution-0.6.tar.gz),找到例如以下位置.我将该文件解压到win ...

  7. Linux系统网络性能实例分析

    由于TCP/IP是使用最普遍的Internet协议,下面只集中讨论TCP/IP 栈和以太网(Ethernet).术语 LinuxTCP/IP栈和 Linux网络栈可互换使用,因为 TCP/IP栈是 L ...

  8. Hive中小表与大表关联(join)的性能分析【转】

    Hive中小表与大表关联(join)的性能分析 [转自:http://blog.sina.com.cn/s/blog_6ff05a2c01016j7n.html] 经常看到一些Hive优化的建议中说当 ...

  9. Jackson的用法实例分析

    这篇文章主要介绍了Jackson的用法实例分析,用于处理Java的json格式数据非常实用,需要的朋友可以参考下 通俗的来说,Jackson是一个 Java 用来处理 JSON 格式数据的类库,其性能 ...

随机推荐

  1. adduser与useradd的区别

    问题:使用 useradd 创建用户,发现 /home 目录下没有自动创建关于用户的目录.所以做了一番调查研究 useradd是一个linux命令,但是它提供了很多参数在用户使用的时候根据自己的需要进 ...

  2. python day03_ 文件处理

    一.文件操作的基本流程 计算机操作文件的过程 #1. 打开文件,得到文件句柄并赋值给一个变量 #2. 通过句柄对文件进行操作 #3. 关闭文件 1.文件的打开过程 # f被程序持有,文件被操作系统持有 ...

  3. Eclipse常用快捷键--摘录他人

    Eclipse常用快捷键 1几个最重要的快捷键 代码助手:Ctrl+Space(简体中文操作系统是Alt+/) 快速修正:Ctrl+1 单词补全:Alt+/ 打开外部Java文档:Shift+F2显示 ...

  4. MySql 游标定义时使用临时表

    参考:Re: Temp Table in Select of a Cursor 方法一: delimiter $$ create procedure test_temp() begin drop te ...

  5. php socket多进程简单服务器(一)

    进程,线程  IO复用,协程都是处理完成并发的方式 socket分为  三步 服务器监听,客户端请求,连接确认, 每次连接都由当前进程来处理,可以通过IO复用来解决这个问题, 这次通过进程来完成并发请 ...

  6. postma概念与使用

    Postman是google开发的一款功能强大的网页调试与发送网页HTTP请求,并能运行测试用例的的Chrome插件.Postman作为一个chrome的插件,你可以打开chrome,在chrome ...

  7. IDEA集成git方法

    一.IDEA集成git方法 首先idea集成git我们需要先下载一个小软件,git bash  地址:https://git-scm.com/downloads  .下载好了之后直接下一步下一步傻瓜试 ...

  8. orm单表查询和模糊查询

    一.单表查询 1. 返回queryset对象的查询 all() 以列表形式返回全部queryset对象 filter(**kwargs) 筛选 exclude(**kwargs) 排除 reverse ...

  9. go的基本数据类型

    一,数据类型的介绍 在go语言中,数据类型是用于声明函数和变量的:数据类型是为了把数据分成所需内存不同大小的数据,除了在需要使用大数据的时候才会申请大内存,这样就会充分的使用内存 Go 语言按类别有以 ...

  10. 京东Alpha平台开发笔记系列(三)

    摘要:通过前面两篇文章的讲述,大致了解了JdAlpha平台前端开发的主要流程.接下来本篇文章主要讲述后台服务器端开发的主要流程.这里会涉及到后台服务器的搭建的内容,本篇文章就不以赘述,如需了解请读下面 ...