主頁 >  其他 > Flink 內核原理與實作-作業提交

Flink 內核原理與實作-作業提交

2021-09-06 07:39:26 其他

可以關注下公眾號,專注資料開發、資料架構之路,熱衷于分享技術干貨,

一、提交流程

Flink作業在開發完畢之后,需要提交到Flink集群執行,ClientFronted是入口,觸發用戶開發的Flink應用Jar檔案中的main方法,然后交給PipelineExecutor(流水線執行器,在FlinkClient 升成JobGraph之后,將作業提交給集群的重要環節,)#execue方法,最侄訓選擇一個觸發一個具體的PiplineExecutor執行,

不同模式的適用場景
運行模式 適用場景
Session模式

共享Dispatcher 和ResourceManager

按需申請資源,作業共享集群資源

適合執行時間段,頻繁執行的短任務

Per-Job模式

獨享Dispatcher 和 ResourceManager

按需申請資源,作業獨享集群資源

長周期執行的任務,集群例外影響范圍小

提交模式又可分為:

  • Detached:Flink Client創建完集群之后,可以退出命令列視窗,集群獨立運行,
  • Attached:不能關閉命令列視窗,需要與集群之間維持連接,

1.1 Yarn Session提交流程

啟動集群

  • 使用bin/yarn-session.sh提交會話模式的作業,

如果提交到已經存在的集群,則獲取Yarn集群資訊、應用ID,并準備提交作業,如果啟動新的Yarn Session集群,則進入步驟(2)

  • Yarn啟動新Flink集群

1)如果沒有集群,則創建一個新的Session模式的集群,首先將應用配置(flink-conf.yaml、logback.xml、log4j.properties)和相關檔案(Flink Jar、配置類檔案、用戶Jar檔案、JobGraph物件等)上傳至分布式存盤(如HDFS)的應用暫存目錄,

2)通過Yarn Client 向Yarn 提交Flink創建集群的申請,Yarn分配資源,在申請的Yarn Container中初始化并啟動FlinkJobManager行程,在JobManager行程中運行YarnSessionClusterEntrypoint作為集群啟動入口(不同的集群部署模式有不同的ClusterEntrypoint實作),初始化Dispatcher、ResourceManager,啟動相關的RPC服務,等待Client通過Rest介面提交作業,

作業提交

Yarn 集群準備好后,開始作業提交,

1)Flink Client通過Rest向Dispatcher提交JobGraph,

2)Dispatcher是Rest介面,不負責實際的調度、執行方面的作業,當收到JobGraph后,為作業創建一個JobMaster,將作業交給JobManager(負責作業調度、管理作業和Task的生命周期),構建ExecutionGraph(JobGraph的并行化版本,調度層最核心的資料結構),

這兩個步驟結束后,作業進入調度執行階段,

作業調度執行

1)JobMaster向YarnResourceManager申請資源,開始調度ExecutionGraph執行,向YarnResourceManager申請資源;初次提交作業集群中尚沒有TaskManager,此時資源不足,開始申請資源,

2)YarnResourceManager收到JobManager的資源請求,如果當前有空閑Slot則將Slot分配給JobMaster.,否則YarnResourceManager將向YarnMaster請求創建TaskManager,

3)YarnResourceManager將資源請求加入到等待請求佇列,并通過心跳向Yarn RM 申請新的Container資源來啟動TaskManager行程,Yarn分配新的Container給TaskManager,

4)YarnResourceManager啟動,然后從HDFS加載Jar檔案等所需要的的相關資源,在容器中啟動TaskManager,

5)TaskManager啟動之后,向ResourceManager注冊,并把自己的Slot資源情況匯報給ResouceManager,

6)ResourceManager從等待佇列中取出Slot請求,向TaskManager確認資源可用情況,并告知TaskManager將Slot分配給哪個JobMaster,

7)TaskManager向JobMaster提供Slot,JobMaster調度Task到TaskManager的此Slot上執行,

1.2 Yarn Per-Job提交流程

啟動集群

  • 使用./flink run -m yarn-cluster提交Per-Job模式的作業,
  • Yarn啟動Flink集群,該模式下Flink集群的啟動入口是YarnJobClusterEntryPoint,其他與YarnSession模式下集群的啟動類似,

作業提交

該步驟與Seesion模式下的不同,Client并不會通過Rest向Dispatcher提交JobGraph,由Dispatcher從本地檔案系統獲取JObGraph,其后的不好走與Session模式的一樣

作業調度執行

與Yarn Session模式下一致,

1.3 K8s Session提交流程

啟動集群

  • Flink客戶端首先連接Kubernetes API Server,提交Flink集群的資源描述檔案,包括flink-configuration-configmap.yaml、jobmanager-service.yaml、jobmanager-deployment.yaml和taskmanager-deployment.yaml等,
  • Kubernets Master會根據這些資源描述檔案去創建對應的Kubernetes物體,以JobManager部署為例,Kubernetes集群中的某個節點收到請求后,Kubelet行程會從中央倉庫下載Flink鏡像,準備和掛載卷,然后執行啟動命令,Pod啟動后Flink Master(JobManager)行程隨之啟動,初始化Dispacher和KubernetesResourceManager,并通過K8s服務對外暴露FlinkMaster的埠,K8s服務類似于路由服務,

兩個步驟完成之后,Session模式的集群就創建成功,集群可以接收作業提交請求,但是此時還沒有JobManager、TaskManager,當作業需要執行時,才會按需創建,

作業提交

  • Client用戶可以通過Flink命令列(即Flink Client)向這個會話模式的集群提交任務,此時JobGraph會在FlinkClient端生成,然后和用戶Jar包一起通過RestClient上傳,
  • 作業提交成功,Dispatcher會為每個作業啟動一個JobMaster,將JobGraph交給JobMaster調度執行,

兩個步驟完成之后,作業進入調度執行階段,

作業調度執行

K8s Session模式集群下,ResourceManager向k8sMaster申請和釋放TaskManager,除此之外,作業的調度與執行和Yarn模式是一樣的,

1)JobMaster向KubernetesResourceManager請求Slot,

2)KubernetesResourceManager從kubernetes集群分配TaskManager,每個TaskManager都是具有唯一標識的Pod,KubernetesResourceManager會為TaskManager生成一份新的組態檔,里面有Flink Master的service name 作為地址,這樣在FLInkMaster failover之后,TaskManager仍然可以重新連上,

3)Kubernetes集群分配一個新的Pod后,在上面啟動TaskManager,

4)TaskManager啟動后注冊到SlotManager,

5)SlotManager向TaskManager請求Slot.

6)TaskManager 提供Slot給JobMaster,然后任務就會被分配到這個Slot上運行,

二、Graph總覽

  • 流計算應用的Graph轉換:StreamGraph-->JobGraph-->ExecutionGraph-->物理執行圖(啟動計算任務)
  • 批處理應用的Graph轉換:OptimizedPlan-->JobGraph
  • Table & SQL API的Graph轉換:Blink Table Planner /Flink Table Planner,

2.1 流圖

使用DataStreamAPI 開發的應用程式,首先被轉換為Transformation,然后被映射為StreamGraph,

2.1.1 SteramGraph核心物件

  • StreamNode

StreamNode是StremGraph中的節點 ,從Transformation轉換而來,可以簡單理解為一個StreamNode表示一個算子,從邏輯上來說,SteramNode在StreamGraph中存在物體和虛擬的StreamNode,StremNode可以有多個輸入,也可以有多個輸出,

物體的StreamNode會最終變成物理算子,虛擬的StreamNode會附著在StreamEdge上,

  • StreamEdge

StreamEdge是StreamGraph中的邊,用來連接兩個StreamNode,一個StreamNode可以有多個出邊、入邊,StreamEdge中包含了旁路輸出、磁區器、欄位篩選輸出等資訊,

2.1.2 StreamGraph生成程序

StreamGraph在FlinkClient中生成,由FlinkClient在提交的時候觸發Flink應用的main方法,用戶撰寫的業務邏輯組裝成Transformation流水線,在最后呼叫StreamExecutionEnvironment.execute() 的時候開始觸發StreamGraph構建,

StreamGraph實際上是在StreamGraphGenerator中生成的,從SinkTransformation(輸出) 向前追溯到SourceTransformation,在遍歷程序中一邊遍歷一邊構建StreamGraph,

在遍歷Transformation的程序中,會對不同型別的Transformation分別進行轉換,對于物理Transformation則轉換為StreamNode物體,對于虛擬Transformation則作為虛擬StreamNode,

針對于某一種型別的Transformation,會呼叫其相應的transformxxx()函式進行轉換,transfromxxx()首先轉換上游Transformation進行遞回轉換,確保上游的都已經完成了轉換,然后通過addOperator()方法構造出StreamNode,通過addEdge()方法與上游的transform進行連接,構造出StreamEdge,

在添加StreamEdge的程序中,如果ShuffleMode為null,則使用ShuffleMode PIPELINED模式,在流計算中,只有PIPLINED模式才會在批處理中設計其他模式,構建StreamEdge的時候,在轉換Transformation程序中生成的 虛擬StreamNode會將虛擬StreamNode的資訊附著在StreamEdge上

2.1.3 虛擬Transformation 的轉換

虛擬的Transformation生成的時候不會轉換為SteramNode,而是添加為虛擬節點,

	private void addEdgeInternal(Integer upStreamVertexID,
			Integer downStreamVertexID,
			int typeNumber,
			StreamPartitioner<?> partitioner,
			List<String> outputNames,
			OutputTag outputTag,
			ShuffleMode shuffleMode) {
		//當上游是sideoutput時,遞回呼叫,并傳入sideoutput資訊
		if (virtualSideOutputNodes.containsKey(upStreamVertexID)) {
			int virtualId = upStreamVertexID;
			upStreamVertexID = virtualSideOutputNodes.get(virtualId).f0;
			if (outputTag == null) {
				outputTag = virtualSideOutputNodes.get(virtualId).f1;
			}
			addEdgeInternal(upStreamVertexID, downStreamVertexID, typeNumber, partitioner, null, outputTag, shuffleMode);
			
		}
		//當上游是select時,遞回呼叫,并傳入select資訊
		else if (virtualSelectNodes.containsKey(upStreamVertexID)) {
			int virtualId = upStreamVertexID;
			upStreamVertexID = virtualSelectNodes.get(virtualId).f0;
			if (outputNames.isEmpty()) {
				// selections that happen downstream override earlier selections
				outputNames = virtualSelectNodes.get(virtualId).f1;
			}
			addEdgeInternal(upStreamVertexID, downStreamVertexID, typeNumber, partitioner, outputNames, outputTag, shuffleMode);
		}
		//當上游是Partition時,遞回呼叫,并傳入Partition資訊
		else if (virtualPartitionNodes.containsKey(upStreamVertexID)) {
			int virtualId = upStreamVertexID;
			upStreamVertexID = virtualPartitionNodes.get(virtualId).f0;
			if (partitioner == null) {
				partitioner = virtualPartitionNodes.get(virtualId).f1;
			}
			shuffleMode = virtualPartitionNodes.get(virtualId).f2;
			addEdgeInternal(upStreamVertexID, downStreamVertexID, typeNumber, partitioner, outputNames, outputTag, shuffleMode);
		}
		//不是以上邏輯轉換的情況,真正構建StreamEdge
		else {
			StreamNode upstreamNode = getStreamNode(upStreamVertexID);
			StreamNode downstreamNode = getStreamNode(downStreamVertexID);

			// If no partitioner was specified and the parallelism of upstream and downstream
			// operator matches use forward partitioning, use rebalance otherwise.
			//沒有指定partitioner時,會為其選擇forward或者rebalance
			if (partitioner == null && upstreamNode.getParallelism() == downstreamNode.getParallelism()) {
				partitioner = new ForwardPartitioner<Object>();
			} else if (partitioner == null) {
				partitioner = new RebalancePartitioner<Object>();
			}

			if (partitioner instanceof ForwardPartitioner) {
				if (upstreamNode.getParallelism() != downstreamNode.getParallelism()) {
					throw new UnsupportedOperationException("Forward partitioning does not allow " +
							"change of parallelism. Upstream operation: " + upstreamNode + " parallelism: " + upstreamNode.getParallelism() +
							", downstream operation: " + downstreamNode + " parallelism: " + downstreamNode.getParallelism() +
							" You must use another partitioning strategy, such as broadcast, rebalance, shuffle or global.");
				}
			}

			if (shuffleMode == null) {
				shuffleMode = ShuffleMode.UNDEFINED;
			}
			//創建StreamEdge,并將該SteramEdge添加到上游的輸出,下游的輸入,
			StreamEdge edge = new StreamEdge(upstreamNode, downstreamNode, typeNumber, outputNames, partitioner, outputTag, shuffleMode);

			getStreamNode(edge.getSourceId()).addOutEdge(edge);
			getStreamNode(edge.getTargetId()).addInEdge(edge);
		}
	}

2.2 作業圖

JobGraph可以由流計算的StreamGraph和批處理的OptimizedPlan轉換而來,流計算中,在StreamGraph的基礎上進行了一些優化,如果通過OperatorChain機制將算子合并起來,在執行時,調度在同一個Task執行緒上,避免資料的跨執行緒、跨網段的傳遞,

2.2.1 JobGraph核心物件

  • JobVertex

經過算子融合優化后符合條件的多個SteramNode可能會融合在一起生成一個JobVertex,即一個JobVertex包含一個或多個算子,JobVertex的輸入是JobEdge,輸出是IntermediateDataSet,

  • JobEdge

JobEdge是JobGraph中連接IntermediateDataSet和JobVertex的邊,表示JobGraph中的一個資料流轉通道,其上游資料源是IntermediateDataSet,下游消費者是JobVertex,資料通過JobEdge 由IntermediateDataSet傳遞給JobVertex,

  • IntermediateDataSet

中間資料集IntermediateDataSet是一種邏輯結構,用來表示JobVertex的輸出,即該JobVertex中包含的算子會產生的資料集,不同的執行模式下,其對應的結果磁區型別不同,決定了在執行時刻資料交換的模式,

IntermediateDataSet的個數與該JobVertex對應的StreamNode的出邊數量相同,可以是一個或者多個,

2.2.2 JobGraph生成程序

StreamingJobGraphGenerator負責流計算JobGraph的生成,在轉換前需要進行一系列的預處理,

	private JobGraph createJobGraph() {
		preValidate();

		// make sure that all vertices start immediately
		//設定調度模式
		jobGraph.setScheduleMode(streamGraph.getScheduleMode());

		// Generate deterministic hashes for the nodes in order to identify them across
		// submission iff they didn't change.
		//為每個節點生成確定的hashid作為唯一表示,在提交和執行程序中保持不變,
		Map<Integer, byte[]> hashes = defaultStreamGraphHasher.traverseStreamGraphAndGenerateHashes(streamGraph);

		// Generate legacy version hashes for backwards compatibility
		//為了向后保持兼容,為每個節點生成老版本的hash id
		List<Map<Integer, byte[]>> legacyHashes = new ArrayList<>(legacyStreamGraphHashers.size());
		for (StreamGraphHasher hasher : legacyStreamGraphHashers) {
			legacyHashes.add(hasher.traverseStreamGraphAndGenerateHashes(streamGraph));
		}

		Map<Integer, List<Tuple2<byte[], byte[]>>> chainedOperatorHashes = new HashMap<>();
		//真正對SteramGraph進行轉換,生成JobGraph圖
		setChaining(hashes, legacyHashes, chainedOperatorHashes);
		setPhysicalEdges();

		//設定共享slotgroup
		setSlotSharingAndCoLocation();

		setManagedMemoryFraction(
			Collections.unmodifiableMap(jobVertices),
			Collections.unmodifiableMap(vertexConfigs),
			Collections.unmodifiableMap(chainedConfigs),
			id -> streamGraph.getStreamNode(id).getMinResources(),
			id -> streamGraph.getStreamNode(id).getManagedMemoryWeight());
		//配置checkpoint
		configureCheckpointing();

		jobGraph.setSavepointRestoreSettings(streamGraph.getSavepointRestoreSettings());
		//如果有之前的快取檔案的配置,則重新讀入
		JobGraphGenerator.addUserArtifactEntries(streamGraph.getUserArtifacts(), jobGraph);

		// set the ExecutionConfig last when it has been finalized
		try {
			//設定執行環境配置
			jobGraph.setExecutionConfig(streamGraph.getExecutionConfig());
		}
		catch (IOException e) {
			throw new IllegalConfigurationException("Could not serialize the ExecutionConfig." +
					"This indicates that non-serializable types (like custom serializers) were registered");
		}

		return jobGraph;
	}

預處理完畢后,開始構建JobGraph中的點和邊,從Source向下遍歷StreamGraph,逐步創建JObGraph,在創建的程序中同事完成算子融合(OperatorChain)優化,

執行具體的Chain和JobVertex生成、JobEdge的關聯、IntermediateDataSet,從StreamGraph讀取資料的StreamNode開始,遞回遍歷同時將StreamOperator連接在一起,

整理構建的邏輯如下(看上圖!!!):

1)從Source開始,Source與下游的FlatMap不可連接,Source是起始節點,自己成為一個JobVertx,

2)此時開始一個新的連接分析,FlatMap是起始節點,與下游的KeyedAgg也不可以連接,那么FlatMap自己成為一個JobVertex,

3)此時開始一個新的連接分析,KeyedAgg是起始節點,并且與下游的Sink可以連接,那么遞回地分析Sink節點,構造Sink與其下游是否可以連接,因為Slink沒有下游,所以KeyedAgg和Sink節點連接在一起,共同構成了一個JobVertex,在這個JobVertex中,KeyedAgg是起始節點,index編號為0,sink節點index編號為1.

構建JobVertex的時候需要將StreamNode中的重要配置資訊復制到JobVertex中,構建好JobVertex之后,需要構建JobEdge將JobVertex連接起來,KeyedAgg和Sink之間構成了一個算子連接,連接內部的算子之間無序構成JobEdge進行連接,

在構建JobEdge的時候,很重要的一點是確定上游JobVertex和下游JobVertex的資料交換方式,此時根據ShuffleMode來確定ResultPartition型別,用FlinkPartition來確定JobVertex的連接方式,

Shuffle確定了ResultPartition,那么就可以確定上游JobVertex輸出的IntermediateDataSet的型別了,也就知道JobEdge的輸入IntermediateDataSet,

ForwardPartitioner和RescalePartitioner兩種型別的Partitioner轉換為DistributionPattern.POINTWISE 的分發模式,其他型別的Partitioner統一轉換為DistributionPattern.ALL_TO_ALL模式,

JobGraph的構建和OperatorChain優化:

private List<StreamEdge> createChain(
			Integer startNodeId,
			Integer currentNodeId,
			Map<Integer, byte[]> hashes,
			List<Map<Integer, byte[]>> legacyHashes,
			int chainIndex,
			Map<Integer, List<Tuple2<byte[], byte[]>>> chainedOperatorHashes) {

		if (!builtVertices.contains(startNodeId)) {

			List<StreamEdge> transitiveOutEdges = new ArrayList<StreamEdge>();

			List<StreamEdge> chainableOutputs = new ArrayList<StreamEdge>();
			List<StreamEdge> nonChainableOutputs = new ArrayList<StreamEdge>();

			StreamNode currentNode = streamGraph.getStreamNode(currentNodeId);
			//獲取當前節點的出邊,判斷是否符合OperatorChain的條件
			//分為兩類:chainableoutputs,nonchainableoutputs
			for (StreamEdge outEdge : currentNode.getOutEdges()) {
				if (isChainable(outEdge, streamGraph)) {
					chainableOutputs.add(outEdge);
				} else {
					nonChainableOutputs.add(outEdge);
				}
			}
			//對于chainable的邊,遞回呼叫createchain
			//回傳值添加到transitiveOutEdges中
			for (StreamEdge chainable : chainableOutputs) {
				transitiveOutEdges.addAll(
						createChain(startNodeId, chainable.getTargetId(), hashes, legacyHashes, chainIndex + 1, chainedOperatorHashes));
			}

			//對于無法chain在一起的邊,邊的下游節點作為Operatorchain的Head節點
			//進行遞回呼叫,回傳值添加到transitiveOutEdges中
			for (StreamEdge nonChainable : nonChainableOutputs) {
				transitiveOutEdges.add(nonChainable);
				createChain(nonChainable.getTargetId(), nonChainable.getTargetId(), hashes, legacyHashes, 0, chainedOperatorHashes);
			}

			List<Tuple2<byte[], byte[]>> operatorHashes =
				chainedOperatorHashes.computeIfAbsent(startNodeId, k -> new ArrayList<>());

			byte[] primaryHashBytes = hashes.get(currentNodeId);
			OperatorID currentOperatorId = new OperatorID(primaryHashBytes);

			for (Map<Integer, byte[]> legacyHash : legacyHashes) {
				operatorHashes.add(new Tuple2<>(primaryHashBytes, legacyHash.get(currentNodeId)));
			}

			chainedNames.put(currentNodeId, createChainedName(currentNodeId, chainableOutputs));
			chainedMinResources.put(currentNodeId, createChainedMinResources(currentNodeId, chainableOutputs));
			chainedPreferredResources.put(currentNodeId, createChainedPreferredResources(currentNodeId, chainableOutputs));

			if (currentNode.getInputFormat() != null) {
				getOrCreateFormatContainer(startNodeId).addInputFormat(currentOperatorId, currentNode.getInputFormat());
			}

			if (currentNode.getOutputFormat() != null) {
				getOrCreateFormatContainer(startNodeId).addOutputFormat(currentOperatorId, currentNode.getOutputFormat());
			}
			//如果當前節點是起始節點,則直接創建JobVertex,否則回傳一個空的StreamConfig
			StreamConfig config = currentNodeId.equals(startNodeId)
					? createJobVertex(startNodeId, hashes, legacyHashes, chainedOperatorHashes)
					: new StreamConfig(new Configuration());

			//將StreamNode中的配置資訊序列化到Streamconfig中,
			setVertexConfig(currentNodeId, config, chainableOutputs, nonChainableOutputs);
			//再次判斷,如果是Chain的起始節點,執行connect()方法,創建JobEdge和IntermediateDataset
			//否則將當前節點的StreamConfig 添加到chainedConfig中,
			if (currentNodeId.equals(startNodeId)) {

				config.setChainStart();
				config.setChainIndex(0);
				config.setOperatorName(streamGraph.getStreamNode(currentNodeId).getOperatorName());

				for (StreamEdge edge : transitiveOutEdges) {
					connect(startNodeId, edge);
				}

				config.setOutEdgesInOrder(transitiveOutEdges);
				config.setTransitiveChainedTaskConfigs(chainedConfigs.get(startNodeId));

			} else {
				chainedConfigs.computeIfAbsent(startNodeId, k -> new HashMap<Integer, StreamConfig>());

				config.setChainIndex(chainIndex);
				StreamNode node = streamGraph.getStreamNode(currentNodeId);
				config.setOperatorName(node.getOperatorName());
				chainedConfigs.get(startNodeId).put(currentNodeId, config);
			}

			config.setOperatorID(currentOperatorId);

			if (chainableOutputs.isEmpty()) {
				config.setChainEnd();
			}
			return transitiveOutEdges;

		} else {
			return new ArrayList<>();
		}
	}

2.2.3 算子融合

一個Operatorchain在同一個Task執行緒內執行,OperatorChain內的算子之間,在同一個執行緒內通過方法呼叫的方式傳遞資料,能減少執行緒之間的切換,減少訊息的序列化/反序列化,無序借助記憶體快取區,也無須通過網路在算子間傳遞資料,可在減少延遲的同時提高整體吞吐量

operatorchain的條件:

1)下游節點的入度為1

2)SteramEdge的下游節點對應的算子不為null

3)StreamEdge的上游節點對應的算子不為null

4)StreamEdge的上下游節點擁有相同的slotSharingGroup,默認都是default.

5)下游算子的連接策略為ALWAYS.

6)上游算子的連接策略為ALWAYS 或者HEAD.

7)StreamEdge的磁區型別為ForwardPartitioner

8)上下游節點的并行度一致

9)當前StreamGraph允許chain

2.3 執行圖

2.3.1 ExecutionGraph核心物件

  • ExecutionJobVertex

該物件和JobGraph中的JobVertex一一對應,該物件還包含了一組ExecutionVertex,數量與該JobVertex中所包含的SteramNode的并行度一致,

ExecutionJobVertex用來將一個JobVertex封裝成一ExecutionJobVertex,并以此創建ExecutionVertex、Execution、IntermediateResult和IntermediateResultPartition,用于豐富ExecutionGraph,

在ExecutionJobVertex的建構式中,首先是依據對應的JobVertex的并發度,生成對應個數的ExecutionVertex,其中,一個ExecutionVertex代表一個ExecutionJobVertex的并發子Task,然后是將原來JobVertex的中間結果IntermediateDataSet轉化為ExecutionGrap中IntermediateResult

  • ExecutionVertex

ExecutionJobVertex中會對作業進行并行化處理,構造可以并行執行的實體,每個并行執行的實體就是ExecutionVertex.

構建ExecutionVertex的同時,也回構建ExecutionVertex的輸出IntermediateResult,并且將ExecutionEdge輸出為IntermediatePartition,

ExecutionVertex的建構式中,首先會創建IntermediatePartition,并通過IntermediateResult.setPartition()建立IntermediateResult和IntermediateResultPartition之間的關系,然后生成Execution,并配置資源相關,

  • IntermediateResult

IntermediateResult又叫做中間結果集,該物件是個邏輯概念,表示ExecutionJobVertex的輸出,和JobGraph中的IntermediateDataSet一一對應,同樣,一個ExecutionJobVertex可以有多個中間二級果,取決于當前JobVertex有幾個出邊,

一個中間結果集包含多個中間結果磁區IntermediateResultPartition,其個數等于該JobVertex的并發度,

  • IntermediateResultPartition

IntermediateResultPartition又叫做中間結果磁區,表示1個ExecutionVertex輸出結果,與ExecutionEdge相關聯,

  • ExecutionEdge

表示ExecutionVertex的輸入,連接到上游產生的IntermediateResultPartition,一個Execution對應于唯一的一個IntermediateResultPartition和一個ExecutionVertex,一個ExecutionVertex可以有多個ExecutionEdge,

  • Execution

ExecutionVertex相當于每個Task的模板,在真正執行的時候,會將ExecutionVertex中的資訊包裝為一個Execution,執行一個ExecutionVertex的一次嘗試,JobManager和TaskManager之間關于Task的部署和Task執行狀態的更新都是通過ExecutionAttemptID來標識實體的,在故障或者資料需要重算的情況下,ExecutionVertex可能會有多個ExecutionAttemptID.一個Execution通過ExecutionAttemptID標識,

2.3.2 ExecutionGrap生成程序

初始話作業調度器的時候,根據JobGraph生活ExecutionGraph,在SchedulerBase的構造方法中觸發構建,最終呼叫SchedulerBase#createExecutionGraph 觸發實際的構建動作,使用ExecutionGraphBuiler構建ExecutionGraph,

核心代碼attachJobGraph:

構建ExecutionEdge 的連接策略

  • 點對點連接(DistributionPattern.POINTWISE)

該策略用來連接當前ExecutionVertex與上游的IntermediataeResultParition,

連接分三種情況

1)一對一連接:并發的Task數量與磁區數相等,

2)多對一連接:下游的Task數量小于上游的磁區數,此時分兩種情況:

a:下游Task可以分配同數量的結果磁區IntermediataeResultParition,如上游有4個結果磁區,下游有2個Task,那么每個Task會分配兩個結果磁區進行消費,

b:每個Task消費的上游磁區結果資料不均,如上游有3個結果磁區,下游有兩個Task,那么一個Task分配2個結果磁區消費,另一個分配一個結果磁區消費,

3)一對多連接:下游的Task數量多余上游的磁區數,此時兩種情況:

a:每個結果磁區的下游消費Task資料量相同,如上游有兩個結果磁區,下游有4個Task,每個結果磁區被兩個Task消費,

b:每個結果磁區的下游消費Task數量不相同,如上游有兩個結果磁區,下游有3個Task,那么一個結果磁區分配2個Task消費,另一個結果磁區分配一個Task消費,

  • 全連接(DistributionPattern.ALL_TO_ALL)

該策略下游的ExecutionVertex與上游的所有IntermediataeResultParition建立連接,消費其生產的資料,一般全連接的情況意味著資料在Shuffle,

接下來Flink資源管理篇,如果對Flink感興趣或者正在使用的小伙伴,可以加我入群一起探討學習,

參考書籍《Flink 內核原理與實作》

歡迎關注公眾號: 資料基石

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/297784.html

標籤:其他

上一篇:關于CSDN的一點記錄

下一篇:python 生產者消費者模式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more