SparkGraphX 快速入门

1.图

图是由顶点和边组成的,并非代数中的图。图可以对事物以及事物之间的关系建模,图可以用来表示自然发生的连接数据,如:
社交网络
互联网web页面
常用的应用有:
在地图应用中找到最短路径
基于与他人的相似度图,推荐产品、服务、人际关系或媒体

1.2  GraphX的框架

设计 GraphX 时,点分割和 GAS 都已成熟,在设计和编码中针对它们进行了优化,并在功能和性能之间寻找最佳的平衡点。如同 Spark 本身,每个子模块都有一个核心抽象。 GraphX 的核心抽象是 Resilient Distributed Property Graph ,一种点和边都带属性的有向多重图。它扩展了 Spark RDD 的抽象,有 Table Graph 两种视图,而只需要一份物理存储。两种视图都有自己独有的操作符,从而获得了灵活操作和执行效率。

2.术语

2.1    顶点和边

一般关系图中,事物为顶点,关系为边

2.2    有向图和无向图

在有向图中,一条边的两个顶点一般扮演者不同的角色,比如父子关系、页面A连接向页面B;
在一个无向图中,边没有方向,即关系都是对等的,比如qq中的好友。
GraphX中有一个重要概念,所有的边都有一个方向,那么图就是有向图,如果忽略边的方向,就是无向图。

2.3    有环图和无环图

有环图是包含循环的,一系列顶点连接成一个环。无环图没有环。在有环图中,如果不关心终止条件,算法可能永远在环上执行,无法退出。

2.4    度、出边、入边、出度、入度

度:表示一个顶点的所有边的数量
出边:是指从当前顶点指向其他顶点的边
入边:表示其他顶点指向当前顶点的边
出度:是一个顶点出边的数量
入度:是一个顶点入边的数量

2.5    超步

图进行迭代计算时,每一轮的迭代叫做一个超步

三种视图及操作

  Spark中图有以下三种视图可以访问,分别通过graph.vertices,graph.edges,graph.triplets来访问。


下面有个示例:
/**
  * Author: king
  * Email: [email protected]
  * Datetime: Created In 2018/4/28 13:43
  * Desc: as follows.
  */
package zy.rttx.spark.graphx.learning

import org.apache.log4j.{Level, Logger}
import org.apache.spark._
import org.apache.spark.graphx.{VertexId, _}
// To make some of the examples work we will also need RDD
import org.apache.spark.rdd.RDD


object GetingStart {
  def main(args: Array[String]): Unit = {
    //屏蔽日志
    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
    //设置运行环境
    val conf =new SparkConf().setAppName("getingStart").setMaster("local[2]")
    val sc =new SparkContext(conf)
    //设置顶点和边,注意顶点和边都是用元组定义的 Array
    //顶点的数据类型是 VD:(String,String)

    val vertexArray= Array(
      (3L,("rxin","student")),
      (7L,("jgonzal","postdoc")),
      (5L,("franklin","prof")),
      (2L,("istoica","prof")))//student:学生,postdoc:博士后,prof:教授
    //边的数据类型 ED:String
    val edgeArray = Array(
      Edge(3L, 7L, "collab"),
      Edge(5L, 3L, "advisor"),
      Edge(2L, 5L, "colleague"),
      Edge(5L, 7L, "pi"))
    //构造 vertexRDD 和 edgeRDD
    val vertexRDD: RDD[(Long, (String, String))] = sc.parallelize(vertexArray)
    val edgeRDD: RDD[Edge[String]] = sc.parallelize(edgeArray)
    /*// 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)*/
    //构造图 Graph[VD,ED]
    val graph: Graph[(String, String), String] = Graph(vertexRDD, edgeRDD)
    // Count all users which are postdocs ,vertices将图转换为矩阵
    println("all postdocs have:"+graph.vertices.filter { case (id, (name, pos)) => pos == "postdoc" }.count)
    // Count all the edges where src > dst
    println(graph.edges.filter(e => e.srcId > e.dstId).count)

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

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

    // 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(_))
  }

}
示例中关系如下;

建立视图:

 上面是一个简单的例子,说明如何建立一个属性图,那么建立一个图主要有哪些方法呢?我们先看图的定义:

object Graph {
  def apply[VD, ED](
      vertices: RDD[(VertexId, VD)],
      edges: RDD[Edge[ED]],
      defaultVertexAttr: VD = null)
    : Graph[VD, ED]

  def fromEdges[VD, ED](
      edges: RDD[Edge[ED]],
      defaultValue: VD): Graph[VD, ED]

  def fromEdgeTuples[VD](
      rawEdges: RDD[(VertexId, VertexId)],
      defaultValue: VD,
      uniqueEdges: Option[PartitionStrategy] = None): Graph[VD, Int]

}
  由上面的定义我们可以看到,GraphX主要有三种方法可以建立图: 
          (1)在构造图的时候,会自动使用apply方法,因此前面那个例子中实际上就是使用apply方法。相当于Java/C++语言的构造函数。有三个参数,分别是:vertices: RDD[(VertexId, VD)], edges: RDD[Edge[ED]], defaultVertexAttr: VD = null),前两个必须有,最后一个可选择。“顶点“和”边“的RDD来自不同的数据源,与Spark中其他RDD的建立并没有区别。 

          这里再举读取文件,产生RDD,然后利用RDD建立图的例子:

(1)读取文件,建立顶点和边的RRD,然后利用RDD建立属性图

//读入数据文件
val articles: RDD[String] = sc.textFile("E:/data/graphx/graphx-wiki-vertices.txt")
val links: RDD[String] = sc.textFile("E:/data/graphx/graphx-wiki-edges.txt")

//装载“顶点”和“边”RDD
val vertices = articles.map { line =>
    val fields = line.split('\t')
      (fields(0).toLong, fields(1))
    }//注意第一列为vertexId,必须为Long,第二列为顶点属性,可以为任意类型,包括Map等序列。

val edges = links.map { line =>
    val fields = line.split('\t')
      Edge(fields(0).toLong, fields(1).toLong, 1L)//起始点ID必须为Long,最后一个是属性,可以为任意类型
    }
//建立图
val graph = Graph(vertices, edges, "").persist()//自动使用apply方法建立图

(2)Graph.fromEdges方法:这种方法相对而言最为简单,只是由”边”RDD建立图,由边RDD中出现所有“顶点”(无论是起始点src还是终点dst)自动产生顶点vertextId,顶点的属性将被设置为一个默认值。 
      Graph.fromEdges allows creating a graph from only an RDD of edges, automatically creating any vertices mentioned by edges and assigning them the default value. 
          举例如下

//读入数据文件        
val records: RDD[String] = sc.textFile("/microblogPCU/microblogPCU/follower_followee")   
//微博数据:000000261066,郜振博585,3044070630,redashuaicheng,1929305865,1994,229,3472,male,first
// 第三列是粉丝Id:3044070630,第五列是用户Id:1929305865
val followers=records.map {case x => val fields=x.split(",")
          Edge(fields(2).toLong, fields(4).toLong,1L )       
      }    
val graph=Graph.fromEdges(followers, 1L)

(3)Graph.fromEdgeTuples方法 
          Graph.fromEdgeTuples allows creating a graph from only an RDD of edge tuples, assigning the edges the value 1, and automatically creating any vertices mentioned by edges and assigning them the default value. It also supports deduplicating the edges; to deduplicate, pass Some of a PartitionStrategy as the uniqueEdges parameter (for example, uniqueEdges = Some(PartitionStrategy.RandomVertexCut)). A partition strategy is necessary to colocate identical edges on the same partition so they can be deduplicated.

          除了三种方法,还可以用GraphLoader构建图。如下面GraphLoader.edgeListFile: 
(4)GraphLoader.edgeListFile建立图的基本结构,然后Join属性 
(a)首先建立图的基本结构: 
          利用GraphLoader.edgeListFile函数从边List文件中建立图的基本结构(所有“顶点”+“边”),且顶点和边的属性都默认为

object GraphLoader {
  def edgeListFile(
      sc: SparkContext,
      path: String,
      canonicalOrientation: Boolean = false,
      minEdgePartitions: Int = 1)
    : Graph[Int, Int]
}

使用方法如下:

val graph=GraphLoader.edgeListFile(sc, "/data/graphx/followers.txt") 
//文件的格式如下:
//2 1
//4 1
//1 2  依次为第一个顶点和第二个顶点

(b)然后读取属性文件,获得RDD后和(1)中得到的基本结构图join在一起,就可以组合成完整的属性图。

 在Scala语言中,可以用case语句进行形式简单、功能强大的模式匹配

//假设graph顶点属性(String,Int)-(name,age),边有一个权重(int)
val graph: Graph[(String, Int), Int] = Graph(vertexRDD, edgeRDD)
用case匹配可以很方便访问顶点和边的属性及id
graph.vertices.map{
      case (id,(name,age))=>//利用case进行匹配
        (age,name)//可以在这里加上自己想要的任何转换
    }

graph.edges.map{
      case Edge(srcid,dstid,weight)=>//利用case进行匹配
        (dstid,weight*0.01)//可以在这里加上自己想要的任何转换
    }

    也可以通过下标访问

graph.vertices.map{
      v=>(v._1,v._2._1,v._2._2)//v._1,v._2._1,v._2._2分别对应Id,name,age
}

graph.edges.map {
      e=>(e.attr,e.srcId,e.dstId)
}

graph.triplets.map{
      triplet=>(triplet.srcAttr._1,triplet.dstAttr._2,triplet.srcId,triplet.dstId)
    }
    可以不用graph.vertices先提取顶点再map的方法,也可以通过graph.mapVertices直接对顶点进行map,返回是相同结构的另一个Graph,访问属性的方法和上述方法是一模一样的。如下:
graph.mapVertices{
      case (id,(name,age))=>//利用case进行匹配
        (age,name)//可以在这里加上自己想要的任何转换
}

graph.mapEdges(e=>(e.attr,e.srcId,e.dstId))

graph.mapTriplets(triplet=>(triplet.srcAttr._1))

3.1 存储模式

3.1.1 图存储模式

巨型图的存储总体上有边分割和点分割两种存储方式。 2013 年, GraphLab2.0 将其存储方式由边分割变为点分割,在性能上取得重大提升,目前基本上被业界广泛接受并使用。
l 边分割( Edge-Cut :每个顶点都存储一次,但有的边会被打断分到两台机器上。这样做的好处是节省存储空间;坏处是对图进行基于边的计算时,对于一条两个顶点被分到不同机器上的边来说,要跨机器通信传输数据,内网通信流量大。
l 点分割( Vertex-Cut :每条边只存储一次,都只会出现在一台机器上。邻居多的点会被复制到多台机器上,增加了存储开销,同时会引发数据同步问题。好处是可以大幅减少内网通信量。

虽然两种方法互有利弊,但现在是点分割占上风,各种分布式图计算框架都将自己底层的存储形式变成了点分割。主要原因有以下两个。
1. 磁盘价格下降,存储空间不再是问题,而内网的通信资源没有突破性进展,集群计算时内网带宽是宝贵的,时间比磁盘更珍贵。这点就类似于常见的空间换时间的策略。
2. 在当前的应用场景中,绝大多数网络都是“无尺度网络”,遵循幂律分布,不同点的邻居数量相差非常悬殊。而边分割会使那些多邻居的点所相连的边大多数被分到不同的机器上,这样的数据分布会使得内网带宽更加捉襟见肘,于是边分割存储方式被渐渐抛弃了。

2.1.2 GraphX存储模式

Graphx 借鉴 PowerGraph ,使用的是 Vertex-Cut( 点分割 ) 方式存储图,用三个 RDD 存储图数据信息:
l VertexTable(id, data) id Vertex id data Edge data
l EdgeTable(pid, src, dst, data) pid Partion id src 为原定点 id dst 为目的顶点 id
l RoutingTable(id, pid) id Vertex id pid Partion id
点分割存储实现如下图所示:


3.2 计算模式

3.2.1 图计算模式

目前基于图的并行计算框架已经有很多,比如来自 Google Pregel 、来自 Apache 开源的图计算框架 Giraph/HAMA 以及最为著名的 GraphLab ,其中 Pregel HAMA Giraph 都是非常类似的,都是基于 BSP Bulk Synchronous Parallell )模式。
Bulk Synchronous Parallell ,即整体同步并行,它将计算分成一系列的超步( superstep )的迭代( iteration )。从纵向上看,它是一个串行模式,而从横向上看,它是一个并行的模式,每两个 superstep 之间设置一个栅栏( barrier ),即整体同步点,确定所有并行的计算都完成后再启动下一轮 superstep
每一个超步( superstep )包含三部分内容:
1. 计算 compute :每一个 processor 利用上一个 superstep 传过来的消息和本地的数据进行本地计算;
2. 消息传递 :每一个 processor 计算完毕后,将消息传递个与之关联的其它 processors
3. 整体同步点 :用于整体同步,确定所有的计算和消息传递都进行完毕后,进入下一个 superstep

3.2.2GraphX计算模式

如同 Spark 一样, GraphX Graph 类提供了丰富的图运算符,大致结构如下图所示。可以在官方 GraphX Programming Guide 中找到每个函数的详细说明,本文仅讲述几个需要注意的方法。

3.2.2.1 图的缓存

每个图是由 3 RDD 组成,所以会占用更多的内存。相应图的 cache unpersist checkpoint ,更需要注意使用技巧。出于最大限度复用边的理念, GraphX 的默认接口只提供了 unpersistVertices 方法。如果要释放边,调用 g.edges.unpersist() 方法才行,这给用户带来了一定的不便,但为 GraphX 的优化提供了便利和空间。参考 GraphX Pregel 代码,对一个大图,目前最佳的实践是:

大体之意是根据GraphXGraph的不变性,对g做操作并赋回给g之后,g已不是原来的g了,而且会在下一轮迭代使用,所以必须cache。另外,必须先用prevG保留住对原来图的引用,并在新图产生后,快速将旧图彻底释放掉。否则,十几轮迭代后,会有内存泄漏问题,很快耗光作业缓存空间。

3.2.2.2 邻边聚合

mrTriplets mapReduceTriplets )是 GraphX 中最核心的一个接口。 Pregel 也基于它而来,所以对它的优化能很大程度上影响整个 GraphX 的性能。 mrTriplets 运算符的简化定义是:

它的计算过程为:map,应用于每一个Triplet上,生成一个或者多个消息,消息以Triplet关联的两个顶点中的任意一个或两个为目标顶点;reduce,应用于每一个Vertex上,将发送给每一个顶点的消息合并起来。
mrTriplets 最后返回的是一个 VertexRDD[A] ,包含每一个顶点聚合之后的消息(类型为 A ),没有接收到消息的顶点不会包含在返回的 VertexRDD 中。
在最近的版本中, GraphX 针对它进行了一些优化,对于 Pregel 以及所有上层算法工具包的性能都有重大影响。主要包括以下几点。
1.   Caching for Iterative mrTriplets & Incremental Updates for Iterative mrTriplets :在很多图分析算法中,不同点的收敛速度变化很大。在迭代后期,只有很少的点会有更新。因此,对于没有更新的点,下一次 mrTriplets 计算时 EdgeRDD 无需更新相应点值的本地缓存,大幅降低了通信开销。
2. Indexing Active Edges :没有更新的顶点在下一轮迭代时不需要向邻居重新发送消息。因此, mrTriplets 遍历边时,如果一条边的邻居点值在上一轮迭代时没有更新,则直接跳过,避免了大量无用的计算和通信。
3. Join Elimination Triplet 是由一条边和其两个邻居点组成的三元组,操作 Triplet map 函数常常只需访问其两个邻居点值中的一个。例如,在 PageRank 计算中,一个点值的更新只与其源顶点的值有关,而与其所指向的目的顶点的值无关。那么在 mrTriplets 计算中,就不需要 VertexRDD EdgeRDD 3-way join ,而只需要 2-way join
所有这些优化使 GraphX 的性能逐渐逼近 GraphLab 。虽然还有一定差距,但一体化的流水线服务和丰富的编程接口,可以弥补性能的微小差距。

3.2.2.3 进化的Pregel模式

GraphX 中的 Pregel 接口,并不严格遵循 Pregel 模式,它是一个参考 GAS 改进的 Pregel 模式。定义如下:

这种基于mrTrilets方法的Pregel模式,与标准Pregel的最大区别是,它的第2段参数体接收的是3个函数参数,而不接收messageList。它不会在单个顶点上进行消息遍历,而是将顶点的多个Ghost副本收到的消息聚合后,发送给Master副本,再使用vprog函数来更新点值。消息的接收和发送都被自动并行化处理,无需担心超级节点的问题。
常见的代码模板如下所示:

可以看到,GraphX设计这个模式的用意。它综合了PregelGAS两者的优点,即接口相对简单,又保证性能,可以应对点分割的图存储模式,胜任符合幂律分布的自然图的大型计算。另外,值得注意的是,官方的Pregel版本是最简单的一个版本。对于复杂的业务场景,根据这个版本扩展一个定制的Pregel是很常见的做法。

3.2.2.4 图算法工具包

GraphX 也提供了一套图算法工具包,方便用户对图进行分析。目前最新版本已支持 PageRank 、数三角形、最大连通图和最短路径等 6 种经典的图算法。这些算法的代码实现,目的和重点在于通用性。如果要获得最佳性能,可以参考其实现进行修改和扩展满足业务需求。另外,研读这些代码,也是理解 GraphX 编程最佳实践的好方法。

Spark GraphX中的图的函数大全

/** 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)

  // Aggregate information about adjacent triplets 
  //图的邻边信息聚合,collectNeighborIds都是效率不高的操作,优先使用aggregateMessages,这也是GraphX最重要的操作之一。
  =================================================
  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 
  //图的算法API(目前给出了三类四个API)  ========================================================================
  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]
}

结构操作

Structural Operators 
      Spark2.0版本中,仅仅有四种最基本的结构操作,未来将开发更多的结构操作。

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]
}
实例:
import org.apache.log4j.{Level, Logger}  
import org.apache.spark.{SparkContext, SparkConf}  
import org.apache.spark.graphx._  
import org.apache.spark.rdd.RDD  
  
object Test {  
  def main(args: Array[String]) {  
    //屏蔽日志  
    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)  
    //设置运行环境  
    val conf = new SparkConf().setAppName("GraphXTest").setMaster("local[*]")  
    val sc = new SparkContext(conf)  
    //设置顶点和边,注意顶点和边都是用元组定义的 Array  
    //顶点的数据类型是 VD:(String,Int)  
    val vertexArray = Array(  
      (1L, ("Alice", 28)),  
      (2L, ("Bob", 27)),  
      (3L, ("Charlie", 65)),  
      (4L, ("David", 42)),  
      (5L, ("Ed", 55)),  
      (6L, ("Fran", 50))  
    )  
    //边的数据类型 ED:Int  
    val edgeArray = Array(  
      Edge(2L, 1L, 7),  
      Edge(2L, 4L, 2),  
      Edge(3L, 2L, 4),  
      Edge(3L, 6L, 3),  
      Edge(4L, 1L, 1),  
      Edge(5L, 2L, 2),  
      Edge(5L, 3L, 8),  
      Edge(5L, 6L, 3)  
    )  
    //构造 vertexRDD 和 edgeRDD  
    val vertexRDD: RDD[(Long, (String, Int))] = sc.parallelize(vertexArray)  
    val edgeRDD: RDD[Edge[Int]] = sc.parallelize(edgeArray)  
    //构造图 Graph[VD,ED]  
    val graph: Graph[(String, Int), Int] = Graph(vertexRDD, edgeRDD)  
    println("***********************************************")  
    println("属性演示")  
    println("**********************************************************")  
    println("找出图中年龄大于 30 的顶点:")  
    graph.vertices.filter { case (id, (name, age)) => age > 30 }.collect.foreach {  
      case (id, (name, age)) => println(s"$name is $age")  
    }  
    graph.triplets.foreach(t => println(s"triplet:${t.srcId},${t.srcAttr},${t.dstId},${t.dstAttr},${t.attr}"))  
    //边操作:找出图中属性大于 5 的边  
    println("找出图中属性大于 5 的边:")  
    graph.edges.filter(e => e.attr > 5).collect.foreach(e => println(s"${e.srcId} to ${e.dstId} att ${e.attr}"))  
    println  
    //triplets 操作,((srcId, srcAttr), (dstId, dstAttr), attr)  
    println("列出边属性>5 的 tripltes:")  
    for (triplet <- graph.triplets.filter(t => t.attr > 5).collect) {  
      println(s"${triplet.srcAttr._1} likes ${triplet.dstAttr._1}")  
    }  
    println  
    //Degrees 操作  
    println("找出图中最大的出度、入度、度数:")  
  
    def max(a: (VertexId, Int), b: (VertexId, Int)): (VertexId, Int) = {  
      if (a._2 > b._2) a else b  
    }  
  
    println("max of outDegrees:" + graph.outDegrees.reduce(max) + ", max of inDegrees:" + graph.inDegrees.reduce(max) + ", max of Degrees:" +  
      graph.degrees.reduce(max))  
    println  
    println("**********************************************************")  
    println("转换操作")  
    println("**********************************************************")  
    println("顶点的转换操作,顶点 age + 10:")  
    graph.mapVertices { case (id, (name, age)) => (id, (name,  
      age + 10))  
    }.vertices.collect.foreach(v => println(s"${v._2._1} is ${v._2._2}"))  
    println  
    println("边的转换操作,边的属性*2:")  
    graph.mapEdges(e => e.attr * 2).edges.collect.foreach(e => println(s"${e.srcId} to ${e.dstId} att ${e.attr}"))  
    println  
    println("**********************************************************")  
    println("结构操作")  
    println("**********************************************************")  
    println("顶点年纪>30 的子图:")  
    val subGraph = graph.subgraph(vpred = (id, vd) => vd._2 >= 30)  
    println("子图所有顶点:")  
    subGraph.vertices.collect.foreach(v => println(s"${v._2._1} is ${v._2._2}"))  
    println  
    println("子图所有边:")  
    subGraph.edges.collect.foreach(e => println(s"${e.srcId} to ${e.dstId} att ${e.attr}"))  
    println  
    println("**********************************************************")  
    println("连接操作")  
    println("**********************************************************")  
    val inDegrees: VertexRDD[Int] = graph.inDegrees  
    case class User(name: String, age: Int, inDeg: Int, outDeg: Int)  
    //创建一个新图,顶点 VD 的数据类型为 User,并从 graph 做类型转换  
    val initialUserGraph: Graph[User, Int] = graph.mapVertices { case (id, (name, age))  
    => User(name, age, 0, 0)  
    }  
    //initialUserGraph 与 inDegrees、outDegrees(RDD)进行连接,并修改 initialUserGraph中 inDeg 值、outDeg 值  
    val userGraph = initialUserGraph.outerJoinVertices(initialUserGraph.inDegrees) {  
      case (id, u, inDegOpt) => User(u.name, u.age, inDegOpt.getOrElse(0), u.outDeg)  
    }.outerJoinVertices(initialUserGraph.outDegrees) {  
      case (id, u, outDegOpt) => User(u.name, u.age, u.inDeg, outDegOpt.getOrElse(0))  
    }  
    println("连接图的属性:")  
    userGraph.vertices.collect.foreach(v => println(s"${v._2.name} inDeg: ${v._2.inDeg} outDeg: ${v._2.outDeg}"))  
    println  
    println("出度和入读相同的人员:")  
    userGraph.vertices.filter {  
      case (id, u) => u.inDeg == u.outDeg  
    }.collect.foreach {  
      case (id, property) => println(property.name)  
    }  
    println  
    println("**********************************************************")  
    println("聚合操作")  
    println("**********************************************************")  
    println("找出年纪最大的follower:")  
    val oldestFollower: VertexRDD[(String, Int)] = userGraph.aggregateMessages[(String,  
      Int)](  
      // 将源顶点的属性发送给目标顶点,map 过程  
      et => et.sendToDst((et.srcAttr.name,et.srcAttr.age)),  
      // 得到最大follower,reduce 过程  
      (a, b) => if (a._2 > b._2) a else b  
    )  
    userGraph.vertices.leftJoin(oldestFollower) { (id, user, optOldestFollower) =>  
      optOldestFollower match {  
        case None => s"${user.name} does not have any followers."  
        case Some((name, age)) => s"${name} is the oldest follower of ${user.name}."  
      }  
    }.collect.foreach { case (id, str) => println(str) }  
    println  
    println("**********************************************************")  
    println("聚合操作")  
    println("**********************************************************")  
    println("找出距离最远的顶点,Pregel基于对象")  
    val g = Pregel(graph.mapVertices((vid, vd) => (0, vid)), (0, Long.MinValue), activeDirection = EdgeDirection.Out)(  
      (id: VertexId, vd: (Int, Long), a: (Int, Long)) => math.max(vd._1, a._1) match {  
        case vd._1 => vd  
        case a._1 => a  
      },  
      (et: EdgeTriplet[(Int, Long), Int]) => Iterator((et.dstId, (et.srcAttr._1 + 1+et.attr, et.srcAttr._2))) ,  
      (a: (Int, Long), b: (Int, Long)) => math.max(a._1, b._1) match {  
        case a._1 => a  
        case b._1 => b  
      }  
    )  
    g.vertices.foreach(m=>println(s"原顶点${m._2._2}到目标顶点${m._1},最远经过${m._2._1}步"))  
  
    // 面向对象  
    val g2 = graph.mapVertices((vid, vd) => (0, vid)).pregel((0, Long.MinValue), activeDirection = EdgeDirection.Out)(  
      (id: VertexId, vd: (Int, Long), a: (Int, Long)) => math.max(vd._1, a._1) match {  
        case vd._1 => vd  
        case a._1 => a  
      },  
      (et: EdgeTriplet[(Int, Long), Int]) => Iterator((et.dstId, (et.srcAttr._1 + 1, et.srcAttr._2))),  
      (a: (Int, Long), b: (Int, Long)) => math.max(a._1, b._1) match {  
        case a._1 => a  
        case b._1 => b  
      }  
    )  
//    g2.vertices.foreach(m=>println(s"原顶点${m._2._2}到目标顶点${m._1},最远经过${m._2._1}步"))  
    sc.stop()  
  }  
  
}  
参考资料:http://spark.apache.org/docs/latest/graphx-programming-guide.html
https://blog.csdn.net/qq_34531825/article/details/52324905

Spark入门实战系列--9.Spark图计算GraphX介绍及实例

https://blog.csdn.net/xubo245/article/details/51306975
https://blog.csdn.net/fengzaibiao/article/details/72857837




猜你喜欢

转载自blog.csdn.net/txbsw/article/details/80135821
今日推荐