GraphX中的分布式图计算Pregel的使用
图本质上是递归数据结构,因为顶点的属性取决于其邻居的属性,而邻居的属性又取决于其邻居的属性。因此,许多重要的图算法迭代地重新计算每个顶点的属性,直到达到一个定点条件。已经提出了一系列图并行抽象来表达这些迭代算法。 GraphX 公开了 Pregel API 的一个变体。
GraphX 中的 Pregel 操作符在高层次上是一个受图拓扑约束的批量同步并行消息传递抽象。 Pregel 算子在一系列超级步骤中执行,其中顶点从上一个超级步骤接收其入站消息的总和,计算顶点属性的新值,然后在下一个超级步骤中将消息发送到相邻顶点。与 Pregel 不同,消息是作为边三元组的函数并行计算的,并且消息计算可以访问源顶点和目标顶点属性。未收到消息的顶点在超级步骤中被跳过。当没有剩余消息时,Pregel 运算符终止迭代并返回最终图。
注意,与更标准的 Pregel 实现不同,GraphX 中的顶点只能向相邻顶点发送消息,并且消息构造是使用用户定义的消息传递函数并行完成的。这些约束允许在 GraphX 中进行额外优化。 以下是 Pregel 算子的类型签名及其实现的示例(注意:为避免由于长沿袭链导致的 stackOverflowError,pregel 通过将“spark.graphx.pregel.checkpointInterval”设置为正数,比如 10。并使用 SparkContext.setCheckpointDir(directory: String)) 设置检查点目录:
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] = { // 在每个顶点接收初始消息 var g = mapVertices( (vid, vdata) => vprog(vid, vdata, initialMsg) ).cache() // 处理消息 var messages = GraphXUtils.mapReduceTriplets(g, sendMsg, mergeMsg) var activeMessages = messages.count() // 循环直到没有消息剩余或达到 maxIterations var i = 0 while (activeMessages > 0 && i < maxIterations) { // 接收消息并更新顶点 g = g.joinVertices(messages)(vprog).cache() val oldMessages = messages // 发送新消息,跳过双方都没有收到消息的边缘。 我们必须缓存消息,以便它可以在下一行实现,允许我们取消缓存前一个迭代。 messages = GraphXUtils.mapReduceTriplets( g, sendMsg, mergeMsg, Some((oldMessages, activeDirection))).cache() activeMessages = messages.count() i += 1 } g } }
请注意,Pregel 有两个参数列表(即 graph.pregel(list1)(list2))。 第一个参数列表包含配置参数,包括初始消息、最大迭代次数和发送消息的边方向(默认情况下沿外边)。 第二个参数列表包含用于接收消息(顶点程序 vprog)、计算消息(sendMsg)和合并消息 mergeMsg 的用户定义函数。
在下面的例子中,我们可以使用 Pregel 算子来表达计算,例如单源最短路径:
import org.apache.spark.graphx.{Graph, VertexId} import org.apache.spark.graphx.util.GraphGenerators // 生成具有包含距离的边属性图 val graph: Graph[Long, Double] = GraphGenerators.logNormalGraph(sc, numVertices = 100).mapEdges(e => e.attr.toDouble) val sourceId: VertexId = 42 // The ultimate source // 初始化图,使除根以外的所有顶点的距离为无穷大 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), // 顶点的程序 triplet => { // 发送消息 if (triplet.srcAttr + triplet.attr < triplet.dstAttr) { Iterator((triplet.dstId, triplet.srcAttr + triplet.attr)) } else { Iterator.empty } }, (a, b) => math.min(a, b) // 合并消息 ) println(sssp.vertices.collect.mkString("\n"))