关闭

Spark的最短路径详解

772人阅读 评论(0) 收藏 举报
分类:


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 = new SparkConf().setAppName("SimpleGraphX") 
        val sc = new SparkContext(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 else Double.PositiveInfinity)
    println(initialGraph.vertices.collect.mkString("\n")) 
    println(initialGraph.edges.collect.mkString("\n"))

    val sssp = initialGraph.pregel(Double.PositiveInfinity)(
      // Vertex Program,节点处理消息的函数,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操作者执行一系列的超级步骤(super steps),在这些步骤中,顶点从 之前的超级步骤中接收进入(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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:34480次
    • 积分:441
    • 等级:
    • 排名:千里之外
    • 原创:76篇
    • 转载:14篇
    • 译文:0篇
    • 评论:3条
    最新评论