一、Spark GraphX 基础
### --- SparkGraphX基础
~~~ 架构
~~~ 存储模式
~~~ 核心数据结构
~~~ GraphX 与 Spark 其他组件相比相对独立,拥有自己的核心数据结构与算子。
二、GraphX 架构
### --- GraphX的整体架构可以分为三个部分:
~~~ 算法层。基于 Pregel 接口实现了常用的图算法。
~~~ 包括 PageRank、SVDPlusPlus、TriangleCount、 ConnectedComponents、StronglyConnectedConponents 等算法
~~~ 接口层。在底层 RDD 的基础之上实现了 Pregel 模型 BSP 模式的计算接口
~~~ 底层。图计算的核心类,包含:VertexRDD、EdgeRDD、RDD[EdgeTriplet]
三、存储模式
### --- 存储模式
~~~ 巨型图的存储总体上有边分割和点分割两种存储方式。
~~~ 2013年,GraphLab2.0将其存储方式由边分割变为点分割,
~~~ 在性能上取得重大提升,目前基本上被业界广泛接受并使用。
### --- 存储模式:边分割、点分割
~~~ 边分割(Edge-Cut):每个顶点都存储一次,但有的边会被打断分到两台机器上。
~~~ 这样做的好处是节省存储空间;坏处是对图进行基于边的计算时,
~~~ 对于一条两个顶点被分到不同机器上的边来说,要跨机器通信传输数据,内网通信流量大
~~~ 点分割(Vertex-Cut):每条边只存储一次,都只会出现在一台机器上。
~~~ 邻居多的点会被复制到多台机器上,增加了存储开销,同时会引发数据同步问题。
~~~ 好处是可以大幅减少内网通信量
### --- 存储分布图说明
~~~ 虽然两种方法互有利弊,但现在是点分割占上风,
~~~ 各种分布式图计算框架都将自己底层的存储形式变成了点分割。
### --- 主要原因有以下两个:
~~~ 磁盘价格下降,存储空间不再是问题,而内网的通信资源没有突破性进展,
~~~ 集群计算时内网带宽是宝贵的,时间比磁盘更珍贵。这点就类似于常见的空间换时间的策略;
~~~ 在当前的应用场景中绝大多数网络都是“无尺度网络”遵循幂律分布,不同点的邻居数量相差非常悬殊。
~~~ 而边分割会使那些多邻居的点所相连的边大多数被分到不同的机器上,
~~~ 这样的数据分布会使得内网带宽更加捉襟见肘,于是边分割存储方式被渐渐抛弃了;
四、核心数据结构
### --- 核心数据结构
~~~ 核心数据结构包括:graph、vertices、edges、triplets
~~~ GraphX API 的开发语言目前仅支持 Scala。GraphX 的核心数据结构 Graph 由 RDD封装而成。
五、Graph
### --- Graph
~~~ GraphX 用属性图的方式表示图,顶点有属性,边有属性。
~~~ 存储结构采用边集数组的形式,即一个顶点表,一个边表,如下图所示:
~~~ 顶点 ID 是非常重要的字段,它不光是顶点的唯一标识符,也是描述边的唯一手段。
~~~ 顶点表与边表实际上就是 RDD,它们分别为 VertexRDD 与 EdgeRDD。
~~~ 在 Spark 的源码中,Graph 类如下:
### --- 源码提取说明
~~~ # 源码提取说明:graph.scala
~~~ # 41行~105行
abstract class Graph[VD: ClassTag, ED: ClassTag] protected () extends Serializable {
/**
* An RDD containing the vertices and their associated attributes.
*
* @note vertex ids are unique.
* @return an RDD containing the vertices in this graph
*/
val vertices: VertexRDD[VD]
/**
* An RDD containing the edges and their associated attributes. The entries in the RDD contain
* just the source id and target id along with the edge data.
*
* @return an RDD containing the edges in this graph
*
* @see `Edge` for the edge type.
* @see `Graph#triplets` to get an RDD which contains all the edges
* along with their vertex data.
*
*/
val edges: EdgeRDD[ED]
/**
* An RDD containing the edge triplets, which are edges along with the vertex data associated with
* the adjacent vertices. The caller should use [[edges]] if the vertex data are not needed, i.e.
* if only the edge data and adjacent vertex ids are needed.
*
* @return an RDD containing edge triplets
*
* @example This operation might be used to evaluate a graph
* coloring where we would like to check that both vertices are a
* different color.
* {{{
* type Color = Int
* val graph: Graph[Color, Int] = GraphLoader.edgeListFile("hdfs://file.tsv")
* val numInvalid = graph.triplets.map(e => if (e.src.data == e.dst.data) 1 else 0).sum
* }}}
*/
val triplets: RDD[EdgeTriplet[VD, ED]]
/**
* Caches the vertices and edges associated with this graph at the specified storage level,
* ignoring any target storage levels previously set.
*
* @param newLevel the level at which to cache the graph.
*
* @return A reference to this graph for convenience.
*/
def persist(newLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, ED]
/**
* Caches the vertices and edges associated with this graph at the previously-specified target
* storage levels, which default to `MEMORY_ONLY`. This is used to pin a graph in memory enabling
* multiple queries to reuse the same construction process.
*/
def cache(): Graph[VD, ED]
/**
* Mark this Graph for checkpointing. It will be saved to a file inside the checkpoint
* directory set with SparkContext.setCheckpointDir() and all references to its parent
* RDDs will be removed. It is strongly recommended that this Graph is persisted in
* memory, otherwise saving it on a file will require recomputation.
*/
def checkpoint(): Unit
### --- 参数说明
~~~ vertices 为顶点表,VD 为顶点属性类型
~~~ edges 为边表,ED 为边属性类型
~~~ 可以通过 Graph 的 vertices 与 edges 成员直接得到顶点 RDD 与边 RDD
~~~ 顶点 RDD 类型为 VerticeRDD,继承自 RDD[(VertexId, VD)]
~~~ 边 RDD 类型为 EdgeRDD,继承自 RDD[Edge[ED]]
六、vertices
### --- vertices
~~~ vertices对应着名为 VertexRDD 的RDD。这个RDD由顶点id和顶点属性两个成员变量。
### --- 源码提取说明
~~~ # 源码提取说明:VertexRDD.scala
~~~ # 55行
abstract class VertexRDD[VD](
sc: SparkContext,
deps: Seq[Dependency[_]]) extends RDD[(VertexId, VD)](sc, deps) {
### --- VertexRDD继承
~~~ VertexRDD继承自 RDD[(VertexId, VD)],这里VertexId表示顶点id,VD表示顶点所带的属性的类别。
~~~ VertexId 实际上是一个Long类型的数据;
### --- 源码提取说明
~~~ # 源码提取说明:graphx\package.scala
~~~ # 26行
package object graphx {
/**
* A 64-bit vertex identifier that uniquely identifies a vertex within a graph. It does not need
* to follow any ordering or any constraints other than uniqueness.
*/
type VertexId = Long
/** Integer identifier of a graph partition. Must be less than 2^30. */
// TODO: Consider using Char.
type PartitionID = Int
private[graphx] type VertexSet = OpenHashSet[VertexId]
}
七、edges
### --- edges
~~~ # edges对应着EdgeRDD。这个RDD拥有三个成员变量,分别是源顶点id、目标顶点id以及边属性。
~~~ # 源码提取说明:EdgeRDD.scala
~~~ # 39行
abstract class EdgeRDD[ED](
sc: SparkContext,
deps: Seq[Dependency[_]]) extends RDD[Edge[ED]](sc, deps) {
### --- Edge代表边,由 源顶点id、目标顶点id、以及边的属性构成。
~~~ # 源码提取说明:Edge.scala
~~~ # 32行
case class Edge[@specialized(Char, Int, Boolean, Byte, Long, Float, Double) ED] (
var srcId: VertexId = 0,
var dstId: VertexId = 0,
var attr: ED = null.asInstanceOf[ED])
extends Serializable {
八、triplets
### --- triplets
~~~ triplets 表示边点三元组,如下图所示(其中圆柱形分别代表顶点属性与边属性):
### --- triplets说明
~~~ 通过 triplets 成员,用户可以直接获取到起点顶点、起点顶点属性、终点顶点、终点顶点属性、
~~~ 边与边属性信息。triplets 的生成可以由边表与顶点表通过 ScrId 与DstId 连接而成。
~~~ triplets对应着EdgeTriplet。
~~~ 它是一个三元组视图,这个视图逻辑上将顶点和边的属性保存为一个RDD[EdgeTriplet[VD, ED]]。
### --- 源码提取说明
~~~ # 源码提取说明:EdgeTriplet.scala
~~~ # 26行
class EdgeTriplet[VD, ED] extends Edge[ED] {
/**
* The source vertex attribute
*/
var srcAttr: VD = _ // nullValue[VD]
/**
* The destination vertex attribute
*/
var dstAttr: VD = _ // nullValue[VD]
/**
* Set the edge properties of this triplet.
*/
protected[spark] def set(other: Edge[ED]): EdgeTriplet[VD, ED] = {
srcId = other.srcId
dstId = other.dstId
attr = other.attr
this
}