【Spark GraphX】分区策略源码解析

GraphX的分区策略:

GraphX采用点切割(Vertex cut)策略,这样可以减少通信和存储开销。在逻辑上,这种方式相当于将边分配到机器上并且点跨越多个机器。边分配的确切方式依赖PartitionStrategy中定义的分区策略。在PartitionStrategy中定义了四种分区策略:2D分区策略(EdgePartition2D);1D分区策(EdgePartiition1D);随机点切割(RandomVertexCut),正则随机点切割(CanonicalRandomVertexCut)。

针对源码分别对这四种分区策略介绍:

2D分区策略(EdgePartition2D)

case object EdgePartition2D extends PartitionStrategy {
override def getPartition(src: VertexId, dst: VertexId, numParts: PartitionID): PartitionID = {
    //第一步:对分区数先求平方根,然后向上取整;
      val ceilSqrtNumParts: PartitionID = math.ceil(math.sqrt(numParts)).toInt
      //作用:分区时,顶点ID乘以一个较大的素数,提高每个分区中数据量相对均衡
      val mixingPrime: VertexId = 1125899906842597L
        //第二步:判断分区数能否完全开方
      if (numParts == ceilSqrtNumParts * ceilSqrtNumParts) {
        //能完全开方的情况:
        // Use old method for perfect squared to ensure we get same results
        //第三步:将源顶点ID乘以给定的素数取绝对值,再与第一步求值取余作为列号;
        val col: PartitionID = (math.abs(src * mixingPrime) % ceilSqrtNumParts).toInt
       //第四步:将目标顶点ID乘以给定的素数取绝对值,在与第一步求值取余作为行号;
        val row: PartitionID = (math.abs(dst * mixingPrime) % ceilSqrtNumParts).toInt
      //第五步:将列号乘以给定素数再加行号的计算结果与分区数取余 作为分区id返回
        (col * ceilSqrtNumParts + row) % numParts
      } else {
        //分区数不能完全开方
        // Otherwise use new method
        val cols = ceilSqrtNumParts
        val rows = (numParts + cols - 1) / cols
        val lastColRows = numParts - rows * (cols - 1)
        val col = (math.abs(src * mixingPrime) % numParts / rows).toInt
        val row = (math.abs(dst * mixingPrime) % (if (col < cols - 1) rows else lastColRows)).toInt
        col * rows + row
      }
    }
  }

源码解析:
这种方法同时使用到了source vertex和target vertex的id, 它把整个图看成一个稀疏的矩阵, 然后对这个矩阵进行切分. 从保证顶点的备份数不大于
2 * sqrt(numParts) 的限制。这里numParts表示分区数。
下面举个例子来说明该方法(源码注释中的示例)。假设我们有一个拥有12个顶点的图,要把它切分到9台机器。我们可以用下面的稀疏矩阵来表示:

         __________________________________
     v0   | P0 *     | P1       | P2    *  |
     v1   |  ****    |  *       |          |
     v2   |  ******* |      **  |  ****    |
     v3   |  *****   |  *  *    |       *  |
          ----------------------------------
     v4   | P3 *     | P4 ***   | P5 **  * |
     v5   |  *  *    |  *       |          |
     v6   |       *  |      **  |  ****    |
     v7   |  * * *   |  *  *    |       *  |
          ----------------------------------
     v8   | P6   *   | P7    *  | P8  *   *|
     v9   |     *    |  *    *  |          |
     v10  |       *  |      **  |  *  *    |
     v11  | * <-E    |  ***     |       ** |

上面的例子中*表示分配到处理器上的边。E表示连接顶点v11和v1的边,它被分配到了处理器P6上。为了获得边所在的处理器,我们将矩阵切分为sqrt(numParts) * sqrt(numParts)块。
注意,上图中与顶点v11相连接的边只出现在第一列的块(P0,P3,P6)或者最后一行的块(P6,P7,P8)中,这保证了V11的副本数不会超过2 * sqrt(numParts)份,在上例中即副本不能超过6份。
在上面的例子中,P0里面存在很多边,这会造成工作的不均衡。为了提高均衡,我们首先用顶点id乘以一个大的素数,然后再shuffle顶点的位置。乘以一个大的素数本质上不能解决不平衡的问题,只是减少了不平衡的情况发生。

1D分区策略(EdgePartiition1D)

/**
   * Assigns edges to partitions using only the source vertex ID, colocating edges with the same
   * source.
   */
case object EdgePartition1D extends PartitionStrategy {
    override def getPartition(src: VertexId, dst: VertexId, numParts: PartitionID): PartitionID = {
//指定一个较大的素数,顶点ID乘以该素数,提高各个分区中数据的均衡
      val mixingPrime: VertexId = 1125899906842597L
     //源顶点乘以素数取绝对值,再与分区数取余,作为分区ID返回
      (math.abs(src * mixingPrime) % numParts).toInt
    }
  }

这种方法仅仅根据source vertex 的 id来分配分区, 这样同一个vertex出来的edge会被切到同一个分区, supernode问题得不到任何缓解, 仅仅适用于比较稀疏的图。这种方式容易造成超级分区的问题。

随机点切割(RandomVertexCut)

/**
   * Assigns edges to partitions by hashing the source and destination vertex IDs, resulting in a
   * random vertex cut that colocates all same-direction edges between two vertices.
   */
case object RandomVertexCut extends PartitionStrategy {
    override def getPartition(src: VertexId, dst: VertexId, numParts: PartitionID): PartitionID = {
  //根据源顶点id和目标顶点id计算hash值然后取绝对值,然后与分区数取余作为分区id
      math.abs((src, dst).hashCode()) % numParts
    }
  }

这个方法比较简单, 设计上使用source vertex和target vertex的id来做hash,这样两个顶点之间相同方向的边会分配到同一个分区。

正则随机点切割

/**
   * Assigns edges to partitions by hashing the source and destination vertex IDs in a canonical
   * direction, resulting in a random vertex cut that colocates all edges between two vertices,
   * regardless of direction.
   */
case object CanonicalRandomVertexCut extends PartitionStrategy {
    override def getPartition(src: VertexId, dst: VertexId, numParts: PartitionID): PartitionID = {
//比较源顶点id和目标顶点id的大小,
      if (src < dst) {
        math.abs((src, dst).hashCode()) % numParts
      } else {
        math.abs((dst, src).hashCode()) % numParts
      }
    }
  }

这种分割方法和前一种方法类似, 只不过把id比较小的vertex放在前面, 这样两个顶点之间所有的边都会分配到同一个分区,而不管方向如何。

目前,Graphx默认的分区策略是使用图形构建中提供的边的初始分区(使用边的 srcId 进行哈希分区,将边数据以多分区形式分布在集群),也即边RDD的分区策略,而边RDD[Edge[srcId,dstId,ED]].在边RDD中的边对象是(srcid,dstId,ED)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

stay_running

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值