spark笔记-GraphX图计算

1. Spark GraphX

  1. Spark GraphX是Spark的一个模块,主要用于进行以图为核心的计算还有分布式图的计算。
  2. GraphX他的底层计算也是RDD计算,他和RDD共用一种存储形态,在展示形态上可以以数据集来表示,也是以图的形式来表示。

1.1 图的应用场景

图数据很好的表达了数据之间的相关性,现实中很多问题都可以用此表示,以下的一些问题就可以以图的数据结构模型来解决问题。

  • PangeRank让链接来投票
  • 基于GraphX的社区发现算法
  • 社交网络分析
  • 基于三角形计数关系的衡量
  • 基于随机游走的用户属性传播
  • 推荐应用

2.Spark GraphX的抽象

  1. 顶点。
    • 顶点的表示用 RDD[(VertexId, VD)]来表示,(VertexId, VD)这个元组用来具体表示一个顶点,VertexID表示顶点的ID,是Long类型的别名,VD是顶点的属性,是一个类型参数,可以是任何类型。
  2. 边。
    • 边的表示用RDD[Edge[ED]]来表示,Edge用来具体表示一个边,Edge里面包含一个ED类型参数来设定的属性,一个源顶点的ID和一个目标顶点的ID。
  3. 三元组。
    • 三元组结构用RDD[EdgeTriplet[VD, ED]]来表示,EdgeTriplet[VD, ED]来表示一个三元组,三元组包含了一个边、边的属性、源顶点ID、源顶点属性、目标顶点ID、目标顶点属性。VD和ED是类型参数,VD表示顶点的属性,ED表示边的属性。
  4. 图。
    • 图在Spark中用Graph来表示,可以通过顶点和边来构建。

3.Spark GraphX图的构建

通常,在图计算中,基本的数据结构表达就是:G = (V,E,D),其中: V :vertex (顶点或者节点),E :edge (边),D:data (权重)

  1. 对于顶点的构建:

    1. 对于RDD[(VertexId, VD)]这种版本:
      val users: RDD[(VertexId, (String, String))] = sc.parallelize(Array((3L, (“rxin”, “student”)), (7L, (“jgonzal”, “postdoc”)),(5L, (“franklin”, “prof”)), (2L, (“istoica”, “prof”))))
    2. 对于VertexRDD[VD]这种版本:是上面版本的优化版本。
      val users1:VertexRDD[(String, String)] = VertexRDD(String, String)
  2. 对于边的构建:

    1. 对于RDD[Edge[ED]]这种版本:
      val relationships: RDD[Edge[String]] = sc.parallelize(Array(Edge(3L, 7L, “collab”), Edge(5L, 3L, “advisor”),Edge(2L, 5L, “colleague”), Edge(5L, 7L, “pi”)))
    2. 对于EdgeRDD[ED]这种版本:也是边的优化版本。
      val relationships1:EdgeRDD[String] = EdgeRDD.fromEdges(relationships)
  3. 对于Graph图的构建:

    1. 通过Graph类的apply方法进行构建如下:Graph[VD: ClassTag, ED: ClassTag]
      val graph = Graph(users,relationships)
      apply方法:
def apply[VD: ClassTag, ED: ClassTag](
    vertices: RDD[(VertexId, VD)],
    edges: RDD[Edge[ED]],
    defaultVertexAttr: VD = null.asInstanceOf[VD],
    edgeStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY,
    vertexStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, ED]
2. 通过Graph类提供fromEdges方法来构建,对于顶点的属性是使用提供的默认属性。

   val graph2 = Graph.fromEdges(relationships,defaultUser)
def fromEdges[VD: ClassTag, ED: ClassTag](
    edges: RDD[Edge[ED]],
    defaultValue: VD,
    edgeStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY,
    vertexStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, ED]
3、通过Graph类提供的fromEdgeTuples方法类构建,对于顶点的属性是使用提供的默认属性,对于边的属性是相同边的数量。

  val relationships: RDD[(VertexId,VertexId)] = sc.parallelize(Array((3L, 7L),(5L, 3L),(2L, 5L), (5L, 7L)))
  val graph3 = Graph.fromEdgeTuples[(String,String)](relationships,defaultUser)
def fromEdgeTuples[VD: ClassTag](
    rawEdges: RDD[(VertexId, VertexId)],
    defaultValue: VD,
    uniqueEdges: Option[PartitionStrategy] = None,
    edgeStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY,
    vertexStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, Int]

4. Spark GraphX图的计算模式

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

  • 通过case访问
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)
    }

4.1 Spark GraphX 图的转换操作

  1. graph.numEdges 返回当前图的边的数量
  2. graph.numVertices 返回当前图的顶点的数量
  3. graph.inDegrees 返回当前图每个顶点入度的数量,返回类型为VertexRDD[Int]
  4. graph.outDegrees 返回当前图每个顶点出度的数量,返回的类型为VertexRDD[Int]
  5. graph.degrees 返回当前图每个顶点入度和出度的和,返回的类型为VertexRDD[Int]

4.2 转换操作

  1. mapVertices

对当前图每一个顶点应用提供的map函数来修改顶点的属性,返回一个新的图。
2. mapEdges

对当前图每一条边应用提供的map函数来修改边的属性,返回一个新图。
3. mapTriplets

对当前图每一个三元组应用提供的map函数来修改边的属性,返回一个新图。

4.2 结构操作

  1. reverse
    使图的边的方向翻转,返回一个新的图。
  2. subgraph
    利用顶点和边的判断式,返回的图仅仅包含满足顶点判断式的顶点、满足边判断式的边以及满足顶点判断式的Tripe。
  3. mask
    构造一个子图,包含输入图中包含的顶点和边。
  4. groupEdge
    合并多重图中的并行边。

4.3 关联操作

  1. joinVertices
    将外部数据加入到图中,操作join输入RDD和顶点。当图中的某个顶点id在另一个图中不存在时,会保留原图中该顶点属性的原值。
  2. outerJoinVertices
    将外部数据加入到图中,操作join输入RDD和顶点。当图中的某个顶点id在另一个图中不存在时,会使用None值作为该顶点的属性值。

4.4 聚合操作

  1. aggregateMessages
    主要功能是向邻边发消息,合并邻边收到的消息,返回messageRDD。
  • 计算比用户年龄大的追随者(即followers)的平均年龄。
import org.apache.spark.graphx.util.GraphGenerators
val graph: Graph[Double, Int] = GraphGenerators.lognormalGraph(sc, numVertices = 100).mapvertices((id, _) => id.toDouble)

val olderFollowers: VertexRDD[(Int, Double)] = graph.aggregateMessages[(Int, Double)](
    triplet => {
        if (triplet.srcAttr > triplet.dstAttr){
            triplet.sendToDst(1, triplet.srcAttr)
        }
    },
    (a, b) => (a._1 + b._1, a._2 + b._2)
)

val avgageofolderFollowers: VertexRDD[Double] = olderFollowers.mapvalues((id, value) => value match{
    case (count, totalAge) => totalAge / count})

avgageofolderFollowers.collect.foreach(printIn(_))
  1. collectNeighborlds
    收集每个顶点的邻居顶点的顶点id和顶点属性。
  2. collectNeighbors
    收集每个顶点的邻居顶点的顶点id。

5.GraphX实例

下图中有6个人,每个人有名字和年龄,这些人根据社会关系形成8条边,每条边有其属性。在以下例子演示中将构建顶点、边和图,打印图的属性、转换操作、结构操作、连接操作、聚合操作,并结合实际要求进行演示。

import org.apache.log4j.{level, Logger}
import org.apache.spark.{Sparkcontext, Sparkconf}
import org.apache.spark.graphx._
import org.apache.spark.rdd.rdd

object GraphXExample {
    def main(args: Array[String]) {
//屏蔽日志
Logger.getLogge("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
//设置运行环境
val conf = new Sparkconf().setAppName("SimpleGraphX").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.paral1elize(vertexArray)
val edgeRDD: RDD[Edge[Int]] = sc.paral1elize(edgeArray)

//构造图 Graph[VD, ED]
val graph: Graph[(String, Int), Int] = Graph(vertexRDD, edgeRDD)

printIn("属性演示")
printIn("**********************")
printIn("找出图中年龄大于30的顶点:")
graph.vertices.filter{
    case(id, (name, age)) => age >30}.collect.foreach{
        case(id, (name, age)) => printIn(s"$name is $age")
    }
printIn("找出图中属性大于5的边:")
graph.edges.filter(e => e.attr > 5).collect.foreach(e =>printIn(s"${e.srcId} to ${e.dstId} att ${e.attr}"))
    printIn

//triplets操作,((srcid, srcAttr), (dstid, dstAttr),attr)
卩门'的1门("列出边属性>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

//**********转换操作***********
printIn("顶点的转换操作,顶点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
pHntln("边的转换操作,边的属性*2:")
graph.mapEdges(e=>e.attr*2).edges.collect.foreach(e => printIn(s"${e.srcid}
to ${e.dstid} att ${e.attr}"))
println

//******结构操作********
printIn("结构操作")

pHntln("顶点年纪>30的子图:")
val subGraph = graph.subgraph(vpred = (id, vd) => vd._2 >= 30)
println("子图所有顶点:")
subGraph.vertices.col1ect.foreach(v => println(s"${v._2._1} is
${v__2._2}"))
printIn
pHntln("子图所有边:")
subGraph.edges.collect.foreach( e => printIn(s"${e.srcId} to ${e.dstId} att ${e.attr}"))
println

//**********连接操作***************
printIn("连接操作")
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)}
    
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.col1ect.foreach(v => println(s"${v._2.name} inDeg:
${v._2.inDeg} outDeg: ${v._2.outDeg}"))
printIn
println("出度和入读相同的人员:")
userGraph.vertices.filter {
case (id, u) => u.inDeg == u.outDeg
}.collect.foreach {
case (id, property) => println(property.name)
printIn

//**********聚合操作**********
println("找出年纪最大的追求者:")
val oldestFollower: vertexRDD[(String, Int)]=
userGraph.mapReduceTriplets[(String, Int)](
//将源顶点的属性发送给目标顶点,map过程
edge => iterator((edge.dstid, (edge.srcAttr.name, edge.srcAttr.age))),
//得到最大追求者,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("找出5到各顶点的最短:")
val sourceId: vertexId = 5L // 定义源点
val initialGraph = graph.mapVertices((id, _) => if (id == sourceld) 0.0
else Double.Positivelnfinity)
val sssp = initialGraph.pregel(Double.Positivelnfinity)(
(id, dist, newDist) => math.min(dist, newDist),
triplet => { // 计算权重
if (triplet.srcAttr + triplet.attr < triplet.dstAttr) {
Iterator((triplet.dstld, triplet.srcAttr + triplet.attr))
} else {
Iterator.empty
}
},
(a,b) => math, min (a, b) //最短距离
)
println(sssp.vertices.col1ect.mkstring("/n"))

sc.stop()
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_42526352/article/details/104921198