Spark的最短路径详解

来源:互联网 发布:js event button 编辑:程序博客网 时间:2024/06/13 13:46


import org.apache.spark.graphx._  
import org.apache.spark.SparkContext  
import org.apache.log4j.{Level, Logger}
import org.apache.spark.{SparkContext, SparkConf}
import org.apache.spark.rdd.RDD
// Import random graph generation library 
//import org.apache.spark.graphx.util.GraphGenerators 
import org.apache.spark.graphx.lib.ShortestPaths
  
 
object Pregel {  
    def main(args:Array[String]) {  
 //屏蔽日志
   Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
   Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)

       //设置运行环境
       val conf = newSparkConf().setAppName("SimpleGraphX") 
       val sc = newSparkContext(conf) 
       
//       val edgeFile:RDD[String] =sc.textFile("xrli/PregelEdge.txt")
//       val vertexFile:RDD[String] =sc.textFile("xrli/PregelVertex.txt")
//       
//       //edge
//       val edge = edgeFile.map { e =>
//       val fields = e.split(" ")
//       Edge(fields(0).toLong,fields(1).toLong,fields(2))
//       }
//      //vertex
//       val vertex = vertexFile.map{e=>
//       val fields = e.split(" ")
//       (fields(0).toLong,fields(1))
//
//       }
//
//       val graph = Graph(vertex,edge,"").persist()
//       println(graph.edges.collect.mkString("\n")) 
       
     //设置顶点和边,注意顶点和边都是用元组定义的Array
   //顶点的数据类型是VD:(String,Int)
    val vertexArray =Array(
      (1L,("Alice", 28)),            //在这里后面的属性("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)
 
    
    val sourceId: VertexId =5L // 定义源点
    
   //初始化一个新的图,该图的节点属性为graph中各节点到原点的距离  
    val initialGraph =graph.mapVertices((id, _) => if (id == sourceId) 0.0 elseDouble.PositiveInfinity)
   println(initialGraph.vertices.collect.mkString("\n")) 
   println(initialGraph.edges.collect.mkString("\n"))

    val sssp =initialGraph.pregel(Double.PositiveInfinity)(
      // VertexProgram,节点处理消息的函数,dist为原节点属性(Double),newDist为消息类(Double) 
      (id,dist, newDist) => math.min(dist, newDist),

   // Send Message,发送消息函数,返回结果为(目标节点id,消息(即最短距离)) 
     triplet => {  // 计算权重
       if (triplet.srcAttr + triplet.attr <triplet.dstAttr) {
         Iterator((triplet.dstId,triplet.srcAttr + triplet.attr))
       } else {
         Iterator.empty
       }
     },

//Merge Message,对消息进行合并的操作,类似于Hadoop中的combiner 
      (a,b)=> math.min(a,b) // 最短距离
    )
   println(sssp.vertices.collect.mkString("\n"))
 
    sc.stop()
  }
}

该程序是求所有节点到5号点的最短距离长度。

Spark的最短路径详解


建立好initialGraph后   
  println(initialGraph.vertices.collect.mkString("\n")) 打印结果如下
(1,Infinity)                                                        
(2,Infinity)
(3,Infinity)
(4,Infinity)
(5,0.0)
(6,Infinity)
println(initialGraph.edges.collect.mkString("\n")) 打印结果如下
Edge(2,1,7)                                                         
Edge(2,4,2)
Edge(3,2,4)
Edge(3,6,3)
Edge(4,1,1)
Edge(5,2,2)
Edge(5,3,8)
Edge(5,6,3)

结果输出:
(1, 5.0)
(2, 2.0)
(3, 8.0)
(4, 4.0)
(5, 0.0)
(6, 3.0)

Pregel的计算模型


某个图节点v从之前的超级步中接收到的消息队列中查找目前看到的最短路径,如果这个值比节点v当前获得的最短路径小,说明找到更短的路径,则更新节点数值为新的最短路径,之后将新值通过邻接节点传播出去,否则将当前节点转换为不活跃状态。


Pregel操作是一个约束到图拓扑的批量同步(bulk-synchronous)并行消息抽象。Pregel操作者执行一系列的超级步骤(supersteps),在这些步骤中,顶点从之前的超级步骤中接收进入(inbound)消息的总和,为顶点属性计算一个新的值,然后在以后的超级步骤中发送消息到邻居顶点。不像Pregel而更像GraphLab,消息作为一个边三元组的函数被并行计算,消息计算既访问了源顶点特征也访问了目的顶点特征。在超级步中,没有收到消息的顶点被跳过。当没有消息遗留时,Pregel操作停止迭代并返回最终的图。
注意,与更标准的Pregel实现不同的是,GraphX中的顶点仅仅能发送信息给邻居顶点,并利用用户自定义的消息函数构造消息。这些限制允许在GraphX进行额外的优化。

主要分为三个函数:
1、vertexProgram函数

2、sendMessage函数

3、messageCombiner函数


def pregel[A](initialMsg: A, maxIterations: Int, activeDirection:EdgeDirection)(

vprog: (VertexId, Double, A) => Double, 

sendMsg: EdgeTriplet[Double, Int] => Iterator[(VertexId,A)], 

mergeMsg: (A, A) => A)

: Graph[Double, Int]

pregel有两个参数列表。第一个参数列表包含配置参数初始消息、最大迭代数、发送消息的边的方向(默认是沿边方向出)。第二个参数列表包含用户自定义的函数用来接收消息(vprog)、计算消息(sendMsg)、合并消息(mergeMsg)。


这里的泛型A在本程序中是Double类型

首先initialGraph.pregel(Double.PositiveInfinity)

这里传递了一个参数:initialMsg:在第一次迭代的时候顶点收到的消息,为无穷大Double.PositiveInfinity。这里也可以写成initialGraph.pregel[Double](Double.PositiveInfinity)


然后vprog:用户定义的顶点程序运行在每一个顶点中,负责接收进来的信息,和计算新的顶点值。在第一次迭代的时候,所有的顶点程序将会被默认的initialMsg调用,在次轮迭代中,顶点程序只有接收到message才会被调用。

 (id, dist,newDist) => math.min(dist, newDist),

显而易见的是:两个消息来的时候,取它们当中路径的最小值。



initialGraph(vertexRDD,edgeRDD,属性)

这个属性是每个顶点到原点5的距离。



SendMessage函数的原理是:

 triplet => { // 计算权重
       if(triplet.srcAttr + triplet.attr < triplet.dstAttr) {
        Iterator((triplet.dstId, triplet.srcAttr +triplet.attr))
       } else{
        Iterator.empty
       }
     },


我们再回顾一下triplet的形式

Spark的最短路径详解
这里vertices形式如下(2,Infinity),(1,Infinity) 

Edges形式如下:  Edge(2L, 1L, 7),

假设我们当前处理的triplet的 A对应点2,B对应点1

所以triplet.srcAttr 就是点2到原点5的距离,

 triplet.attr 就是这里的7(A、B间的长度权重)

triplet.dstAttr就是点1到原点5的距离。



如果triplet.srcAttr + triplet.attr <triplet.dstAttr)

即如果和更小,则通过Iterator发送该信息到目标顶点的函数。

Iterator((triplet.dstId,triplet.srcAttr + triplet.attr))

该信息的结构为(目标节点id,消息(即最短距离)),否则不发送。


注意到Pregel图算法整体思路是通过迭代计算每个顶点的属性直到到达定点条件。

第一次迭代,初始消息发的是无穷大,这个反正最后都会被math.min(a,b)给弄掉的。

下一次迭代的时候,对于下面几条边对应的triple暂时都不会有新的消息出来,还都是无穷大

Edge(2,1,7)                                                         
Edge(2,4,2)
Edge(3,2,4)
Edge(3,6,3)
Edge(4,1,1)
而对于下面这几条边对应的triple则会有消息更新
Edge(5,2,2)
Edge(5,3,8)
Edge(5,6,3)

比如

Edge(5,2,2)

Spark的最短路径详解
可以看出triplet.srcAttr + triplet.attr< triplet.dstAttr)   这里是 0+2

Iterator((triplet.dstId,triplet.srcAttr + triplet.attr))  为(2,2)

这样,再到下一轮的迭代中,节点处理函数vprog:  (id,dist, newDist) => math.min(dist, newDist),

dist为原节点属性(Double)点2原来的节点属性是Inf,newDist为消息2。

所以,就会更新新的节点2属性值为2。

。。。。以此类推。


//Merge Message,对消息进行合并的操作,类似于Hadoop中的combiner 
     (a,b) =>math.min(a,b) 
//对于同一定点所有接受到的消息(这里是最短距离)进行整合,取最小的。因为每一轮迭代中跟某一节点有关的消息可能不止一个,而我们只关心最小的就行了。
0 0