阅读 《A Comparison of Join Algorithms for Log Processing in MapReduce》

这篇论文主要提供了equi-join的多种join的算法:

文章给出的假定:

1.每个map和reduce都实现了init()和close()

2.有实现好的partition.

3.中间文件都存储在HDFS中。

 

1.Repartition Join

mapreduce框架中该算法是最常使用的,L和R按照join key分区,从而每个分区中对应的pair进行join操作。

  Standard Repartition Join:

  并行关系数据库中的partitioned sort-merge join和hadoop中的默认datajoin包也是这样做连接的,在map过程中,每个map任务处理一条R或者L的split,同时标志数据来自哪一个表。reduce任务首先通过tag分离数据,将两个不同的数据集做交叉乘积。

  该算法思想在现实中可能存在key的集数太小或者数据量太大,从而需要需要缓存的数据量过大以致超过内存容量。

  伪代码:

 

  Improved Repartition Join:

  使用复合键,将原有的key和tag合并为key。分区仍然按照原有key进行分区,这样可以确保当其中一个表的大小较小时(比如说R),可以只缓存R表数据,同时L表数据流式获得,这样可以在一定程度上缓解缓存空间不够用的情况。

  伪代码:

  

  这两种方法都必须在shuffle阶段对数据进行排序并且通过网络传输数据。

  Preprocessing for Repartition Join:

  在数据生成时按照join key对L表预先分区,并且在R表存入HDFS中的时候对R表预分区。和并行的关系数据库不同,MapReduce不能够保证L表和R表对应的分区数据在同一个物理节点上,因此采用directed join策略,每个map任务是在一个L的split上运行,如果对应的R的split不在本地上,把么就从其他节点获取后存入本地,这里采用了内存hash的方式来映射R的split。该方法与hadoop join package中的map-side join类似,但是可以处理data skew(数据倾斜),因为map-side join在数据倾斜时很容易内存溢出。

  directed join伪代码:

   

 

2.BroadCast Join

  主要针对表R远远小于表L的情况,该方法主要采用广播表R的方法来减小因为对L表排序和通过网络传播所造成的开销。该方法仅有1个map 任务,每个节点从hdfs文件系统中获取表R的数据信息,并且每个map任务使用内存哈希表来对L的分片和R表进行join操作。

  在init()方法中,每个map任务首先检查R表是否已储存在本地文件系统,如果不的话,那么就从hdfs文件系统中获取R表,并且按照join key进行分区然后存储这些分区到本地文件系统中,从而避免将R表所有数据加载到内存中。

  一般来说典型的分片大小小于100MB,所以该join算法会根据R表和L表的分片大小比较来决定加载R还是L表的split到内存中。如果R表较小的话,就会将R表加载到内存中,构建内存哈希表,然后通过探查hash table来对每一个L表的split中的每条记录做join操作; 如果L表的split较小的话,那么在map过程中不作join而是与对R分区一样对L表做分区操作,然后再close()方法中对同一分区中的R和L的数据做join。如果某一个L的分区中没有L的数据,那么也将不加载R表对应的数据。

  文中为了保证大部分节点上都有r文件的拷贝,增加了replication factor的值。

  伪代码如下所示:

  

 

3.Semi-Join

 这种情况主要是处理当R表很大的时候,防止通过网络发送不会跟L表做join操作的R表中的部分记录,该算法有三个步骤,每个步骤都对应一个完整的mapreduce任务。

  第一阶段:

  在map过程中,用内存哈希表来为L表中的join key做hash,之所以用哈希的原因是因为mapreduce并不直接支持基于hash的聚合,map任务只发送join key出去,并且对应的reduce任务简单的将所有的L表的分片中的join key(不重复)整合到一个输出文件中,譬如L.uk。(我们假设该输出文件在内存中没有溢出)

  第二阶段:

  第二阶段跟broadcast join类似,也只有一个map 任务,init()方法中将L.uk文件加载如内存哈希表,map()方法中,如果join key在L.uk文件中发现,相当于对R做一个分片操作,输出一些列的Ri文件,每一个对应一个R的分片。

  第三阶段:

  所有的Ri文件与L表做join操作,使用之前提到的boradcast join方法。

尽管semi-join避免了发送不跟L表做join操作的部分记录,但是它同时也额外对 L表做了一次扫描操作。


伪代码如下所示:

可以讲前面两步骤看做一个预处理。

4.Per-Split Semi-Join

  该方法与Semi-Join的不同之处在于不是以L整表过滤R,而是以L的分片过滤R。该方法使得第三步骤更加快速以及负载较低,但是前面两步骤则相对的增加了工作量。

伪代码如下:

比较结果:

  如果不考虑预处理的话,improved repartition join会比standard join要好,而且不容易出现撑爆内存的额情况,当R大约有1000或更少的记录的时候,broadcast join会比其improved repartition join要好,semi join在不考虑预处理的情况下从来都不是最好的算法,因为相对来说在hdfs中扫描L表示一个相对比较大的开销。并且值得注意的是,在数据偏斜的情况下,standard repartition join在性能方面降低非常快。

  如果考虑预处理的话,standard repartition join当R的大小较小的时候(小于10GB),分区的大小对性能影响不大(分区可以根据节点数设置200,也可以根据保证绝大部分分片能够加载到内存为5000),但是超过了,则会发现分区数设置为5000时,性能大大提高。Per-split semi-join拥有非常出色的性能,可以见下图所示:

  

文中给出额外的实验证明了所有的算法随着节点数增加和输入数据量的增加呈线性关系,也就是说拥有较好的scalability。

posted @ 2012-08-21 20:27  editice  阅读(681)  评论(0)    收藏  举报