Flink中的join实现原理

首先假如我们有两个Dataset,一个Dataset中的数据为用户信息,另一个Dataset中的数据是站点访问记录。

case class PageVisit(url: String, ip: String, userId: Long)
case class User(id: Long, name: String, email: String, country: String)

如果想通过这两个Dataset获取来自中国用户的访问记录应该怎么做?很显然,把两个Dataset join一下然后根据country进行过滤即可,join的key选择userId和id。

val visits: DataSet[PageVisit] = ...
val users: DataSet[User] = ...

// filter the users data set
val chinaUsers = users.filter((u) => u.country.equals("ch"))
// join data sets
val chinaVisits: DataSet[(PageVisit, User)] =
      // equi-join condition (PageVisit.userId = User.id)
     visits.join(chinaUsers).where("userId").equalTo("id")

很美好对不对,不过join背后的实现可就不像用起来这么简单了。

 

在Flink中,join的实现分为两个阶段,第一个阶段被称为Ship阶段,而第二个阶段被称为Local阶段,这个Ship阶段很像mapreduce中的shuffle,就是将具有相同join key的element shuffle到同一个节点,不然没法在task节点进行本地join。ship阶段有两种不同的策略,一种是根据join key把element重新进行partition。

第二种策略是将一个完整的Dataset shuffle到每个task节点。比如R S两个Dataset,图中示例就是将R shuffle到每个节点。

可以看到ship阶段需要将大量的数据缓存在内存中,数据量如果很大的时候会造成频繁的内存溢出,所以为了应对这种问题,前面也提到过Flink应用一套更高效的内存管理机制,而且Flink中特有的序列化方式能将java 对象大大压缩从而节约内存空间。

 

Ship阶段完成之后,每个节点都要开始进行本地join,也就是上面所说的Local阶段。

在这个阶段,Flink借用了数据库中常见的两种join方式,一种是Sort-Merge-Join,另一种是Hybrid-Hash-Join。

所谓Sort-Merge-Join其实就是先将两个Dataset中的元素进行排序,然后利用两个游标不断的从Dataset中取具有相同join key的元素出来。而Hybrid-Hash-Join相对来说更复杂一些,先来看简单的Hash-Join,就是将一个较小的Dataset装进哈希表,然后遍历另一个Dataset,根据join key进行配对。但是如果较小的那个Dataset中的数据量也很大根本没法装进内存中呢?这时候就要将Dataset再进行partition,然后在各个partition上进行简单的Hash-Join。Hybrid-Hash-Join在此基础上有个小的优化,就是在内存足够的情况下,将一些数据一直保存在内存中。

因为不同阶段有不同的策略,所以可以构建出多种的join策略,具体选择哪种join策略,在Flink中会根据初始Dataset的数据量对各个join策略进行预估,然后选择预估时间最小的那个策略。

参考资料:https://zhuanlan.zhihu.com/p/34140095

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值