Spark GraphX入门
一、概述
GraphX 是 Spark 四大核心组件之一,它也是使用 Spark 作为计算引擎的,GraphX 是用于图形和图形并行计算的组件,实现了大规模图计算的功能。GraphX 的出现使 Spark 生态系统变得更加完善和丰富,同时它能够与 Spark 生态系统的其它组件天然融合,再加上它强大的图数据处理能力,在业届得到了广泛的运用。
在高层次上,GraphX 通过引入一个新的图形抽象来扩展 Spark RDD :即顶点
和边
带有特性的有向多重图
。GraphX 提供了一些基本运算符以及优化了的 谷歌的Pregel API
。 此外,GraphX 提供了大量的图算法和构建器,以简化图形分析任务。
1.1 图
图是由很多个节点(vertex)构成的,节点之间通过边(edge)进行连接。图在网络科学中被称为网络。实际应用中,有很多图结构数据:
- 计算机网络:由许多节点(计算机或者路由器)以及节点之间的边(网线)构成的网络;
- 城市交通系统:由节点(路口)和边(道路)构成的图;
- 微信社交网络:由节点(个人或公众号)和边(关注或点赞)构成的图;
- 淘宝的交易网络:由节点(个人或商品)和边(购买或收藏)构成的图;
- 网页的链接网络:由节点(网页)和边(链接)构成的图。
在GraphX中,有两个RDD, 一个是点RDD,一个是边RDD。一个是节点(vertex),另一个是边(edge)。
在GraphX的实际应用场景中,比如:Twitter、Facebook、微博、微信等,人与人之间存在着很多的关系链。这些地方产生的数据更加适合使用图处理来进行计算。图的分布式或者并行处理其实是把图拆分成很多个子图,然后分别对这些子图进行计算,计算的时候可以分别迭代进行分阶段的计算,即对图进行并行计算。
1.2 图的处理技术
图处理技术包括图数据库、图数据查询、图数据分析和图数据可视化。
- 图数据库:Neo4j、Titan、OrientDB、DEX和InfiniteGraph等基于遍历算法的、实时的图数据库;
- 图数据查询:对图数据库中的内容进行查询;
- 图数据分析:Google Pregel、Spark GraphX、GraphLab等图计算软件。传统的数据分析方法侧重于事物本身,即实体,例如银行交易、资产注册等等。而图数据不仅关注事物,还关注事物之间的联系。例如,如果在通话记录中发现张三曾打电话给李四,就可以将张三和李四关联起来,这种关联关系提供了与两者相关的有价值的信息,这样的信息是不可能仅从两者单纯的个体数据中获取的。
- 图数据可视化:OLTP风格的图数据库或者OLAP风格的图数据分析系统(或称为图计算软件),都可以应用图数据库可视化技术。需要注意的是,图可视化与关系数据可视化之间有很大的差异,关系数据可视化的目标是对数据取得直观的了解,而图数据可视化的目标在于对数据或算法进行调试。
通常,在进行大规模图数据分析时,都会组合使用图数据库(比如Neo4j)和图计算软件(比如Spark Gtaph X)。如果只是简单地存储实体之间的关系,那么,只要使用图数据库即可,不需要使用图计算软件。业界的一个发展趋势是,会出现可以同时处理OLAP和OLTP类型应用的图系统,也就是说,在图数据库与图计算框架之间的紧密集成,或者通过Neo4j这样的图数据库与Spark GraphX这样的图处理系统间的无缝互操作来实现,或者在将来某一天这些功能会出现在同一产品中。
二、GraphX的核心概念及实操
官方文档地址:https://spark.apache.org/docs/latest/graphx-programming-guide.html
首先需要将 Spark 和 GraphX 导入到项目中,如下所示:
import org.apache.spark._
import org.apache.spark.graphx._
import org.apache.spark.rdd.RDD
2.1 属性图
属性图是一个有向多重图,它的每个顶点和每条边都附有用户定义的对象。作为有向图,有向多重图可能有多个平行的边来共享相同的源顶点和目标顶点。作为多重图,它支持并行边,这个特性简化了许多涉及多重关系的建模场景。每个顶点的主键是一个长度为64 bit的唯一标识符(VertexID)。GraphX没有为顶点添加任何顺序的约束。类似地,每一条边有对应的源顶点和目标顶点的标识符。
假设我们要构造一个由 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。
有许多方法可以构建属性图,例如从原始文件、RDD 以及合成生成器(graph builders),最通用的方法是使用 Graph 对象。首先介绍从RDD的集合中构造一个图,例如:
// Assume the SparkContext has already been constructed val sc: SparkContext // Create an RDD for the vertices val users: RDD[(VertexId, (String, String))] = sc.parallelize(Seq((3L, ("rxin", "student")), (7L, ("jgonzal", "postdoc")), (5L, ("franklin", "prof")), (2L, ("istoica", "prof")))) // Create an RDD for edges val relationships: RDD[Edge[String]] = sc.parallelize(Seq(Edge(3L, 7L, "collab"), Edge(5L, 3L, "advisor"), Edge(2L, 5L, "colleague"), Edge(5L, 7L, "pi"))) // 定义的默认用户,以防缺少与用户的关系 val defaultUser = ("John Doe", "Missing") // Build the initial Graph val graph = Graph(users, relationships, defaultUser)
在上面的例子中,我们使用了 Edge 样例类。 边有一个 srcId 和一个 dstId对应于源和目标顶点标识符。 此外,Edge 类还有一个存储边缘属性的 attr 成员。
我们还可以分别使用 graph.vertices 和 graph.edges 方法将图解构为各自的顶点和边视图,如下所示:
//接上面的代码 val graph: Graph[(String, String), String] // 计算所有博士后用户 graph.vertices.filter { case (id, (name, pos)) => pos == "postdoc" }.count // 计算所有 src > dst 的边 graph.edges.filter(e => e.srcId > e.dstId).count
请注意,graph.vertices 返回一个 VertexRDD[(String, String)],它扩展了 RDD[(VertexId, (String, String))],因此我们使用 scala case 表达式来解构元组。 另一方面,graph.edges 返回一个包含 Edge[String] 对象的 EdgeRDD,同样可以使用 case 类类型构造函数,如下所示:
graph.edges.filter { case Edge(src, dst, prop) => src > dst }.count
2.2 三元组视图
除了属性图的顶点和边视图之外,GraphX 还公开了一个三元组视图。 三元组视图在逻辑上连接了顶点和边的属性,产生一个包含 EdgeTriplet 类实例的 RDD[EdgeTriplet[VD, ED]]。 这个连接可以用下面的 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
或图形化为:
EdgeTriplet 类(边三元组表示一条边及其相邻顶点的顶点属性,是Edge的子类)通过添加分别包含源和目标属性的 srcAttr 和 dstAttr 成员来扩展 Edge 类。 我们可以使用图的三元组视图来呈现描述用户之间关系的字符串集合。
// 代码接上面 val graph: Graph[(String, String), String] // 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(_))