# 圖解Spark Graphx實現頂點關聯鄰接頂點的collectNeighbors函數原理

## 一、場景案例

``````(1L, "Alice"),
(2L, "Bob"),
(3L, "Charlie"),
(4L, "David"),
(5L, "Eve"),
(6L, "Frank"),
(7L, "Grace"),
(8L, "Henry"),
(9L, "Ivy")
``````

``````Edge(1L, 2L, "friend"),
Edge(1L, 5L, "friend"),
Edge(2L, 3L, "friend"),
Edge(2L, 4L, "friend"),
Edge(3L, 4L, "friend"),
Edge(4L, 6L, "friend"),
Edge(5L, 7L, "friend"),
Edge(5L, 8L, "friend"),
Edge(6L, 9L, "friend"),
Edge(7L, 8L, "friend"),
Edge(8L, 9L, "friend")
``````

``````val conf = new SparkConf().setMaster("local[*]").setAppName("graphx")
val ss = SparkSession.builder().config(conf).getOrCreate()

// 創建頂點RDD
val vertices = ss.sparkContext.parallelize(Seq(
(1L, "Alice"),
(2L, "Bob"),
(3L, "Charlie"),
(4L, "David"),
(5L, "Eve"),
(6L, "Frank"),
(7L, "Grace"),
(8L, "Henry"),
(9L, "Ivy")
))

// 創建邊RDD
val edges = ss.sparkContext.parallelize(Seq(
Edge(1L, 2L, "friend"),
Edge(1L, 5L, "friend"),
Edge(2L, 3L, "friend"),
Edge(2L, 4L, "friend"),
Edge(3L, 4L, "friend"),
Edge(4L, 6L, "friend"),
Edge(5L, 7L, "friend"),
Edge(5L, 8L, "friend"),
Edge(6L, 9L, "friend"),
Edge(7L, 8L, "friend"),
Edge(8L, 9L, "friend")
))

val graph = Graph(vertices, edges, null)
``````

``````val neighborVertexs = graph.mapVertices{
case (id,(label)) => (label)
}.collectNeighbors(EdgeDirection.Either)
``````

``````(5,[Lscala.Tuple2;@bb793d7)
(8,[Lscala.Tuple2;@6d5786e6)
(1,[Lscala.Tuple2;@398cb9ea)
(9,[Lscala.Tuple2;@61c4eeb2)
(2,[Lscala.Tuple2;@d7d0256)
(6,[Lscala.Tuple2;@538f0156)
(7,[Lscala.Tuple2;@77a17e3d)
(3,[Lscala.Tuple2;@1be2a4fb)
(4,[Lscala.Tuple2;@1e0153f9)
``````

``````neighborVertexs.coalesce(1).foreach(x => {
print("頂點：" + x._1 + "關聯的鄰居頂點集合->{" )
var str = "";
x._2.foreach(y => {
str += y + ","})
print(str.substring(0, str.length - 1 ) +"}")
println()
})
``````

``````頂點：8關聯的鄰居頂點集合->{(5,Eve),(7,Grace),(9,Ivy)}

``````

## 二、函數代碼原理解析

``````def collectNeighbors(edgeDirection: EdgeDirection): VertexRDD[Array[(VertexId, VD)]] = {
val nbrs = edgeDirection match {
//聚合本頂點出度指向的鄰居頂點和入度指向本頂點的鄰居頂點
case EdgeDirection.Either =>
graph.aggregateMessages[Array[(VertexId, VD)]](
ctx => {
ctx.sendToSrc(Array((ctx.dstId, ctx.dstAttr)))
ctx.sendToDst(Array((ctx.srcId, ctx.srcAttr)))
},
(a, b) => a ++ b, TripletFields.All)
//聚合本頂點出度指向的鄰居頂點
case EdgeDirection.In =>
graph.aggregateMessages[Array[(VertexId, VD)]](
ctx => ctx.sendToDst(Array((ctx.srcId, ctx.srcAttr))),
(a, b) => a ++ b, TripletFields.Src)
//聚合入度指向本頂點的鄰居頂點
case EdgeDirection.Out =>
graph.aggregateMessages[Array[(VertexId, VD)]](
ctx => ctx.sendToSrc(Array((ctx.dstId, ctx.dstAttr))),
(a, b) => a ++ b, TripletFields.Dst)
case EdgeDirection.Both =>
throw new SparkException("collectEdges does not support EdgeDirection.Both. Use" +
"EdgeDirection.Either instead.")
}
graph.vertices.leftJoin(nbrs) { (vid, vdata, nbrsOpt) =>
nbrsOpt.getOrElse(Array.empty[(VertexId, VD)])
}
} // end of collectNeighbor
``````

Edge(2L, 1L),
Edge(2L, 4L),
Edge(3L, 2L),
Edge(2L, 5L),

• EdgeDirection.Either表示本頂點的出度鄰居和入度鄰居。若本頂點爲2，那麼它得到鄰居頂點包括（1，4，3，5），該參數表示只要與頂點2一度邊關聯的，都會聚集成鄰居頂點。

• EdgeDirection.In表示指向本頂點的鄰居，即本頂點的入度鄰居。若本頂點爲2，圖裏鄰居頂點只有3是指向2的，那麼頂點2得到鄰居頂點包括（3）。

• EdgeDirection.Out表示本頂點的出度指向的鄰居頂點。若本頂點爲2，圖裏從頂點2指向鄰居頂點的，將得到（1，4，5）。

`````` graph.aggregateMessages[Array[(VertexId, VD)]](
ctx => {
ctx.sendToSrc(Array((ctx.dstId, ctx.dstAttr)))
ctx.sendToDst(Array((ctx.srcId, ctx.srcAttr)))
},
(a, b) => a ++ b, TripletFields.All)
``````

1. 源頂點（Source Vertex）：圖中的一條邊的起始點或源節點。
2. 目標頂點（Destination Vertex）：圖中的一條邊的結束點或目標節點。
3. 邊屬性（Edge Attribute）：連接源頂點和目標頂點之間的邊上的屬性值。

• `ctx.srcId`：獲取源頂點的ID。

• `ctx.srcAttr`：獲取源頂點的屬性。

• `ctx.dstId`：獲取目標頂點的ID。

• `ctx.dstAttr`：獲取目標頂點的屬性。

ctx作爲一個知道源頂點、目標頂點的三元組對象，就像一個郵差一樣，負責給兩邊頂點發送消息。

1、ctx.sendToSrc(Array((ctx.dstId, ctx.dstAttr)))函數，這裏頂點A是作爲目標頂點，鄰居節點B是源頂點，ctx對象就會將目標頂點B的頂點ID和屬性組成的元組(ctx.dstId, ctx.dstAttr)當作消息傳給源頂點A，A會將收到的消息保存下來，這樣就知道EdgeDirection.Either無向邊情況下，它有一個鄰居B了。

2、 ctx.sendToDst(Array((ctx.srcId, ctx.srcAttr)))函數，這時A成爲了源頂點，C成爲了目標頂點，ctx對象就會將源頂點A的頂點ID和屬性組成的元組(ctx.dstId, ctx.dstAttr)當作消息傳給源頂點B。B會將收到的消息以數組格式Array((ctx.dstId, ctx.dstAttr))保存下來，這樣B以後就知道EdgeDirection.Either無向邊情況下，它有一個鄰居A了。

TripletFields.All表示本頂點將聚合包括源頂點以及目標頂點發送頂點消息。

TripletFields.Src表示本頂點只聚合源頂點發送過來的頂點消息。

TripletFields.Dst表示本頂點只聚合目標頂點發送過來的頂點消息。

EdgeDirection.Either參數對應的是TripletFields.All，表示需要將本頂點接收到的所有源頂點以及目標頂點發送的頂點消息進行聚合。

Array((B，屬性))

Array((C，屬性))

Array((D，屬性))

......

``````val nbrs = edgeDirection match {
case EdgeDirection.Either =>
graph.aggregateMessages[Array[(VertexId, VD)]](
ctx => {
ctx.sendToSrc(Array((ctx.dstId, ctx.dstAttr)))
ctx.sendToDst(Array((ctx.srcId, ctx.srcAttr)))
},
(a, b) => a ++ b, TripletFields.All)
......
}
``````

``````graph.vertices.leftJoin(nbrs) { (vid, vdata, nbrsOpt) =>
nbrsOpt.getOrElse(Array.empty[(VertexId, VD)])
}
``````

(5,[Lscala.Tuple2;@bb793d7) 頂點5展開鄰居頂點=> 頂點：5關聯的鄰居頂點集合->{(1,Alice),(7,Grace),(8,Henry)}
(8,[Lscala.Tuple2;@6d5786e6) 頂點8展開鄰居頂點=> 頂點：8關聯的鄰居頂點集合->{(5,Eve),(7,Grace),(9,Ivy)}
(1,[Lscala.Tuple2;@398cb9ea) 頂點1展開鄰居頂點=> 頂點：1關聯的鄰居頂點集合->{(2,Bob),(5,Eve)}
(9,[Lscala.Tuple2;@61c4eeb2) 頂點9展開鄰居頂點=> 頂點：9關聯的鄰居頂點集合->{(6,Frank),(8,Henry)}
(2,[Lscala.Tuple2;@d7d0256) 頂點2展開鄰居頂點=> 頂點：2關聯的鄰居頂點集合->{(1,Alice),(3,Charlie),(4,David)}
(6,[Lscala.Tuple2;@538f0156) 頂點6展開鄰居頂點=> 頂點：6關聯的鄰居頂點集合->{(4,David),(9,Ivy)}
(7,[Lscala.Tuple2;@77a17e3d) 頂點7展開鄰居頂點=> 頂點：7關聯的鄰居頂點集合->{(5,Eve),(8,Henry)}
(3,[Lscala.Tuple2;@1be2a4fb) 頂點3展開鄰居頂點=> 頂點：3關聯的鄰居頂點集合->{(2,Bob),(4,David)}
(4,[Lscala.Tuple2;@1e0153f9) 頂點4展開鄰居頂點=> 頂點：4關聯的鄰居頂點集合->{(2,Bob),(3,Charlie),(6,Frank)}