graphx翻译

overview

graphx是spark中的一个新的成员,用于图和图并行计算。它通过引入一个新的图抽象扩展了spark RDD:一个有向多元图,每个节点和边都有相应的属性。为了支持图计算,graphx开放了一系列基础操作符(比如subgraph,joinVertices和aggregateMessages),和Pregel API的优化版本一样。此外,graphx包含了一个不断增加的图算法和建立图的集合,用来精简图分析的任务。

Migrating from spark1.1

spark1.5.2中的Graphx包含一些用户接口API的变化:

1. 为了提高性能,我们引入了一个mapReduceTriplets的新版本,叫做aggregateMessages,它通过一个callback(EdgeContext)来获取从mapReduceTriplets得到的消息,而不是通过返回值。我们反对mapReduceTriplets,鼓励用户查阅transition guide。

2. 在spark1.0和1.1中

EdgeRDD的特性从EdgeRDD[ED]切换到EdgeRDD[ED,VD]来容许caching优化,我们因此发现了一个更优雅的解,将这个类型重塑为更自然的EdgeRDD[ED]类型。

Getting Started

graphx开始的第一步,你需要导入Spark和Graphx到你的程序中,如下:

import org.apache.spark._
import org.apache.spark.graphx._
// To make some of the examples work we will also need RDD
import org.apache.spark.rdd.RDD

如果你不用Spark shell,你也需要一个SparkContext。

The Property Graph

属性图是一个有向多重图,每个节点和边都有用户定义的对象。一个有向多重图是一个有向的图,它有多个并行的边,这些边共享相同的源和目标节点。graphx这种支持并行边的能力简化了建模场景,像相同节点中有多重关系的情况。每个节点由唯一的一个64位长字符串标识。graphx没有对节点标识符施加任何顺序约束。相似的,边也有相应的源和目标节点标识符。

图的性质由节点(VD)和边(ED)类型参数化描述,它们是关联每个节点和边的目标类型。

graphx优化节点和边类型的表示,这些类型都是最初的数据类型(如int,double等),通过将它们存储在特定的序列中来降低内存。

一些情况下,需要在一幅图中有不同属性类型的节点,这可以通过继承来完成。比如,为将用户和商品描述为一个双边图,我们可以做以下操作:

class VertexProperty()
case class UserProperty(val name: String) extends VertexProperty
case class ProductProperty(val name: String, val price: Double) extends VertexProperty
// The graph might then have the type:
var graph: Graph[VertexProperty, String] = null

如同RDDs,特征图是不可交互、分布式和错误容忍。对图中值和结构的改变通过新建一个图来完成,在这个图中添加你所希望的改变之处。注意到,原始图的本质部分(如未受影响的结构、属性等)会在新的图中再利用,从而减少继承函数结构的消耗。图通过一系列vertex partitioning heuristics来分割到不同executors。

逻辑上特征图对应一对RDDs,为每个节点和边描述特性。因此,图类包含成员来描述图中节点和边

class Graph[VD, ED] {
  val vertices: VertexRDD[VD]
  val edges: EdgeRDD[ED]
}

类VertexRDD[VD]和EdgeRDD[ED]各自扩展、进一步在RDD[(VertexID,VD)]和RDD[Edge[ED]]优化。VertexRDD[VD]和EdgeRDD[ED]提供额外的函数。我们在vertex and edge RDDs中更详细讨论VertexRdd和EdgeRDD API,但现在它们可以看作RDD[(VertexID,VD)]和RDD[Edge[ED]]的简单RDDs形式。

Example Property Graph

假设我们构建一个包含各种各样协作者的特征图,节点特征可能包括用户名和职业,我们可以用字符串注释边从而描述协作者之间的关系。

The Property Graph

结果图将会有以下类型签名:

val userGraph: Graph[(String, String), String]

有很多方法可以从源文件、RDDs、以及合成发生器中构建特征图,这会在graph builders环节详细讨论。可能最通用的方法是使用Graph object,例如下面代码是从RDDs集合中构建图。

// Assume the SparkContext has already been constructed
val sc: SparkContext
// Create an RDD for the vertices
val users: RDD[(VertexId, (String, String))] =
  sc.parallelize(Array((3L, ("rxin", "student")), (7L, ("jgonzal", "postdoc")),
                       (5L, ("franklin", "prof")), (2L, ("istoica", "prof"))))
// Create an RDD for edges
val relationships: RDD[Edge[String]] =
  sc.parallelize(Array(Edge(3L, 7L, "collab"),    Edge(5L, 3L, "advisor"),
                       Edge(2L, 5L, "colleague"), Edge(5L, 7L, "pi")))
// Define a default user in case there are relationship with missing user
val defaultUser = ("John Doe", "Missing")
// Build the initial Graph
val graph = Graph(users, relationships, defaultUser)

在上面例子中,我们利用Edge类。Edge有一个scrID和一个dstID对应源和目标节点标识符,此外Edge类也有一个attr属性来存储边属性。

我们可以通过graph.vertices和graph.edges将图分解成节点和边两种视角。

val graph: Graph[(String, String), String] // Constructed from above
// Count all users which are postdocs
graph.vertices.filter { case (id, (name, pos)) => pos == "postdoc" }.count
// Count all the edges where src > dst
graph.edges.filter(e => e.srcId > e.dstId).count

注意到graph.vertices返回一个VertexRDD[(String,String)],这个扩展了RDD[(VertexID,(String,String))],因此我们使用scala的case表达式来分解这个元组。另一方面,graph.edges返回一个EdgeRDD包含Edge[String]对象。我们也可以用case class类型的构造函数,如下所示

graph.edges.filter { case Edge(src, dst, prop) => src > dst }.count

除了从节点和边的角度看特征图外,graphx也提供了三元组视角。这个三元组视角逻辑上联合节点和边特性,产出一个RDD[EdgeTriplet[VD,ED]],包括EdgeTriplet类的实例,这种联合可以标示为以下SQL表达:

SELECT src.id, dst.id, src.attr, e.attr, dst.attr
FROM edges AS e LEFT JOIN vertices AS src, vertices AS dst
ON e.srcId = src.Id AND e.dstId = dst.Id

或者图形化表示为:

Edge Triplet

这个EdgeTriplet类通过增加srcAttr和dstAttr成员扩展Edge类,这两个成员包括源、目标特征。我们可以用图的三元组视角来产生描述用户间关系的字符串集合。

val graph: Graph[(String, String), String] // Constructed from above
// Use the triplets view to create an RDD of facts.
val facts: RDD[String] =
  graph.triplets.map(triplet =>
    triplet.srcAttr._1 + " is the " + triplet.attr + " of " + triplet.dstAttr._1)
facts.collect.foreach(println(_))

Graph Operators

就像RDDs有基本操作符像map,filter,reduceByKey一样,特征图也有一系列基本操作符,这些操作符采用用户定义的函数,产生经过特征、结果变换后的新图。核心操作符优化Graph中定义的操作,便捷的操作符作为核心操作符的一种组成被定义在GraphOps中。然而,由于Scala在GraphOps隐式操作符。例如,我们可以计算一个节点的入度(在GraphOps中定义),如下

val graph: Graph[(String, String), String]
// Use the implicit GraphOps.inDegrees operator
val inDegrees: VertexRDD[Int] = graph.inDegrees

区分图核心操作符与GraphOps可以支持不同的图表示,每个图表示必须提供核心操作符的执行和重用GpraphOps中有用的操作。

Summary List of Operators

以下是Graph和GraphOps中定义函数的总结,为了简化,被表示为Graph的成员。注意到一个函数标识已经被简化,一些更高级的函数功能已经被删除,所以查阅官方AIP文档

/** Summary of the functionality in the property graph */
class Graph[VD, ED] {
  // Information about the Graph ===================================================================
  val numEdges: Long
  val numVertices: Long
  val inDegrees: VertexRDD[Int]
  val outDegrees: VertexRDD[Int]
  val degrees: VertexRDD[Int]
  // Views of the graph as collections =============================================================
  val vertices: VertexRDD[VD]
  val edges: EdgeRDD[ED]
  val triplets: RDD[EdgeTriplet[VD, ED]]
  // Functions for caching graphs ==================================================================
  def persist(newLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, ED]
  def cache(): Graph[VD, ED]
  def unpersistVertices(blocking: Boolean = true): Graph[VD, ED]
  // Change the partitioning heuristic  ============================================================
  def partitionBy(partitionStrategy: PartitionStrategy): Graph[VD, ED]
  // Transform vertex and edge attributes ==========================================================
  def mapVertices[VD2](map: (VertexID, VD) => VD2): Graph[VD2, ED]
  def mapEdges[ED2](map: Edge[ED] => ED2): Graph[VD, ED2]
  def mapEdges[ED2](map: (PartitionID, Iterator[Edge[ED]]) => Iterator[ED2]): Graph[VD, ED2]
  def mapTriplets[ED2](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2]
  def mapTriplets[ED2](map: (PartitionID, Iterator[EdgeTriplet[VD, ED]]) => Iterator[ED2])
    : Graph[VD, ED2]
  // Modify the graph structure ====================================================================
  def reverse: Graph[VD, ED]
  def subgraph(
      epred: EdgeTriplet[VD,ED] => Boolean = (x => true),
      vpred: (VertexID, VD) => Boolean = ((v, d) => true))
    : Graph[VD, ED]
  def mask[VD2, ED2](other: Graph[VD2, ED2]): Graph[VD, ED]
  def groupEdges(merge: (ED, ED) => ED): Graph[VD, ED]
  // Join RDDs with the graph ======================================================================
  def joinVertices[U](table: RDD[(VertexID, U)])(mapFunc: (VertexID, VD, U) => VD): Graph[VD, ED]
  def outerJoinVertices[U, VD2](other: RDD[(VertexID, U)])
      (mapFunc: (VertexID, VD, Option[U]) => VD2)
    : Graph[VD2, ED]
  // Aggregate information about adjacent triplets =================================================
  def collectNeighborIds(edgeDirection: EdgeDirection): VertexRDD[Array[VertexID]]
  def collectNeighbors(edgeDirection: EdgeDirection): VertexRDD[Array[(VertexID, VD)]]
  def aggregateMessages[Msg: ClassTag](
      sendMsg: EdgeContext[VD, ED, Msg] => Unit,
      mergeMsg: (Msg, Msg) => Msg,
      tripletFields: TripletFields = TripletFields.All)
    : VertexRDD[A]
  // Iterative graph-parallel computation ==========================================================
  def pregel[A](initialMsg: A, maxIterations: Int, activeDirection: EdgeDirection)(
      vprog: (VertexID, VD, A) => VD,
      sendMsg: EdgeTriplet[VD, ED] => Iterator[(VertexID,A)],
      mergeMsg: (A, A) => A)
    : Graph[VD, ED]
  // Basic graph algorithms ========================================================================
  def pageRank(tol: Double, resetProb: Double = 0.15): Graph[Double, Double]
  def connectedComponents(): Graph[VertexID, ED]
  def triangleCount(): Graph[Int, ED]
  def stronglyConnectedComponents(numIter: Int): Graph[VertexID, ED]
}

Property Operators

就像RDD map操作符,特征图包括以下操作
class Graph[VD, ED] {
  def mapVertices[VD2](map: (VertexId, VD) => VD2): Graph[VD2, ED]
  def mapEdges[ED2](map: Edge[ED] => ED2): Graph[VD, ED2]
  def mapTriplets[ED2](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2]
}
其中的每个操作符产生一个图,图中节点和边的特性可以由用户定义的map函数修正。
注意到在每个类中,图结构是不受影响的。这是这些操作符的关键特征,使得结果图可以重用原始图的结构索引。下面小段是逻辑上相等的,但是第一个没有保存结果索引,不会
从Graphx系统优化中获益。
val newVertices = graph.vertices.map { case (id, attr) => (id, mapUdf(id, attr)) }
val newGraph = Graph(newVertices, graph.edges)
相反,用mapVertices来保存索引:
val newGraph = graph.mapVertices((id, attr) => mapUdf(id, attr))
这些操作符往往被用于初始化图,为了一个特定的运算或项目。例如,给定一个图,出度作为节点属性(后面会构建这个图),我们初始化这个图
// Given a graph where the vertex property is the out degree
val inputGraph: Graph[Int, String] =
  graph.outerJoinVertices(graph.outDegrees)((vid, _, degOpt) => degOpt.getOrElse(0))
// Construct a graph where each edge contains the weight
// and each vertex is the initial PageRank
val outputGraph: Graph[Double, Double] =
  inputGraph.mapTriplets(triplet => 1.0 / triplet.srcAttr).mapVertices((id, _) => 1.0)

Structural Operators

当前Graphx支持小的结构操作符集,下面是一些基本结构操作符。
class Graph[VD, ED] {
  def reverse: Graph[VD, ED]
  def subgraph(epred: EdgeTriplet[VD,ED] => Boolean,
               vpred: (VertexId, VD) => Boolean): Graph[VD, ED]
  def mask[VD2, ED2](other: Graph[VD2, ED2]): Graph[VD, ED]
  def groupEdges(merge: (ED, ED) => ED): Graph[VD,ED]
}
reverse操作符返回一个所有边反向的新图,这对于计算逆PageRank很有用。因为reverse操作符没有修正节点或边特性或改变边数目,这不需要数据移动或者复制就可以有效执行。
subgraph操作符
mask操作符

Join Operators

很多情况下,有必要从外部集合(RDDs)中结合数据,例如,我们可能有额外的用户属性,想将这些属性和已有的图或者我们可能想将节点属性从一个图到另一个图
这些操作都可以用join操作符完成,下面我们列了关键的join操作符:
class Graph[VD, ED] {
  def joinVertices[U](table: RDD[(VertexId, U)])(map: (VertexId, VD, U) => VD)
    : Graph[VD, ED]
  def outerJoinVertices[U, VD2](table: RDD[(VertexId, U)])(map: (VertexId, VD, Option[U]) => VD2)
    : Graph[VD2, ED]
}
joinVertices操作符将输入RDD联合节点,返回一个新的图

Aggregate Messages

Graphx中核心的聚合操作是aggregateMessages,这个操作符对每个边triplet应用一个用户定义的sendMsg函数来聚合它们目标结点的信息。
class Graph[VD, ED] {
  def aggregateMessages[Msg: ClassTag](
      sendMsg: EdgeContext[VD, ED, Msg] => Unit,
      mergeMsg: (Msg, Msg) => Msg,
      tripletFields: TripletFields = TripletFields.All)
    : VertexRDD[Msg]
}

Map Reduce Triplets Transition Guide (Legacy)

 

Computing Degree Information

 

Graph Builders




posted on 2015-12-23 20:50  sunxs  阅读(81)  评论(0)    收藏  举报

导航