Hive以及spark的Join过程

  Join大致包括三个要素:Join方式、Join条件以及过滤条件。其中过滤条件也可以通过AND语句放在Join条件中。

二、Hive/MR中的Join可分为Common Join(Reduce阶段完成join)和Map Join(Map阶段完成join)。介绍两种join的原理和机制。

(1)Common Join:如果不指定MapJoin或者不符合MapJoin的条件,那么Hive解析器会将Join操作转换成Common Join,即:在Reduce阶段完成join.整个过程包含Map、Shuffle、Reduce阶段。

  1、Map阶段:读取源表的数据,Map输出时候以Join on条件中的列为key,如果Join有多个关联键,则以这些关联键的组合作为key;Map输出的value为join之后所关心的列即(select或者where中需要用到的);同时在value中还会包含表的Tag信息,用于标明此value对应哪个表;

  2、shuffle阶段:根据key的值进行hash,并将key/value按照hash值推送至不同的reduce中,这样确保两个表中相同的key位于同一个reduce中。

  3、reduce阶段:通过Tag来判断每一个value是来自table1还是table2,在内部分成2组,做集合笛卡尔乘积。

  缺点:shuffle的网络传输和排序性能很低,reduce 端对2个集合做乘积计算,很耗内存,容易导致OOM。

  4、实例:SELECT a.id,a.dept,b.age FROM a join b ON (a.id = b.id);如下图所示; 

     

(2)Map join:MapJoin通常用于一个很小的表和一个大表进行join的场景,具体小表有多小,由参数hive.mapjoin.smalltable.filesize来决定,该参数表示小表的总大小,默认值为25000000字节,即25M。

  Hive0.7之前,需要使用hint提示 /*+ mapjoin(table) */才会执行MapJoin,否则执行Common Join,但在0.7版本之后,默认自动会转Map Join,由参数hive.auto.convert.join来控制,默认为true.以0.7之后的HQL来解析,假设a表为一张大表,b为小表,并且hive.auto.convert.join=true,那么Hive在执行时候会自动转化为MapJoin。

  在map 端进行join,其原理是broadcast join,即把小表作为一个完整的驱动表来进行join操作。通常情况下,要连接的各个表里面的数据会分布在不同的Map中进行处理。即同一个Key对应的Value可能存在不同的Map中。这样就必须等到 Reduce中去连接。要使MapJoin能够顺利进行,那就必须满足这样的条件:除了一份表的数据分布在不同的Map中外,其他连接的表的数据必须在每 个Map中有完整的拷贝。MAPJION会把小表全部读入内存中,在map阶段直接拿另外一个表的数据和内存中表数据做匹配,由于在map是进行了join操作,省去了reduce运行的效率也会高很多。

  1、执行流程:

       

   1)如图中的流程,首先是Task A,它是一个Local Task(在客户端本地执行的Task),负责扫描小表b的数据,将其转换成一个HashTable的数据结构,并写入本地的文件中,之后将该文件加载到DistributeCache中(使用静态方法DistributedCache.addCacheFile()指定要复制的文件,它的參数是文件的URI)。

   2)接下来是Task B,该任务是一个没有Reduce的MR,启动MapTasks扫描大表a,在Map阶段,根据a的每一条记录去和DistributeCache中b表对应的HashTable关联,并直接输出结果。 

   3)相关参数

    a、小表自动选择Mapjoin:set hive.auto.convert.join=true;默认值:false。该参数为true时,Hive自动对左边的表统计量,若是小表就加入内存,即对小表使用Map join

    b、小表阀值:set hive.mapjoin.smalltable.filesize=25M;

二、Spark的join

  两种方式使用SparkSQL,一种是直接写sql语句,这个需要有元数据库支持,例如Hive等,另一种是通过Dataset/DataFrame编写Spark应用程序。Join的物理执行解析如下。

(1)join的基本实现过程

  Spark将参与Join的两张表抽象为流式遍历表(streamIter)和查找表(buildIter),通常streamIter为大表,buildIter为小表。Spark根据join语句自动区分大小表。

       

(2)spark提供了三种join实现:hash join,sort merge join以及broadcast join。

  1、hash join:通过分区的形式将大批量的数据通过hash划分成n份较小的数据集进行并行计算。

   1)对两张表分别按照join keys进行重分区,即shuffle,目的是为了让有相同join keys值的记录分到对应的分区中

   2)对对应分区中的数据进行join,此处先将小表分区构造为一张hash表,然后根据大表分区中记录的join keys值拿出来进行匹配

  总结:要将来自buildIter的记录放到hash表中,那么每个分区来自buildIter的记录不能太大,否则就存不下,默认情况下hash join的实现是关闭状态。buildIter总体估计大小以及分区后的大小要超过spark.sql.autoBroadcastJoinThreshold设定的值,即不满足broadcast join条件

    

  2、sort join:hash join对于实现大小表比较合适,但是两个表都非常大时,对内存计算造成很大的压力。

   1)实现方式:不需要将一侧数据全部加载后再进行hash join,但需要在join前将数据排序

   在shuffle read阶段,分别对streamIter和buildIter进行merge sort,在遍历streamIter时,对于每条记录,都采用顺序查找的方式从buildIter查找对应的记录,由于两个表都是排序的,每次处理完streamIter的一条记录后,对于streamIter的下一条记录,只需从buildIter中上一次查找结束的位置开始查找,所以说每次在buildIter中查找不必重头开始。

       

  3、broadCast join:Broadcast不会内存溢出,因为数据保存级别StoreageLevel是MEMORY_AND_DISK模式

  1)设计思想:避免大量的shuffle。若buildIter是一个非常小的表,其实没必要做shuffle了,直接将buildIter广播到每个计算节点,然后将buildIter放到hash表中。

  2)步骤:

   a、broadcast阶段:将小表广播分发到大表所在的所有主机。分发方式可以有driver分发。

   b、在每个executor上执行单机版hash join,小表映射,大表试探。

     

 

 

  

 

posted on 2020-04-28 10:41  hdc520  阅读(1763)  评论(0编辑  收藏  举报

导航