MapReduce6 MapJoin和ReduceJoin

MapReduce6 MapJoin和ReduceJoin

更多整理都在我的github上:Auraros欢迎大家。

其实Join就是Mysql中表的连接操作,后面使用hive还是一条sql语句就可以解决了,这里反正比较麻烦,但是要了解一下

Reduce Join原理

Map端的主要工作

为来自不同表或文件的key/value对,打标签以区别不同来源的记录。然后用连接字段作为key,其余部分和新加的标志作为value,最后进行输出

Reduce端主要工作

在Reduce端以连接字段作为key的分组已经完成,我们只需要在每一个分组当中那些来源不同文件的记录(在Map阶段已打标记)分开,最后进行合并就ok了

缺点

它的缺点很明显就是会造成Map和Reduce端也就是Shffle阶段出现大量的数据传输,效率很低下

Reduce Join实战

需求

订单表

订单idpid数量
1001011
1002022
1003033
1001011
1002022
1003033

公司名称表

Pid产品名称
01小米
02华为
03格力

预期输出数据

订单id      产品名称      数量
1001         小米        1
1001         小米        1
1002		 华为		   2 
1002		 华为		   2
1003		 格力		   3
1003 		 格力		   3

过程分析

MapTask

1)Map中处理的事情

  1. 获取输入文件类型
  2. 获取输入数据
  3. 不同文件分别处理
  4. 封装Bean对象输出
01		1001	1	order
02		1002	2	order
03		1003	3	order
01		1001	1	order
02		1002	2	order
03		1003	3	order
01		小米		pd
02		华为		pd
03		格力		pd

2)默认对产品id进行排序

01		1001	1	order
01		1001	1	order
01		小米		pd
02		1002	2	order
02		1002	2	order
02		华为		pd
03		1003	3	order
03		1003	3	order
03		格力		pd

ReduceTask

1)Reduce方法缓存订单数据集合和产品表,然后合并

订单		产品名称		数量
1001	  小米			1
1001	  小米			1

1002	  华为			2
1002	  华为			2

1003	  格力			3
1003	  格力			3

代码

TableBean代码

import java.io.DataInput; 
import java.io.DataOutput; 
import java.io.IOException; 
import org.apache.hadoop.io.Writable;

public class TableBean implenments Writable{
    
    private String order_id;  //订单id
    private String p_id;   //产品id
    private int amount; //产品数量
    private String pname; //产品名称
    private String flag;  //表的标记
    
    public TableBean() { 
        super(); 
    }
    
    public TableBean(String order_id, String p_id, int amount, String pname, String flag) { 
        super(); 
        this.order_id = order_id; 
        this.p_id = p_id; 
        this.amount = amount; 
        this.pname = pname; 
        this.flag = flag; 
    }
    
    public String getFlag() { 
        return flag; 
    }
    
    public void setFlag(String flag) { 
        this.flag = flag;
    }
    
    public String getOrder_id() { 
        return order_id; 
    }
    
    public void setOrder_id(String order_id) {
        this.order_id = order_id; 
    }
    
    public String getP_id() { 
        return p_id; 
    }
    
    public void setP_id(String p_id) { 
        this.p_id = p_id; 
    }
    
    public int getAmount() { 
        return amount; 
    }
    
    public void setAmount(int amount) { 
        this.amount = amount; 
    }
    
    public String getPname() { 
        return pname; 
    }
    
    public void setPname(String pname) { 
        this.pname = pname; 
    }
    
    @Override public void write(DataOutput out) throws IOException { 
        out.writeUTF(order_id); 
        out.writeUTF(p_id); 
        out.writeInt(amount); 
        out.writeUTF(pname); 
        out.writeUTF(flag); 
    }
    
    @Override public void readFields(DataInput in) throws IOException { 
        this.order_id = in.readUTF();
        this.p_id = in.readUTF();
        this.amount = in.readInt();
        this.pname = in.readUTF();
        this.flag = in.readUTF();
    }
    
    @Override 
    public String toString() { 
        return order_id + "\t" + pname + "\t" + amount + "\t" ; 
    } 
}

TableMappe代码

import java.io.IOException; 
import org.apache.hadoop.io.LongWritable; 
import org.apache.hadoop.io.Text; 
import org.apache.hadoop.mapreduce.Mapper; 
import org.apache.hadoop.mapreduce.lib.input.FileSplit; 

public class TableMapper extends Mapper<LongWritable, Text, Text, TableBean>{ 
    String name; 
    TableBean bean = new TableBean(); 
    Text k = new Text(); 
    
    @Override 
    protected void setup(Context context) throws IOException, InterruptedException { 
        
        // 1 获取输入文件切片 
        FileSplit split = (FileSplit) context.getInputSplit(); 
        
        // 2 获取输入文件名称 
        name = split.getPath().getName(); 
    }
    
    @Override 
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { 
        
        // 1 获取输入数据 
        String line = value.toString(); 
        
        // 2 不同文件分别处理 
        if (name.startsWith("order")) {
            // 订单表处理 
            // 2.1 切割 
            String[] fields = line.split("\t"); 
            // 2.2 封装bean对象 
            bean.setOrder_id(fields[0]); 
            bean.setP_id(fields[1]); 
            bean.setAmount(Integer.parseInt(fields[2])); 
            bean.setPname(""); 
            
            // 打上标签 
            bean.setFlag("other"); 
            k.set(fields[1]); 
        }else {
            // 产品表处理 
            // 2.3 切割 
            String[] fields = line.split("\t"); 
            // 2.4 封装bean对象 
            bean.setP_id(fields[0]); 
            bean.setPname(fields[1]); 
            bean.setFlag("pd"); 
            bean.setAmount(0);

            bean.setOrder_id(""); 
            k.set(fields[0]); 
        }
        // 3 写出 
        context.write(k, bean); 
    } 
}

TableReducer代码

import java.io.IOException; 
import java.util.ArrayList; 
import org.apache.commons.beanutils.BeanUtils; 
import org.apache.hadoop.io.NullWritable; 
import org.apache.hadoop.io.Text; 
import org.apache.hadoop.mapreduce.Reducer; 

public class TableReducer extends Reducer<Text, TableBean, TableBean, NullWritable> { 
    @Override protected void reduce(Text key, Iterable<TableBean> values, Context context) throws IOException, InterruptedException { 
        // 1准备存储订单的集合 
        ArrayList<TableBean> orderBeans = new ArrayList<>(); 
        // 2 准备bean对象 
        TableBean pdBean = new TableBean(); 
        for (TableBean bean : values) { 
            
            // 根据flag判定是哪个表来的数据 
            if ("order".equals(bean.getFlag())) {
                // 订单表 // 拷贝传递过来的每条订单数据到集合中
                TableBean orderBean = new TableBean();
                try { 
                    // 对象互拷,Web中常见操作 
                    BeanUtils.copyProperties(orderBean, bean); 
                } catch (Exception e) {
                    e.printStackTrace();
                }
                orderBeans.add(orderBean); 
            } else {
                
                // 产品表 try 
                {
                    // 拷贝传递过来的产品表到内存中 
                    BeanUtils.copyProperties(pdBean, bean); 
                } catch (Exception e) {
                    e.printStackTrace(); 
                } 
            } 
        }


        // 3 表的拼接 
        for(TableBean bean:orderBeans){ 
            // 遍历订单表,将名字设置进来即可 
            bean.setPname (pdBean.getPname()); 
            // 4 数据写出去 
            context.write(bean, NullWritable.get()); 
        } 
    } 
}

TableDriver代码

import org.apache.hadoop.conf.Configuration; 
import org.apache.hadoop.fs.Path; 
import org.apache.hadoop.io.NullWritable; 
import org.apache.hadoop.io.Text; 
import org.apache.hadoop.mapreduce.Job; 
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; 
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; 

public class TableDriver { 
    public static void main(String[] args) throws Exception { 
        // 0 根据自己电脑路径重新配置 
        args = new String[]{"e:/input/inputtable","e:/output1"}; 
        
        // 1 获取配置信息,或者job对象实例 
        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration); 
        
        // 2 指定本程序的jar包所在的本地路径 
        job.setJarByClass(TableDriver.class); 
        
        // 3 指定本业务job要使用的Mapper/Reducer业务类 
        job.setMapperClass(TableMapper.class); 
        job.setReducerClass(TableReducer.class); 
        
        // 4 指定Mapper输出数据的kv类型 
        job.setMapOutputKeyClass(Text.class); 
        job.setMapOutputValueClass(TableBean.class); 
        
        // 5 指定最终输出的数据的kv类型 
        job.setOutputKeyClass(TableBean.class); 
        job.setOutputValueClass(NullWritable.class); 
        
        // 6 指定job的输入原始文件所在目录 
        FileInputFormat.setInputPaths(job, new Path(args[0])); 
        FileOutputFormat.setOutputPath(job, new Path(args[1])); 
        // 7 将job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行 
        boolean result = job.waitForCompletion(true); System.exit(result ? 0 : 1); 
    }
}

总解

**缺点:**这种方式中,合并的操作是Reduce阶段完成的,Reduce端的处理压力太大,Map节点的运算负载则很低,资源利用率不高,而且Reduce阶段极易产生数据倾斜。

**解决方案:**Map端实现数据合并

MapJoin

应用场景

MapJoin适用于一张表十分小,一张表十分大的场景

对于Reducer数据倾斜的处理办法是:在Map端缓存多张表,提前处理业务逻辑,这样增加Map端业务,减少Reduce端数据的压力,尽可能减少数据倾斜

通常都是用hive进行mapjoin操作

采用DistributedCache

(1)在Mapper的setup阶段,奖文件读入到缓存集合中

(2)在驱动函数加载缓存

//缓存普通文件到Tas运行节点
job.addCacheFile(new URI("file://e:/cache/pd.txt"));

代码

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

/**
 * map-join中小表的数据如下:
 * 
 *    1    Beijing
 *    2    Guangzhou
 *    3    Shenzhen
 *    4    Xian
 * 
 * 大表的数据如下:
 * 
 *    Beijing Red Star              1
 *    Shenzhen Thunder              3
 *    Guangzhou Honda               2
 *    Beijing Rising                1
 *    Guangzhou Development Bank    2
 *    Tencent                       3
 *    Back of Beijing               1
 */
public class MapJoin {    

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        job.setJarByClass(MapJoin2.class);        
        //此方法已过时,被job.addCacheFile()所取代
        //DistributedCache.addCacheFile(new URI("hdfs://10.16.17.182:9000/test/in/address.txt"), conf);        
        //加载小表到 分布式缓存DistributedCache
     job.addCacheFile(new Path(args[0]).toUri());        
        job.setMapperClass(MJMapper.class);
        job.setNumReduceTasks(0);        
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);        
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);        
        FileInputFormat.addInputPath(job, new Path(args[1]));
        FileOutputFormat.setOutputPath(job, new Path(args[2]));        
        System.exit(job.waitForCompletion(true)? 0:1);
    }

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

        /**
         * 此map是存放小表数据用的
         * 注意小表的key是不能重复的,类似与数据库的外键表
         * 在这里的小表,就相当于一个外键表
         * **/
        private HashMap<String, String> map=new HashMap<String, String>();

        @Override
        protected void setup(Context context) throws IOException, InterruptedException {            
                        
            BufferedReader br=null;        // 读取文件流
            String line;

            // 获取DistributedCached里面 的共享文件
        Path[] paths = context.getLocalCacheFiles();

            for(Path path : paths){                    
                //如果是 address文件  
                if(path.getName().indexOf("address") >= 0){                  
            br=new BufferedReader(new FileReader(path.toString()));                    
					//读取文件中的每一行
                    while((line=br.readLine()) != null){    
               String[] splited = line.split("\t");
                        //将小表解析成 key/value 存放进map
                        map.put(splited[0], splited[1]);    
                    }
                }
            }
        }
        
        /**
         * map阶段读取并处理大表中的数据
         * 小表中的数据是加载到HashMap中的,无需从hdfs读取
         */
        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
            
            if(value==null || ("").equals(value.toString())){    //跳过空值
                return;
            }
            
            String[] splited = value.toString().split("\t");            
            if(map.get(splited[1]) != null){    //map中大表的 key 对应的 value 不为空                
                Text keyOut = new Text(splited[0]);        //key=大表的第一列                
                Text valueOut = new Text(map.get(splited[1]));    //value=小表的第二列                
                context.write(keyOut, valueOut);     
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值