- Pregel API
Pregel API
图本身是递归数据结构,顶点的属性依赖于它们邻居的属性,这些邻居的属性又依赖于自己邻居的属性。所以许多重要的图算法都是迭代的重新计算每个顶点的属性,直到满足某个确定的条件。
一系列的graph-parallel抽象已经被提出来用来表达这些迭代算法。GraphX公开了一个类似Pregel的操作,它是广泛使用的Pregel和GraphLab抽象的一个融合。
在GraphX中,更高级的Pregel操作是一个约束到图拓扑的批量同步(bulk-synchronous)并行消息抽象。Pregel操作者执行一系列的超级步骤(super steps),在这些步骤中,顶点从
之前的超级步骤中接收进入(inbound)消息的总和,为顶点属性计算一个新的值,然后在以后的超级步骤中发送消息到邻居顶点。不像Pregel而更像GraphLab,消息作为一个边三元组的函数被并行
计算,消息计算既访问了源顶点特征也访问了目的顶点特征。在超级步中,没有收到消息的顶点被跳过。当没有消息遗留时,Pregel操作停止迭代并返回最终的图。
注意,与更标准的Pregel实现不同的是,GraphX中的顶点仅仅能发送信息给邻居顶点,并利用用户自定义的消息函数构造消息。这些限制允许在GraphX进行额外的优化。
一下是@pregel[A](A,Int,EdgeDirection"> Pregel操作((VertexId,VD,A)⇒VD,(EdgeTriplet[VD,ED])⇒Iterator[(VertexId,A)],(A,A)⇒A)(ClassTag[A]):Graph[VD,ED])的类型签名以及实现草图(注意,访问graph.cache已经被删除)
class GraphOps[VD, ED] {def pregel[A](initialMsg: A,maxIter: Int = Int.MaxValue,activeDir: EdgeDirection = EdgeDirection.Out)(vprog: (VertexId, VD, A) => VD,sendMsg: EdgeTriplet[VD, ED] => Iterator[(VertexId, A)],mergeMsg: (A, A) => A): Graph[VD, ED] = {// Receive the initial message at each vertexvar g = mapVertices( (vid, vdata) => vprog(vid, vdata, initialMsg) ).cache()// compute the messagesvar messages = g.mapReduceTriplets(sendMsg, mergeMsg)var activeMessages = messages.count()// Loop until no messages remain or maxIterations is achievedvar i = 0while (activeMessages > 0 && i < maxIterations) {// Receive the messages: -----------------------------------------------------------------------// Run the vertex program on all vertices that receive messagesval newVerts = g.vertices.innerJoin(messages)(vprog).cache()// Merge the new vertex values back into the graphg = g.outerJoinVertices(newVerts) { (vid, old, newOpt) => newOpt.getOrElse(old) }.cache()// Send Messages: ------------------------------------------------------------------------------// Vertices that didn't receive a message above don't appear in newVerts and therefore don't// get to send messages. More precisely the map phase of mapReduceTriplets is only invoked// on edges in the activeDir of vertices in newVertsmessages = g.mapReduceTriplets(sendMsg, mergeMsg, Some((newVerts, activeDir))).cache()activeMessages = messages.count()i += 1}g}}
注意,pregel有两个参数列表(graph.pregel(list1)(list2))。第一个参数列表包含配置参数初始消息、最大迭代数、发送消息的边的方向(默认是沿边方向出)。第二个参数列表包含用户
自定义的函数用来接收消息(vprog)、计算消息(sendMsg)、合并消息(mergeMsg)。
我们可以用Pregel操作表达计算单源最短路径( single source shortest path)。
import org.apache.spark.graphx._// Import random graph generation libraryimport org.apache.spark.graphx.util.GraphGenerators// A graph with edge attributes containing distancesval graph: Graph[Int, Double] =GraphGenerators.logNormalGraph(sc, numVertices = 100).mapEdges(e => e.attr.toDouble)val sourceId: VertexId = 42 // The ultimate source// Initialize the graph such that all vertices except the root have distance infinity.val initialGraph = graph.mapVertices((id, _) => if (id == sourceId) 0.0 else Double.PositiveInfinity)val sssp = initialGraph.pregel(Double.PositiveInfinity)((id, dist, newDist) => math.min(dist, newDist), // Vertex Programtriplet => { // Send Messageif (triplet.srcAttr + triplet.attr < triplet.dstAttr) {Iterator((triplet.dstId, triplet.srcAttr + triplet.attr))} else {Iterator.empty}},(a,b) => math.min(a,b) // Merge Message)println(sssp.vertices.collect.mkString("\n"))
