GraphX中顶点和边的RDD操作
GraphX 公开了存储在图中的顶点和边的 RDD 视图。但是,由于 GraphX 在优化的数据结构中维护了顶点和边,并且这些数据结构提供了额外的功能,所以顶点和边分别返回为 VertexRDDVertexRDD 和 EdgeRDDEdgeRDD。
一、顶点RDD(VertexRDDs)
VertexRDD[A] 扩展了 RDD[(VertexId, A)] 并添加了每个 VertexId 仅出现一次的附加约束。此外,VertexRDD[A] 表示一组顶点,每个顶点都具有类型 A 的属性。在内部,这是通过将顶点属性存储在可重用的哈希映射数据结构中来实现的。因此,如果两个 VertexRDD 是从相同的基础 VertexRDDVertexRDD 派生的(例如,通过 filter 或 mapValues),它们可以在恒定时间内连接,而无需哈希评估。为了利用这种索引数据结构,VertexRDDVertexRDD 开源了以下附加功能:
class VertexRDD[VD] extends RDD[(VertexId, VD)] { // 过滤顶点集但保留内部索引 def filter(pred: Tuple2[VertexId, VD] => Boolean): VertexRDD[VD] // 在不更改 id 的情况下转换值(保留内部索引) def mapValues[VD2](map: VD => VD2): VertexRDD[VD2] def mapValues[VD2](map: (VertexId, VD) => VD2): VertexRDD[VD2] // 根据 VertexId 显示此集合唯一的顶点 def minus(other: RDD[(VertexId, VD)]) // 从这个集合中删除出现在另一个集合中的顶点 def diff(other: VertexRDD[VD]): VertexRDD[VD] // 利用内部索引来加速连接的连接运算符 def leftJoin[VD2, VD3](other: RDD[(VertexId, VD2)])(f: (VertexId, VD, Option[VD2]) => VD3): VertexRDD[VD3] def innerJoin[U, VD2](other: RDD[(VertexId, U)])(f: (VertexId, VD, U) => VD2): VertexRDD[VD2] // 使用这个 RDD 上的索引来加速对输入 RDD 的 `reduceByKey` 操作。 def aggregateUsingIndex[VD2](other: RDD[(VertexId, VD2)], reduceFunc: (VD2, VD2) => VD2): VertexRDD[VD2] }
例如,请注意过滤器运算符如何返回 VertexRDD。 过滤器实际上是使用 BitSet 实现的,从而重用索引并保留与其他 VertexRDD 进行快速连接的能力。 同样,mapValues 运算符不允许 map 函数更改 VertexId,从而可以重用相同的 HashMap 数据结构。 leftJoin 和 innerJoin 都能够识别何时连接来自同一个 HashMap 的两个 VertexRDD,并通过线性扫描而不是昂贵的点查找来实现连接。
aggregateUsingIndex 运算符对于从 RDD[(VertexId, A)] 高效构造新的 VertexRDD 很有用。 从概念上讲,如果我在一组顶点上构造了一个 VertexRDD[B],它是某些 RDD[(VertexId, A)] 中顶点的超集,那么我可以重用索引来聚合,然后索引 RDD[(VertexId, A)]。 例如:
val setA: VertexRDD[Int] = VertexRDD(sc.parallelize(0L until 100L).map(id => (id, 1))) val rddB: RDD[(VertexId, Double)] = sc.parallelize(0L until 100L).flatMap(id => List((id, 1.0), (id, 2.0))) // rddB有200个节点 rddB.count val setB: VertexRDD[Double] = setA.aggregateUsingIndex(rddB, _ + _) // setB有100个节点 setB.count // 现在加入A和B会很快 val setC: VertexRDD[Double] = setA.innerJoin(setB)((id, a, b) => a + b)
二、边RDD(EdgeRDDs)
扩展 RDD[Edge[ED]] 的 EdgeRDD[ED] 将边缘组织在使用 PartitionStrategy 中定义的各种分区策略之一进行分区的块中。 在每个分区内,边缘属性和邻接结构分别存储,以便在更改属性值时最大限度地重复使用。
EdgeRDDEdgeRDD 开源的三个附加功能是:
// 在保留结构的同时变换边缘属性 def mapValues[ED2](f: Edge[ED] => ED2): EdgeRDD[ED2] // 反转边重用属性和结构 def reverse: EdgeRDD[ED] // 加入两个使用相同分区策略分区的 `EdgeRDD`。 def innerJoin[ED2, ED3](other: EdgeRDD[ED2])(f: (VertexId, VertexId, ED, ED2) => ED3): EdgeRDD[ED3]
在大多数应用程序中,我们发现对 EdgeRDD 的操作是通过图运算符完成的,或者依赖于 RDD 基类中定义的操作。