Graphx 原始碼剖析-圖的生成
Graphx的實現程式碼並不多,這得益於Spark RDD niubility的設計。眾所周知,在分散式上做圖計算需要考慮點、邊的切割。而RDD本身是一個分散式的資料集,所以,做Graphx只需要把邊和點用RDD表示出來就可以了。本文就是從這個角度來分析Graphx的運作基本原理(本文基於Spark2.0)。
分散式圖的切割方式
在單機上圖很好表示,在分散式環境下,就涉及到一個問題:圖如何切分,以及切分之後的不同子圖如何保持彼此的聯絡構成一個完整的圖。圖的切分方式有兩種:點切分和邊切分。在Graphx中,採用點切分。
在GraphX中,Graph
類除了表示點的VertexRDD
和表示邊的EdgeRDD
外,還有一個將點的屬性和邊的屬性都包含在內的RDD[EdgeTriplet]
。
方便起見,我們先從GraphLoader
中來看看如何從一個用邊來描述圖的檔案中如何構建Graph
的。
def edgeListFile( sc: SparkContext, path: String, canonicalOrientation: Boolean = false, numEdgePartitions: Int = -1, edgeStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY, vertexStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY) : Graph[Int, Int] = { // Parse the edge data table directly into edge partitions val lines = ... ... val edges = lines.mapPartitionsWithIndex { (pid, iter) => ... ... Iterator((pid, builder.toEdgePartition)) }.persist(edgeStorageLevel).setName("GraphLoader.edgeListFile - edges (%s)".format(path)) edges.count() GraphImpl.fromEdgePartitions(edges, defaultVertexAttr = 1, edgeStorageLevel = edgeStorageLevel, vertexStorageLevel = vertexStorageLevel) } // end of edgeListFile
從上面精簡的程式碼中可以看出來,先得到lines
一個表示邊的RDD(這裡所謂的邊依舊是文字描述的),然後再經過一系列的轉換來生成Graph。
EdgeRDD
GraphImpl.fromEdgePartitions
中傳入的第一個引數edges
為EdgeRDD
的EdgePartition
。先來看看EdgePartition
究竟為何物。
class EdgePartition[ @specialized(Char, Int, Boolean, Byte, Long, Float, Double) ED: ClassTag, VD: ClassTag]( localSrcIds: Array[Int], localDstIds: Array[Int], data: Array[ED], index: GraphXPrimitiveKeyOpenHashMap[VertexId, Int], global2local: GraphXPrimitiveKeyOpenHashMap[VertexId, Int], local2global: Array[VertexId], vertexAttrs: Array[VD], activeSet: Option[VertexSet]) extends Serializable {
其中:localSrcIds
為本地邊的源點的本地編號。localDstIds
為本地邊的目的點的本地編號,與localSrcIds
一一對應成邊的兩個點。data
為邊的屬性值。index
為本地邊的源點全域性ID到localSrcIds中下標的對映。global2local
為點的全域性ID到本地ID的對映。local2global
是一個Vector,依次儲存了本地出現的點,包括跨節點的點。
透過這樣的方式做到了點切割。
有了EdgePartition
之後,再透過得到EdgeRDD
就容易了。
VertexRDD
現在看fromEdgePartitions
def fromEdgePartitions[VD: ClassTag, ED: ClassTag]( edgePartitions: RDD[(PartitionID, EdgePartition[ED, VD])], defaultVertexAttr: VD, edgeStorageLevel: StorageLevel, vertexStorageLevel: StorageLevel): GraphImpl[VD, ED] = { fromEdgeRDD(EdgeRDD.fromEdgePartitions(edgePartitions), defaultVertexAttr, edgeStorageLevel, vertexStorageLevel) }
fromEdgePartitions
中呼叫了 fromEdgeRDD
private def fromEdgeRDD[VD: ClassTag, ED: ClassTag]( edges: EdgeRDDImpl[ED, VD], defaultVertexAttr: VD, edgeStorageLevel: StorageLevel, vertexStorageLevel: StorageLevel): GraphImpl[VD, ED] = { val edgesCached = edges.withTargetStorageLevel(edgeStorageLevel).cache() val vertices = VertexRDD.fromEdges(edgesCached, edgesCached.partitions.length, defaultVertexAttr) .withTargetStorageLevel(vertexStorageLevel) fromExistingRDDs(vertices, edgesCached) }
可見,VertexRDD
是由EdgeRDD
生成的。接下來講解怎麼從EdgeRDD
生成VertexRDD
。
def fromEdges[VD: ClassTag]( edges: EdgeRDD[_], numPartitions: Int, defaultVal: VD): VertexRDD[VD] = { val routingTables = createRoutingTables(edges, new HashPartitioner(numPartitions)) val vertexPartitions = routingTables.mapPartitions({ routingTableIter => val routingTable = if (routingTableIter.hasNext) routingTableIter.next() else RoutingTablePartition.empty Iterator(ShippableVertexPartition(Iterator.empty, routingTable, defaultVal)) }, preservesPartitioning = true) new VertexRDDImpl(vertexPartitions) } private[graphx] def createRoutingTables( edges: EdgeRDD[_], vertexPartitioner: Partitioner): RDD[RoutingTablePartition] = { // Determine which vertices each edge partition needs by creating a mapping from vid to pid. val vid2pid = edges.partitionsRDD.mapPartitions(_.flatMap( Function.tupled(RoutingTablePartition.edgePartitionToMsgs))) .setName("VertexRDD.createRoutingTables - vid2pid (aggregation)") val numEdgePartitions = edges.partitions.length vid2pid.partitionBy(vertexPartitioner).mapPartitions( iter => Iterator(RoutingTablePartition.fromMsgs(numEdgePartitions, iter)), preservesPartitioning = true) }
從程式碼中可以看到先建立了一個路由表,這個路由表的本質依舊是RDD,然後透過路由表的轉得到RDD[ShippableVertexPartition]
,最後再構造出VertexRDD
。先講解一下路由表,每一條邊都有兩個點,一個源點,一個終點。在構造路由表時,源點標記位或1,目標點標記位或2,並結合邊的partitionID編碼成一個Int(高2位表示源點終點,低30位表示邊的partitionID)。再根據這個編碼的Int反解出ShippableVertexPartition
。值得注意的是,在createRoutingTables
中,反解生成ShippableVertexPartition
過程中根據點的id hash值partition了一次,這樣,相同的點都在一個分割槽了。有意思的地方來了:我以為這樣之後就會把點和這個點的映象合成一個,然而實際上並沒有。點和邊是相互關聯的,透過邊生成點,透過點能找到邊,如果合併了點和點的映象,那也找不到某些邊了。ShippableVertexPartition
依舊以邊的區分為標準,並記錄了點的屬性值,源點、終點資訊,這樣邊和邊的點,都在一個分割槽上。
最終,透過new VertexRDDImpl(vertexPartitions)
生成VertexRDD
。
Graph
def fromExistingRDDs[VD: ClassTag, ED: ClassTag]( vertices: VertexRDD[VD], edges: EdgeRDD[ED]): GraphImpl[VD, ED] = { new GraphImpl(vertices, new ReplicatedVertexView(edges.asInstanceOf[EdgeRDDImpl[ED, VD]])) }
在fromExistingRDDs
呼叫new GraphImpl(vertices, new ReplicatedVertexView(edges.asInstanceOf[EdgeRDDImpl[ED, VD]]))
來生成圖。
class ReplicatedVertexView[VD: ClassTag, ED: ClassTag]( var edges: EdgeRDDImpl[ED, VD], var hasSrcId: Boolean = false, var hasDstId: Boolean = false)
ReplicatedVertexView
是邊和圖的檢視,當點的屬性發生改變時,將改變傳輸到對應的邊。
作者:AlbertCheng
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4479/viewspace-2818581/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java集合原始碼剖析——ArrayList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】ArrayList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】Vector原始碼剖析Java原始碼
- 【Java集合原始碼剖析】HashMap原始碼剖析Java原始碼HashMap
- 【Java集合原始碼剖析】Hashtable原始碼剖析Java原始碼
- 【Java集合原始碼剖析】TreeMap原始碼剖析Java原始碼
- 【長文剖析】Spring Cloud OAuth 生成Token 原始碼解析SpringCloudOAuth原始碼
- 【Java集合原始碼剖析】LinkedList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】LinkedHashmap原始碼剖析Java原始碼HashMap
- YYImage 原始碼剖析:圖片處理技巧原始碼
- epoll–原始碼剖析原始碼
- HashMap原始碼剖析HashMap原始碼
- Alamofire 原始碼剖析原始碼
- Handler原始碼剖析原始碼
- Kafka 原始碼剖析Kafka原始碼
- TreeMap原始碼剖析原始碼
- SDWebImage原始碼剖析(-)Web原始碼
- Boost原始碼剖析--原始碼
- Spring原始碼剖析9:Spring事務原始碼剖析Spring原始碼
- 我的原始碼閱讀之路:redux原始碼剖析原始碼Redux
- 圖表庫原始碼剖析 – Chart.js 最流行的 Canvas 圖表庫原始碼JSCanvas
- 圖表庫原始碼剖析 - Chart.js 最流行的 Canvas 圖表庫原始碼JSCanvas
- Flutter 原始碼剖析(一)Flutter原始碼
- 全面剖析 Redux 原始碼Redux原始碼
- vue原始碼剖析(一)Vue原始碼
- Kafka 原始碼剖析(一)Kafka原始碼
- Thread原始碼剖析thread原始碼
- Retrofit 原始碼剖析-深入原始碼
- SDWebImage原始碼剖析(二)Web原始碼
- iOS Aspects原始碼剖析iOS原始碼
- Apache Spark原始碼剖析ApacheSpark原始碼
- 《STL原始碼剖析》-- memory原始碼
- 圖表庫原始碼剖析 - 你不知道的 Frappé Charts原始碼APP
- OC原始碼剖析物件的本質原始碼物件
- mmdetection原始碼剖析(1)--NMS原始碼
- Java LinkedList 原始碼剖析Java原始碼
- 深入剖析RocketMQ原始碼-NameServerMQ原始碼Server
- spark核心原始碼深度剖析Spark原始碼