0x00 教程内容
- Spark GraphX 理论
- GraphX 重要概念与实操
0x01 Spark GraphX 介绍
1. GraphX 介绍
GraphX 是 Spark 四大核心组件之一,它也是使用 Spark 作为计算引擎的,GraphX 是用于图形和图形并行计算的组件,实现了大规模图计算的功能。GraphX 的出现使 Spark 生态系统变得更加完善和丰富,同时它能够与 Spark 生态系统的其它组件天然融合,再加上它强大的图数据处理能力,在工业届得到了广泛的运用。
在高层次上,GraphX 通过引入一个新的图形抽象来扩展 Spark RDD :即顶点和边带有特性的有向多重图。GraphX 提供了一些基本运算符以及优化了的 Pregel API。 此外,GraphX 提供了大量的图算法和构建器,以简化图形分析任务。
GraphX 有两个RDD, 一个是点RDD,一个是边RDD。
Vertex:表示顶点,每个顶点都会有一个唯一标识以及这个顶点的属性 。
Edge:表示有向边,由一个源顶点id,一个目标顶点id以及这条边的属性组成 。
这些关系,都可以转化为相应的表来描述。
2. GraphX 的使用场景
在社交网络中,比如:Twitter、Facebook、微博、微信等,人与人之间存在着很多的关系链。这些地方产生的数据更加适合使用图处理来进行计算。图的分布式或者并行处理其实是把图拆分成很多个子图,然后分别对这些子图进行计算,计算的时候可以分别迭代进行分阶段的计算,即对图进行并行计算。
0x02 GraphX 重要概念与实操
1. 属性图
属性图是一个有向多重图,它的每个顶点和每条边都附有用户定义的对象。作为有向图,有向多重图可能有多个平行的边来共享相同的源顶点和目标顶点。作为多重图,它支持并行边,这个特性简化了许多涉及多重关系的建模场景。每个顶点的主键是一个长度为64 bit的唯一标识符(VertexID)。GraphX没有为顶点添加任何顺序的约束。类似地,每一条边有对应的源顶点和目标顶点的标识符。
因此,属性图的参数是通过顶点(VD)和边的类型(ED)来决定的。
在某些情况下,你可能希望在同一个图里面,顶点能够有不同的属性类型。这个想法可以通过继承实现。举个例子,我们可以对用户和产品进行建模,将其作为一个二分图,然后进行如下的定义:
class VertexProperty() case class UserProperty(val name: String) extends VertexProperty case class ProductProperty(val name: String, val price: Double) extends VertexProperty // 图可能会有这个类型 var graph: Graph[VertexProperty, String] = null
类似于 RDD,属性图是不可变的、分布式的,并且具有容错性。对于图而言,它的值或者结构上的改变,是通过产生带有预期改变的新图来完成的。主要注意的是,原始图的主要部分(即不受影响的结构、属性和索引等)在新图中被重用,以减少固有功能的数据结构成本。通过使用大量的启发式顶点分区,图在不同的执行器里被划分。就像 RDD 一样,图的每个分区可以在发生故障时被不同的机器重建。
属性图在逻辑上对应于一对类型化集合(RDD),该集合编码了每个顶点和每条边属性。因而,图类包含了可以访问图的顶点和边的成员,它的定义如下:
class Graph[VD, ED] { val vertices: VertexRDD[VD] val edges: EdgeRDD[ED] }
VertexRDD[VD]类和EdgeRDD[ED]类分别继承和优化了RDD[(VertexID, VD)]类和RDD[Edge[ED]]类。两者都提供基于图计算和内部优化构建的额外功能。在此你可将其简单地理解为以RDD[(VertexID, VD)]和RDD[Edge[ED]]形式定义的 RDD。
2. 多种方式理解GraphX
a. EdgeTriplet表示
val facts: RDD[String] = graph.triplets.map(triplet => triplet.srcAttr._1 + " is the " + triplet.attr + " of " + triplet.dstAttr._1)
b. SQL表示triplet表示
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
c. 图表示triplet表示
3. 属性图编程示例
现在假设我们要构建一个由许多来自 GraphX 项目组的协作者组成的属性图。顶点的属性可能包含用户名
和职业
。我们应该用一个可以描述协作者间的关系的字符串来注释图的边。依然是使用上面的图:
启动Spark Shell:
spark-shell
编写案例代码:
import org.apache.spark.graphx.{Edge, Graph, VertexId} import org.apache.spark.rdd.RDD // 如果不是在 `spark-shell` 中操作,则还需要额外引入 `SparkContext`。 // val sc: SparkContext // 创建一个RDD用于表示顶点 val users: RDD[(VertexId, (String, String))] = sc.parallelize(Array((3L, ("xiaoshao", "student")), (7L, ("laoshao", "Postdoctoral")), (5L, ("laonai", "professor")), (2L, ("laoyi", "professor")))) // 创建一个RDD用于表示边 val relationships: RDD[Edge[String]] = sc.parallelize(Array(Edge(3L, 7L, "partner"), Edge(5L, 3L, "mentor"),Edge(2L, 5L, "worker"), Edge(5L, 7L, "leader"))) // 定义默认的用户,用于建立与缺失的用户之间的关系 val defaultUser = ("spark", "default") // 构造图对象,即初始化 val graph = Graph(users, relationships, defaultUser)
Edge类有srcId
和dstId
,分别对应于源顶点
和目标顶点
的标识符。另外,Edge类有一个名为attr
的成员,用于存储边的属性,可以结合着表来理解。
执行过程如图:
我们可以进一步操作,使用graph.vertices
和graph.edges
函数来解构一个图,将其转化为各个顶点和边的视图,并且实现一些简单的统计操作:
// 看看博士后的有多少人 graph.vertices.filter { case (id, (name, pos)) => pos == "Postdoctoral" }.count
// 源顶点id大于目标顶点id的数量 graph.edges.filter(e => e.srcId > e.dstId).count
// 显示关系 val facts: RDD[String] = graph.triplets.map(triplet => triplet.srcAttr._1 + " is the " + triplet.attr + " of " + triplet.dstAttr._1) facts.collect.foreach(println(_))
解释:graph.vertices函数的返回值类型是 VertexRDD[(String, String)],它继承了 RDD[(VertexID, (String, String))],所以我们可以用 Scala 的 case 表达式来解构这个元组。另外,graph.edges函数的返回值类型是EdgeRDD,它包含了Edge[String]对象。
0xFF 总结
- 更多学习请参考官网:https://spark.apache.org/docs/latest/graphx-programming-guide.html
- 推荐一本入门GraphX的入门书籍:《Spark GraphX实战》,挺适合初学者入门。