hive中Mapjoin 和Reducejoin 的区别

Map join

配置:
set hive.auto.convert.join = true(0.11版本后默认是true)
set hive.mapjoin.smalltable.filesize=25000000(设置小表的大小,默认就是25M)

原理:
mapjoin :主要用于小表连接大表,一般小表的大小为25M,大表没有什么具体的限制。
使用mapjoin的原因是:
在进行表的连接时,在map端处理完数据后,会把不同表的数据,形成不同的文件,reduce端进行拉取map端获得文件时,由于map端文件不在一个节点 ,且由于表的大小不一,获得的相应的文件也会大小不一,特别是针对相差较大的大小表,更会在数据拉去的时候浪费资源,拖慢job进度。为此引入了mapjoin这种处理手断。
解决:hive会自动选择小表(元数据中有记录),把小表put到各个节点的缓存(Distributed Cache)中,大表在硬盘中(这个和普通表进入map的操作一样)在map中直接与关联表连接,连表操作直接在map中进行,从内存中拉取小表,从硬盘中拉去大表,不需要走reduce,大大节约的时间。提高效率。
我们都知道。hive低层是编译成java代码,走mapreduce来执行hive语句。下面我们来看看mapjoin的java代码:

public class mapJoinDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
        //创建配置文件
        Configuration conf=new Configuration();
        Job job=Job.getInstance(conf,"mapjoin");
        //设置jar的位置
        job.setJarByClass(mapJoinDriver.class);
        //设置map和reduce的位置
        job.setMapperClass(mapJoinMapper.class);
        //设置map输出的key value类型
        job.setMapOutputKeyClass(NullWritable.class);
        job.setMapOutputValueClass(CustomerOrder.class);
        //设置输入输出的路径
        FileInputFormat.setInputPaths(job,new Path("file:///I:\\hadoopteam\\data\\join\\mapjoin"));
        FileOutputFormat.setOutputPath(job,new Path("file:///I:\\hadoopteam\\data\\joinoutput"));
        //提交小文件到内存
        job.addCacheFile(new URI("file:///I:/hadoopteam/data/join/customers.csv"));
        //提交程序运行
        boolean result=job.waitForCompletion(true);
        System.exit(result?0:1);
    }
    }

从代码中可以看到:

 FileInputFormat.setInputPaths(job,new Path("file:///I:\\hadoopteam\\data\\join\\mapjoin"));

读取我们的大文件的路径,读到各个节点的硬盘上

job.addCacheFile(new URI("file:///I:/hadoopteam/data/join/customers.csv"));

就是把我们的小文件,put到各个节点的缓存中,job的addCacheFile函数,调用的是DistributedCache.addCacheFile()函数,DistributedCache 可将具体应用相关的、大尺寸的、只读的文件有效地分布放置。DistributedCache 是Map/Reduce框架提供的功能,能够缓存应用程序所需的文件 (包括文本,档案文件,jar文件等)。
接下来是join操作:

public class mapJoinMapper extends Mapper<LongWritable, Text, NullWritable, CustomerOrder> {
    HashMap<String,String> customerMap=new HashMap<>();
    CustomerOrder customerorder=new CustomerOrder();
    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
       //获取缓存文件的uri
        URI[] cachefile= context.getCacheFiles();
        if (null!=cachefile && cachefile.length>0){
            //获取文件路径 文件名
            String filename=cachefile[0].getPath().toString();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(filename), "UTF-8"));
            String line;
            while (StringUtils.isNotEmpty(line=bufferedReader.readLine())){
                String[] split=line.split(",");
                customerMap.put(split[0],split[1]);
            }
       bufferedReader.close();

        }
    }

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] fileds=value.toString().split(",");
        customerorder.setCustomer_id(fileds[2]);
        customerorder.setOrder_id(fileds[0]);
        customerorder.setOrder_status(fileds[3]);
        customerorder.setFlag("");
        //从客户端hashmap中获取客户的姓名
        customerorder.setCustomer_name(customerMap.get(fileds[2]));
        //
        context.write(NullWritable.get(),customerorder);
    }
}

我们先看setup()函数,map()函数
setup函数主要做的是对我们的小表进行操作,首先获取我们的文件路径,然后获得文件名,在读取我们的小表文件,根据业务把小表文件转换成键值对的形式,保存在一个map集合中,用于后面的表的连接。
map函数就是mapreduce中的函数,每读一行执行一次map函数,这里我们输入的value是我们的大文件。执行一次map,我们都会获得相应的键值对,然后根据业务,把setup()集合中的数据与map中获得数据连接,最后在写出即可。
这就是mapjoin的过程,读取数据和数据的连接都在map中进行,挺高效率。但是其对每个节点的内存有要求,很吃节点内存,因为表的JOIN操作是在Map端且在内存进行的。

Reduce join

原理:
reduce join 又称shuffel join和commen join
他是一个完整的mapreduce过程,包括map阶段、shuffel阶段、reduce阶段,通过这三个阶段完整表的连接
map阶段:
读取源表数据,map输出的数据的key是join 中的on的条件,如果有多个,则一起作为key
map输出的数据的value为join之后所关心的列(select或者where所需的数据),同时还会有tag数据,用于标志数据属于哪个表,同时按key进行排序。
java代码如下:

public class Ordermapeer extends Mapper<LongWritable, Text,Text, CustomerOrder> {
    CustomerOrder customerorder=new CustomerOrder();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //切割字段
        String[] fileds=value.toString().split(",");
        //4个字段是订单表  否则是用户表
        if (fileds.length==4){
            //对订单表中可以赋值的字段赋值
            customerorder.setCustomer_id(fileds[2]);
            customerorder.setOrder_id(fileds[0]);
            customerorder.setOrder_status(fileds[3]);
            customerorder.setCustomer_name("");
            customerorder.setFlag("1");
        }else{
            customerorder.setCustomer_id(fileds[0]);
            customerorder.setCustomer_name(fileds[1]);
           customerorder.setOrder_id("");
           customerorder.setOrder_status("");
            customerorder.setFlag("0");
        }
        //写出
        context.write(new Text(customerorder.getCustomer_id()),customerorder);
    }
}

Shuffle阶段
根据key的值进行hash,并将key/value按照hash值推送至不同的reduce中,这样确保两个表中相同的key位于同一个reduce中
这个是封装在一些函数中了。
Reduce阶段
根据key的值完成join操作,期间通过Tag来识别不同表中的数据。

public class OrderReduce extends Reducer <Text, CustomerOrder, CustomerOrder, NullWritable>{
    @Override
    protected void reduce(Text key, Iterable<CustomerOrder> values, Context context) throws IOException, InterruptedException {
        //准备一个订单记录集合
        ArrayList<CustomerOrder> orderbeans=new ArrayList<>();
        //准备1个客户的bean对象
        CustomerOrder cusbean = new CustomerOrder();

        //把数据放入到集合总合并bean对阿星
        for (CustomerOrder bean : values) {
            if ("1".equals(bean.getFlag())){//订单表
               CustomerOrder orderbean= new CustomerOrder();
                try {
                    BeanUtils.copyProperties(orderbean,bean);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
                orderbeans.add(orderbean);
            }else{
                try {
                    //客户表
                    BeanUtils.copyProperties(cusbean,bean);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
        //遍历表 进行空白拼接
        for (CustomerOrder bean : orderbeans) {
            bean.setCustomer_name(cusbean.getCustomer_name());
            context.write(bean,NullWritable.get());
        }
    }
}

总结

mapjoin主要就是用于连表,现在已经默认开启,一旦hive发现大表和小表,就会走mapjoin ,如果一个小表和大表关联后,也有统计求和等操作 ,也会把数据的放到迭代器中,也要走reduce操作。
总的来说:map就做数据处理,(连表也是一种数据处理)
reduce:根据业务,对数据进行计算,获得我们想要的结果,比如统计,求和等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值