分布式空间分析引擎-Simba架构分析与源码阅读之SpatialJoin实现与总结

在分区器和索引部分铺垫了很多,其实Simba中Spatial join算子的部分是真正利用前面的结构来有效降低计算量的逻辑,也是simba最大的亮点。simba主要实现了三类spatial join算子:

KNN query:select * from table IN KNN ($target) within ($k);

Distance join:SELECT * FROM R JOIN S ON (R.x - S.x) * (R.x - S.x) + (R.y - S.y) * (R.y - S.y)
<= 10.0 * 10.0

KNN join:SELECT * FROM point1 AS p1 KNN JOIN point2 AS p2 ON POINT(p2.x, p2.y) IN KNN(POINT(p1.x, p1.y), 10).
出于篇幅原因,本章会对每类算子选择一个算子具体讲述代码和算法原理,对于其余算子仅会分析代码实现,具体的算法原理可以参考simba的论文。在本章的介绍中,会把参数join的两部分数据分别用leftRDD和rightRDD表示。

KNN Query

Simba基于two-level index对KNN Query进行了特别的优化,值得注意的是,不同的索引对于KNN Query的优化方式有所不同,作者所实现的效果最好的KNN算子需要配合提前建立RTree索引。另外KNN query算子并没有作为单独一个类出现,KNN query部分代码放在了org.apache.spark.sql.simba.index.IndexedRelationScan类的doExceute()函数的RTreeIndexedRelation优化逻辑的部分,在由physicalPlan生成RDD的时候执行。KNN Query算子的主要逻辑如下:

1)首先会根据global index未给定点找到最近的几个partition,然后调用knnGlobalPrune方法粗略找到第一个KNN候选集。在knnGlobalPrune方法中会首先调用PartitionPruningRDD,基于给定的paritionID裁剪不满足要求的partition;然后在裁剪后的每个分区内部调用local inedx的KNN方法找到最近的k个点,再把每个裁剪分区的k个点进行归并,去距离最近的k个,生成候选集tmp_ans,并计算候选集与点之间的距离theta作为第二次的裁剪;

def knnGlobalPrune(global_part: Set[Int]): Array[InternalRow] = {
                  val pruned = new PartitionPruningRDD(rtree._indexedRDD, global_part.contains)
                  pruned.flatMap{ packed =>
                    var tmp_ans = Array[(Shape, Int)]()
                    if (packed.index.asInstanceOf[RTree] != null) {
                      tmp_ans = packed.index.asInstanceOf[RTree]
                        .kNN(query_point, k, keepSame = false)
                    }
                    tmp_ans.map(x => packed.data(x._2))
                  }.takeOrdered(k)(ord)
                }
                // first prune, get k partitions, but partitions may not be final partitions
                val global_part1 = rtree.global_rtree.kNN(query_point, k, keepSame = false).map(_._2).toSet
                val tmp_ans = knnGlobalPrune(global_part1) // to get a safe and tighter bound
                val theta = evalDist(tmp_ans.last, query_point, column_keys, rtree.isPoint)

采用上面这样的逻辑的原因是:首先选择与目标点最近的、含有至少k个点的partition,以这些parition到目标点的最远距离theta作为裁剪依据。到目标点的距离大于theta的分区内的点一定不属于到目标点最近的k个点。

上图中每个矩形代表一个数据分区,步骤1)就是找到深灰色的部分,并计算红色圆形的半径,深灰色内至少含有k点,所以到目标点最近的k个点一定全部位于红色圆形内的部分,也就一定位于浅灰色的分区内,步骤2)就是找到浅灰色分区的部分。

2)计算到半径theta之后,simba会调用global index的circleRange方法找到半径为theta圆相交的partitionID,进行裁剪得到第二个候选集tmp_knn_res;第3行部分的逻辑可以看做一个懒加载,如果两部分重合就不再计算,不重合说明数据可能位于红色圈内、深灰色矩形之外,需要继续计算。

val global_part2 = rtree.global_rtree.circleRange(query_point, theta).
                  map(_._2).toSet -- global_part1
val tmp_knn_res = if (global_part2.isEmpty) tmp_ans
                else knnGlobalPrune(global_part2).union(tmp_ans).sorted(ord).take(k)

3)在裁剪失败(候选集为空)或者上一步的结果大于给定阈值的场景下,会尝试利用global index的range方法计算所有与给定点的MBR相交的部分进行计算。

var global_part = rtree.global_rtree.range(queryMBR).map(_._2).toSeq
              if (cir_ranges.nonEmpty){ // circle range
                global_part = global_part.intersect(
                  rtree.global_rtree.circleRangeConj(cir_ranges).map(_._2)
                )
              }
val pruned = new PartitionPruningRDD(rtree._indexedRDD, global_part.contains)

Distance Join

Distance Join部分代码位于org.apache.spark.sql.simba.execution.join包中,实现了DJSpark、CDJSpark、RDJSpark、BDJSpark四类Distance Join算子。

DJSpark

DJSpark的思路与SpatialHadoop的思路一致:1)分别对参与join的leftRDD和rightRDD按key值进行分桶;2)对各个分桶内的数据做两两组合,每个分桶的数据与其他分桶数据组合做nested loop join;3)数据合并。对应地simba中的算法实现如下:

1)分别把leftRDD和rightRDD进行STR分区(对应于hadoop的分桶),并对rightRDD建立RTree索引:

val (left_partitioned, left_mbr_bound) = STRPartition(left_rdd, dimension, num_partitions,
      sample_rate, transfer_threshold, max_entries_per_node)
val (right_partitioned, right_mbr_bound) = STRPartition(right_rdd, dimension, num_partitions,
      sample_rate, transfer_threshold, max_entries_per_node)
val right_rt = RTree(right_mbr_bound.zip(Array.fill[Int](right_mbr_bou
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值