MapReduce在Reduce中实现LEFT JOIN

本文通过具体案例,详细解析了如何使用MapReduce实现左连接操作,包括数据准备、Map阶段和Reduce阶段的具体实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文以订单和商品演示如何实现left join。

一:准备数据

订单数据表t_order:

id

date

pid

amount

1001

20150710

P0001

2

1002

20150710

P0001

3

1002

20150710

P0002

3

 

商品信息表t_product

id

pname

category_id

price

P0001

小米5

1000

2000

P0002

锤子T1

1000

3000

实现类似如下sql的left join效果:

select  a.id,a.date,b.name,b.category_id,b.price from t_order a join t_product b on a.pid = b.id

二:mapreduce实现

将订单表的pid和商品表的id作为map阶段的k2,为了实现上面sql输出的效果,所以v2则是我们自定义的一个orderBean,封装了sql输出字段,也就是封装了两张表的字段

根据上下文对象context,从中获取当前map读取的文件名,从而v2就是OrderBean,利用读取不同的文件作为判断条件,将不同表的信息封装到orderBean中,在reduce阶段利用相同key在同一reduceTask,value合并的规则,可知,在reduce阶段相同k2的v2是一个orderBean的集合,该集合中的orderBean分别保存了订单bean和商品bean,遍历v2将组合orderBean进行输出


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;

import java.io.IOException;

/**
 * 读取多个文件
 * 自定义map
 */
public class OrderJoinMapper extends Mapper<LongWritable,Text,Text,OrderJoinBean>{
    private OrderJoinBean orderJoinBean = new OrderJoinBean();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //通过获取文件名来区分两个不同的文件
        String[] split = value.toString().split(",");
        FileSplit inputSplit = (FileSplit) context.getInputSplit();//获取输入分区
        String fileName = inputSplit.getPath().getName();
        System.out.println("当前指定的文件名称是:"+fileName);
        if("orders.txt".equals(fileName)){
            //订单数据
            orderJoinBean.setId(split[0]);
            orderJoinBean.setDate(split[1]);
            orderJoinBean.setPid(split[2]);
            orderJoinBean.setAmount(split[3]);
            context.write(new Text(split[2]),orderJoinBean);
        }else{
            //商品数据
            orderJoinBean.setName(split[1]);
            orderJoinBean.setCategoryId(split[2]);
            orderJoinBean.setPrice(split[3]);
            context.write(new Text(split[0]),orderJoinBean);
        }
    }
}

三:reduce实现


import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class OrderJoinReduce extends Reducer<Text,OrderJoinBean,OrderJoinBean,NullWritable> {
    private OrderJoinBean orderJoinBean;
    @Override
    protected void reduce(Text key, Iterable<OrderJoinBean> values, Context context) throws IOException, InterruptedException {
        List<OrderJoinBean> temp = new ArrayList<>();
        List<OrderJoinBean> temp1 = new ArrayList<>();
         for (OrderJoinBean value : values) {//注意:temp集合不能直接添加value
             orderJoinBean = new OrderJoinBean();
            //相同的key的对象都发送到了这里,在这里将数据拼接完整
          if(null !=value.getId() && !value.getId().equals("null") ){//通过自定义map可知,订单数据设置了id,商品数据没有设置id
              orderJoinBean.setId(value.getId());
              orderJoinBean.setDate(value.getDate());
              orderJoinBean.setPid(value.getPid());
              orderJoinBean.setAmount(value.getAmount());
              temp.add(orderJoinBean);
          }else{
              orderJoinBean.setName(value.getName());
              orderJoinBean.setCategoryId(value.getCategoryId());
              orderJoinBean.setPrice(value.getPrice());
              temp1.add(orderJoinBean);
          }
        }
        if(temp != null && !temp.isEmpty()){
            for (OrderJoinBean bean : temp) {
                if(temp1 != null && !temp1.isEmpty()){
                    for (OrderJoinBean joinBean : temp1) {
                        bean.setName(joinBean.getName());
                        bean.setCategoryId(joinBean.getCategoryId());
                        bean.setPrice(joinBean.getPrice());
                        context.write(bean,NullWritable.get());
                    }
                }else{
                    context.write(bean,NullWritable.get());
                }
            }
        }
    }
}

四:注意map端获取文件名称的方式:

//通过获取文件名来区分两个不同的文件

FileSplit inputSplit = (FileSplit) context.getInputSplit();//获取输入分区

String fileName = inputSplit.getPath().getName();

System.out.println("当前指定的文件名称是:"+fileName);

 

 

 

 

<think>我们被要求设计一个实验来量化测试Hive中MINUS操作与等效LEFTJOIN实现的性能差异。以下将详细说明测试方法、实验设计和分析要点。###测试目标1.比较`MINUS`原生语法与`LEFTJOIN`重写方式的执行效率。2.分析不同数据规模(小数据、大数据)、数据分布(均匀分布、倾斜分布)下的性能差异。3.验证Hive配置参数(如执行引擎、FetchTask转换)对两种写法的影响。###实验环境配置```bash#Hive版本:2.1.0+(支持原生MINUS)#测试引擎:MapReduce/Tez/Spark(需分别测试)#关键参数调整:sethive.fetch.task.conversion=more;--控制FetchTask[^1]sethive.auto.convert.join=true;--开启MapJoin优化```###测试数据设计创建两张结构相同的测试表`table_a`和`table_b`,包含以下数据特征:1.**数据规模**:-小数据集:10万行-大数据集:1亿行2.**数据分布**:-均匀分布:键值随机生成(范围1~N)-倾斜分布:80%数据集中在10%的键值上3.**表结构**:```sqlCREATETABLEtable_a(idINT,skuSTRING,sale_amtDOUBLE)PARTITIONEDBY(dtSTRING)STOREDASORC;```###测试用例####用例1:基础MINUSvsLEFTJOIN```sql--原生MINUSSELECT*FROMtable_aMINUSSELECT*FROMtable_b;--LEFTJOIN重写SELECTA.*FROMtable_aALEFTJOINtable_bBONA.id=B.idANDA.sku=B.skuWHEREB.idISNULL;--注意:需确保连接条件覆盖所有列[^2]```####用例2:带分区过滤```sqlSELECT*FROMtable_aWHEREdt='20230101'MINUSSELECT*FROMtable_bWHEREdt='20230101';--LEFTJOIN重写(分区条件下推)SELECTA.*FROM(SELECT*FROMtable_aWHEREdt='20230101')ALEFTJOIN(SELECT*FROMtable_bWHEREdt='20230101')BONA.id=B.idANDA.sku=B.skuWHEREB.idISNULL;```###性能指标1.**执行时间**:从提交查询到获取完整结果的时间2.**资源消耗**:-Map/Reduce任务数量-Shuffle数据量(通过`hive.log`获取)3.**执行计划分析**:```sqlEXPLAIN[EXTENDED|DEPENDENCY|AUTHORIZATION]<query>;```###实验步骤1.**数据准备**:```sql--生成倾斜数据示例INSERTINTOtable_aPARTITION(dt='20230101')SELECTCASEWHENrand()<0.8THEN1ELSECAST(rand()*100000ASINT)END,CONCAT('SKU',CAST(rand()*1000ASSTRING)),rand()*100FROMhuge_source_table;```2.**执行测试**:对每个测试用例运行10次,取平均值(排除缓存干扰)。3.**参数组合测试**:|参数组|`hive.fetch.task.conversion`|执行引擎||--------|-----------------------------|----------||组1|`none`|MapReduce||组2|`more`|Tez||组3|`minimal`|Spark|###预期结果分析1.**小数据集场景**:-当数据量小时(满足`hive.fetch.task.conversion=more`条件),`MINUS`可能直接走FetchTask,速度显著快于`LEFTJOIN`[^1]。-执行计划对比:```diff+MINUS计划:Stage-Stage-0:FetchOperator-LEFTJOIN计划:Stage-Stage-1:MapOperatorStage-Stage-2:ReduceOperator```2.**大数据集场景**:-原生`MINUS`可能被优化为单次MapReduce作业,而`LEFTJOIN`写法固定为两阶段作业(MapJoin除外)。-数据倾斜时,`LEFTJOIN`可通过`skewjoin`参数优化,但`MINUS`缺乏直接优化手段:```sqlsethive.optimize.skewjoin=true;--仅对JOIN有效```3.**引擎差异**:-**Tez引擎**:`MINUS`的DAG优化更高效,减少中间落盘。-**Spark引擎**:两种写法可能被转换为相同的逻辑计划(`EXCEPT`算子)。###结论提炼通过量化测试可验证以下假设:$$T_{\text{MINUS}}\approxk\cdotT_{\text{LEFT\_JOIN}}+C$$其中:-$k$:与数据分布相关的系数(倾斜时$k>1$)-$C$:查询解析优化开销常数>**建议**:在均匀分布的大数据场景优先用`MINUS`,倾斜场景或低版本Hive用优化后的`LEFTJOIN`。---###§§相关问题§§1.如何解释Hive执行计划中`MINUS`操作的物理算子?2.在存在数据倾斜时,如何优化Hive的`MINUS`查询性能?3.Hive3.0对集合操作(UNION/MINUS/INTERSECT)做了哪些性能改进?4.`MINUS`与`NOTIN`子查询在Hive中的性能差异如何?5.如何收集Hive查询的运行时资源监控指标(CPU/内存)?</think>### Hive中MINUS与LEFT JOIN性能对比测试方法及分析 以下为量化测试方案,涵盖**测试设计、执行指标、结果分析方法**,重点解决数据规模、倾斜场景、执行引擎等变量影响。 --- #### 一、测试环境设计 | **组件** | **配置** | |----------------|------------------------------------------| | Hive版本 | 2.1.0+ (支持原生`MINUS`) | | 执行引擎 | MR/Tez/Spark (需分别测试) | | 数据规模 | 小数据集(10万行) / 大数据集(1亿行+) | | 数据分布 | 均匀分布 / 倾斜分布(80%数据集中10%key) | | 关键参数 | `hive.fetch.task.conversion=more`[^1] | #### 二、测试用例设计 ```sql -- 原生MINUS (需Hive 2.1.0+) SELECT * FROM table_a MINUS SELECT * FROM table_b; -- LEFT JOIN等效写法 (兼容所有版本) SELECT A.* FROM table_a A LEFT JOIN table_b B ON A.key = B.key WHERE B.key IS NULL; -- 显式处理NULL[^2] ``` #### 三、性能指标采集 1. **时间指标** - 查询总耗时(从提交到结果返回) - Map/Reduce阶段耗时(通过`EXPLAIN ANALYZE`获取) 2. **资源指标** - Shuffle数据量(通过`hive.log`中的`HDFS Read/Written`) - 容器数量(YARN ResourceManager日志) 3. **执行计划** ```sql EXPLAIN DEPENDENCY SELECT ... ; -- 分析依赖关系 ``` #### 四、关键变量控制实验 ##### 实验1:数据规模影响 | **数据量** | **MINUS耗时** | **LEFT JOIN耗时** | **Shuffle量差异** | |-------------|--------------|------------------|------------------| | 10万行 | 测量值 | 测量值 | MINUS减少X% | | 1000万行 | 测量值 | 测量值 | LEFT JOIN多Y% | | 1亿行 | 测量值 | 测量值 | - | **预期结论**: 小数据时`MINUS`可能走**Fetch Task**直接读取[^1],大数据时`LEFT JOIN`因需两阶段Shuffle更慢。 ##### 实验2:数据倾斜场景 ```sql -- 创建倾斜数据 INSERT INTO table_a SELECT CASE WHEN rand() < 0.8 THEN 1 ELSE rand()*100000 END, -- 80%数据key=1 ... FROM generator; ``` **测量倾斜处理能力**: - 观察`Skewed Join`是否触发(Tez/Spark引擎) - 对比两种方法在倾斜时的**最长Task耗时** ##### 实验3:执行引擎差异 | **引擎** | **MINUS逻辑计划** | **LEFT JOIN逻辑计划** | |---------|--------------------------|----------------------------| | MR | 转换为Single Reduce任务 | 需MapJoin+Reduce阶段 | | Tez | 优化为单DAG | 多Stage DAG | | Spark | 转换为`except()`算子 | 仍为`left_anti` Join | **验证方法**: ```sql EXPLAIN FORMATTED SELECT ... ; -- 查看物理计划 ``` #### 五、结果分析方法 1. **性能公式量化** 建立耗时模型: $$ T_{\text{total}} = T_{\text{map}} + T_{\text{shuffle}} + T_{\text{reduce}} $$ - `MINUS`通常有更低的 $T_{\text{shuffle}}$ (单次聚合) - `LEFT JOIN`在倾斜时 $T_{\text{reduce}}$ 显著增大 2. **优化效果评估** - 当启用`hive.fetch.task.conversion=more`时,小数据集**MINUS直接命中Fetch Task**,速度提升5-10倍[^1] - 倾斜场景下,`LEFT JOIN`可通过参数优化: ```sql set hive.optimize.skewjoin=true; -- 仅对JOIN有效 set hive.skewjoin.key=100000; -- 超过此值视为倾斜 ``` #### 六、结论预期 | **场景** | **推荐方案** | **原因** | |------------------------|-------------------|-----------------------------| | 小数据+均匀分布 | `MINUS`原生语法 | 触发Fetch Task,免MapReduce | | 大数据+无倾斜 | `MINUS`原生语法 | 更少Shuffle阶段 | | 严重数据倾斜 | 优化后的`LEFT JOIN`| 可启用SkewJoin优化 | | 低版本Hive(<2.1.0) | `LEFT JOIN` | 原生MINUS不可用 | > **验证工具**: > 使用Hive的**运行时性能日志**: > ```bash > # 查看资源消耗 > grep "HDFS Read" hive.log | awk '{sum+=$5} END {print sum}' > # 获取各阶段耗时 > grep "Time taken" hive.log > ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值