??Flink提供了三種通用的基于迭代的圖計算模型的實現(Flink-Gelly:Iterative Graph Processing),分別是:Vertex-Centric, Scatter-Gather和Gather-Sum-Apply,接下來將分三篇文章分別來詳細介紹每個模型的特點和具體使用方式。
Vertex-Centric模型概述
??Vertex-Centric模型也被稱之為Pregel,核心思想為:從圖中每個頂點的角度表達計算。其計算過程以同步迭代地方式進行,每次迭代過程稱之為一個Superstep(超步,也不知道這樣翻譯對不對),每個Superstep中處于活躍狀態(active)的頂點并行地執行同樣地UDF。頂點之間通過消息進行通信,在知道目標頂點ID的前提下,一個頂點可以向任何目標頂點發送消息。Superstep之間是同步執行的,下一個Superstep的執行需要依賴前一個Superstep的執行完成,因此上一個Superstep傳遞的消息會保證在下一個Superstep開始之前傳遞完畢。消息傳遞除了能在頂點之間傳遞消息之外,還能用于判斷當前輪次的Superstep下,哪些頂點處于活躍狀態,在每個輪次的Superstep中,只有那些接收到消息的頂點才會被認為是處于活躍狀態。Vertex-Centric模型的執行過程如下圖所示:
Vertex-Centric模型使用
??Vertex-Centric模型原理十分簡單,使用起來也不難,只需要我們定義兩個部分:每個頂點需要執行的用戶自定義函數ComputeFunction
和消息在每個迭代輪次中的組合方式MessageCombiner
,其中MessageCombiner
是可選的。下面我們以一段完整的示例代碼來闡述Vertex-Centric模型的使用方法。
package com.quan.graph;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.graph.Edge;
import org.apache.flink.graph.Graph;
import org.apache.flink.graph.Vertex;
import org.apache.flink.graph.pregel.ComputeFunction;
import org.apache.flink.graph.pregel.MessageCombiner;
import org.apache.flink.graph.pregel.MessageIterator;
import java.util.LinkedList;
import java.util.List;
public class VC_SSSP {
//Set 1 as the source.
public static int srcId = 1;
public static void main(String[] args) throws Exception {
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
List<Edge<Integer, Integer>> edgesList = new LinkedList<>();
edgesList.add(new Edge<Integer, Integer>(1, 2, 12));
edgesList.add(new Edge<Integer, Integer>(1, 6, 3));
edgesList.add(new Edge<Integer, Integer>(1, 7, 14));
edgesList.add(new Edge<Integer, Integer>(2, 6, 7));
edgesList.add(new Edge<Integer, Integer>(2, 3, 10));
edgesList.add(new Edge<Integer, Integer>(3, 4, 3));
edgesList.add(new Edge<Integer, Integer>(3, 5, 5));
edgesList.add(new Edge<Integer, Integer>(3, 6, 4));
edgesList.add(new Edge<Integer, Integer>(4, 5, 4));
edgesList.add(new Edge<Integer, Integer>(5, 6, 2));
edgesList.add(new Edge<Integer, Integer>(5, 7, 8));
edgesList.add(new Edge<Integer, Integer>(6, 7, 9));
DataSet<Edge<Integer, Integer>> edges = env.fromCollection(edgesList);
// Read the input data and create a graph.
Graph<Integer, Integer, Integer> graph = Graph.fromDataSet(edges, new InitVertices(), env);
// Convert the graph to undirected.
Graph<Integer, Integer, Integer> undirected_graph = graph.getUndirected();
// Define the maximum number of iterations.
int maxIterations = 10;
// Execute the vertex-centric iteration.
Graph<Integer, Integer, Integer> result = undirected_graph.runVertexCentricIteration(
new SSSPComputeFunction(), new SSSPMessageCombiner(), maxIterations);
// Extract the vertices as the result.
DataSet<Vertex<Integer, Integer>> singleSourceShortestPaths = result.getVertices();
// Print the result.
singleSourceShortestPaths.print();
}
// User Define Function.
@SuppressWarnings("serial")
public static final class SSSPComputeFunction extends ComputeFunction<Integer, Integer, Integer, Integer> {
@Override
public void compute(Vertex<Integer, Integer> vertex, MessageIterator<Integer> messages) throws Exception {
Integer minDistance = vertex.getId().equals(srcId) ? 0 : Integer.MAX_VALUE;
for (Integer msg : messages) {
minDistance = Math.min(minDistance, msg);
}
if (minDistance < vertex.getValue()) {
setNewVertexValue(minDistance);
for (Edge<Integer, Integer> e : getEdges()) {
sendMessageTo(e.getTarget(), minDistance + e.getValue());
}
}
}
}
// Message combiner
@SuppressWarnings("serial")
public static final class SSSPMessageCombiner extends MessageCombiner<Integer, Integer> {
@Override
public void combineMessages(MessageIterator<Integer> messageIterator) throws Exception {
Integer minMessage = Integer.MAX_VALUE;
for (Integer msg : messageIterator) {
minMessage = Math.min(minMessage, msg);
}
sendCombinedMessage(minMessage);
}
}
@SuppressWarnings("serial")
private static final class InitVertices implements MapFunction<Integer, Integer> {
public Integer map(Integer id) {
return Integer.MAX_VALUE;
}
}
??這里我們用一個下圖所示的無向圖為例,初始化的時候將每個節點的值都設置成當前數據類型的最大值,其中頂點ID為1的點作為源點,該圖的單源點最短路徑(SSP)執行結果如下圖所示。在第一個Superstep期間,所有頂點都處于活躍狀態,但是根據compute
的執行過程,最終僅有源點可以向其鄰居傳播距離。在接下來的Superstep驟中,每個頂點檢查其接收到的消息并選擇出它們之間的最小距離,如果這個距離小于它的當前值,該頂點就會更新它的當前值,并為它的鄰居產生消息(當前最小值+到鄰居的邊的距離)。如果一個頂點在上一步中沒有改變它的值,那么該頂點在當前輪次的迭代中不執行任何compute
操作,也不向下一個Superstep中的任何頂點發送消息。當所有頂點的狀態不再改變或達到最大迭代次數時算法收斂。在該算法中,可以使用MessageCombiner
減少發送到目標頂點的消息數量。
Vertex-Centric模型參數配置
??可以使用VertexCentricConfiguration對象配置Vertex-Centric模型。目前可指定的參數有
Name: 可以使用setName()
方法 為vertex-centric迭代模型指定一個名稱。
Parallelism: 可以使用setParallelism()
方法為每個輪次迭代中頂點執行ComputeFunction
計算的并行度。
Solution set in unmanaged memory: 可以使用setSolutionSetUnmanagedMemory()
方法來指定結果集是否保存在托管內存中,默認情況下結果集是運行在托管內存中。
Aggregators: 可以使用registerAggregator()
方法來為每個迭代注冊聚合函數,迭代聚合器在每個超步驟中將所有聚合全局地組合一次,并使它們在下一個超步驟中可用。
Broadcast Variables: 可以使用addBroadcastSet()
方法為 ComputeFunction
添加廣播變量(Broadcast Variables)。
// configure the iteration
VertexCentricConfiguration parameters = new VertexCentricConfiguration();
// set the iteration name
parameters.setName("Gelly Iteration");
// set the parallelism
parameters.setParallelism(16);
//Defines whether the solution set is kept in managed memory
parameters.setSolutionSetUnmanagedMemory(true);
// register an aggregator
parameters.registerAggregator("sumAggregator", new LongSumAggregator());