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,小表映射,大表试探。