Flink源码漫游指南<陆>Flink的默认资源调度过程

注意:

  • 本章源码基于1.10版本,由于这部分重构频繁,太早(1.8)和太近(1.14)的版本都会和本版本有较大出入。
  • 本章所指的调度过程指的是从生成ExecutionGraph之后,为其分配slot的过程,前面transform的过程和后面deply的过程不包含在内。

一、Flink的默认调度器

Flink的自带调度器统一实现了一个叫SchedulerNG的接口,继承关系如下:

再来看看这个接口里面有什么功能

 

 在Job提交过程中,JobMaster会调用内部的startScheduling()方法开启调度过程,本文章就围绕着这一方法展开。

startScheduling()这个方法写在抽象类SchedulerBase中,我们看看他干了什么

 我们再看看DefaultScheduler.startSchedulingInternal()干了什么吧

这里转到了SchedulingStrategy,我们再看看这个类 

  这是一个接口,有以上实现类,我们以EagerSchedulingStrategy为例继续看看他的startScheduling干了什么,这个类会对Job立即进行部署。

 上面两段代码显示了这一段的调用关系,然后出现了几个新的类和名词,我在下面的引用框中解释一下,如果你知道,可以跳过这一段,直接看allocateSlotsAndDeploy干了什么。

其中要注意,这里生成的ExecutionVertexDeploymentOption的列表是拓扑排序的顺序。

 

1、什么是Vertex?

ExecutionGraph中节点就叫Vertex。

2、ExecutionVertexDeploymentOption是什么?

是存储需要被调度的task的组件,里面只包装了VertexId和DeploymentOption,而DeploymentOption中只包含一个信息,就是“这个task是否需要发送scheduleOrUpdateConsumer给master”

3、什么是SchedulerOperations?

 顾名思义就是定义了具体的调度和部署操作(本文章只讲调度部分),里面只有一个方法。

allocateSlotsAndDeploy()上面的绿字翻译过来大概是

* 分配slots和当slots返回的时候进行部署
* 会根据List的顺序进行分配(优先分配靠前的)
* 只有Vertex状态为CREATED的才会被接受,如果不是CREATED状态会报错

 好的,打完岔我们继续看ScheduleStrategy的默认实现类DefaultScheduler中的allocateSlotsAndDeploy干了啥(可以放大看

 可以看到,调度的最后一步被框起来了,我们继续进去看看

先通过executionVertexDeploymentOptions中的VertexId生成ExecutionVertexSchedulingRequirements(包含调度一个Vertex的需求),再通过ExecutionSlotAllocator.allocator继续套皮

再看看这个ExecutionSlotAllocator是什么

 

 官方说它是把slots分配到execution的组件(也就是调度),里面有一个最重要的方法就是allocator,其中定义了调度的逻辑

我们快进到这个类的默认实现中看一看

@Override
	public List<SlotExecutionVertexAssignment> allocateSlotsFor(
			List<ExecutionVertexSchedulingRequirements> executionVertexSchedulingRequirements) {

		//查看是否有vertex已经分配了(检查有效性
		validateSchedulingRequirements(executionVertexSchedulingRequirements);

		//生成初始的SlotExecutionVertexAssignment的List
		List<SlotExecutionVertexAssignment> slotExecutionVertexAssignments =
				new ArrayList<>(executionVertexSchedulingRequirements.size());

		//计算并返回一个集合,集合包含所有调度的execution vertex的上一次的分配id
		Set<AllocationID> allPreviousAllocationIds = computeAllPriorAllocationIds(executionVertexSchedulingRequirements);

		//对每一个Requirement单独处理
		for (ExecutionVertexSchedulingRequirements schedulingRequirements : executionVertexSchedulingRequirements) {
			final ExecutionVertexID executionVertexId = schedulingRequirements.getExecutionVertexId();
			final SlotRequestId slotRequestId = new SlotRequestId();
			final SlotSharingGroupId slotSharingGroupId = schedulingRequirements.getSlotSharingGroupId();

			LOG.debug("Allocate slot with id {} for execution {}", slotRequestId, executionVertexId);

			//计算一个execution的合适分配地点
			//先根据state算,如果没有就根据input算
			CompletableFuture<LogicalSlot> slotFuture = calculatePreferredLocations(
					executionVertexId,
					schedulingRequirements.getPreferredLocations(),
					inputsLocationsRetriever).thenCompose(
							(Collection<TaskManagerLocation> preferredLocations) ->
								slotProviderStrategy.allocateSlot(    //⭐根据SlotRequestId、ScheduledUnit、SlotProfile进行分配
									slotRequestId,                    //ScheduledUnit指需要分配slot的task,SlotProfile描述了task想要的slot
									new ScheduledUnit(
										executionVertexId.getJobVertexId(),
										slotSharingGroupId,
										schedulingRequirements.getCoLocationConstraint()),
									SlotProfile.priorAllocation(
										schedulingRequirements.getTaskResourceProfile(),
										schedulingRequirements.getPhysicalSlotResourceProfile(),
										preferredLocations,
										Collections.singletonList(schedulingRequirements.getPreviousAllocationId()),
										allPreviousAllocationIds)));

			SlotExecutionVertexAssignment slotExecutionVertexAssignment =
					new SlotExecutionVertexAssignment(executionVertexId, slotFuture);
			// add to map first to avoid the future completed before added.
			pendingSlotAssignments.put(executionVertexId, slotExecutionVertexAssignment);

			slotFuture.whenComplete(
					(ignored, throwable) -> {
						pendingSlotAssignments.remove(executionVertexId);
						if (throwable != null) {
							slotProviderStrategy.cancelSlotRequest(slotRequestId, slotSharingGroupId, throwable);
						}
					});

			slotExecutionVertexAssignments.add(slotExecutionVertexAssignment);
		}

		return slotExecutionVertexAssignments;
	}

 其中带⭐的那一行标注了进行分配的逻辑,我们再快进到slotProviderStrategy的默认实现中看一看

原来是又调用了SlotProvider的方法,我们再进去看一看SlotProvider

slotprovider有这么多实现类,到底系统默认用的哪个呢?

经过我的不屑翻找,终于找到了

 默认使用的实现类是SchedulerImpl,所以我们要去这个类的allocateSlot方法里看一看,具体的跳转我就不一个个看了,直接看最后的

然后又涉及到SlotSelectionStrategy类,又是个没见过的类,我们再去看看

 

下面直接写答案,代码就不复制了,怕弄乱

  •  SlotSelectionStrategy
    • LocationPreferenceSlotSelectionStrategy  基于位置偏好进行选择,根据locality进行打分,打完了选候选中分最高的,打分方法写在子类中了
      • EvenlySpreadOutLocationPreferenceSlotSelectionStrategy  当没有位置偏好的时候,从符合task要求的slot中选择executor利用率最小的
      • DefaultLocationPreferenceSlotSelectionStrategy  当没有位置偏好的时候,从候选中选择第一个匹配的
    • PreviousAllocationSlotSelectionStrategy  基于之前的位置进行选择,如果没有这个参数,就按位置偏好进行选择

 

以上,schedule过程(为task分配合适的slot)就完毕了,后面可能还会写一篇补充一下TaskManager

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要将Flink的KafkaSource<ObjectNode>转换为DataStream<RowData>,需要进行以下步骤: 1. 创建一个DeserializationSchema,将ObjectNode反序列化为RowData对象。例如,可以使用JsonRowDataDeserializationSchema。 2. 使用KafkaSource从Kafka主题中读取ObjectNode对象,并使用上一步中创建的DeserializationSchema将其转换为RowData对象。 3. 在DataStream上应用map操作,将RowData对象转换为所需的格式。 下面是一个示例代码,使用JsonRowDataDeserializationSchema将ObjectNode转换为RowData,然后将其转换为Tuple2<String, String>: ``` import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.typeutils.RowTypeInfo; import org.apache.flink.formats.json.JsonRowDataDeserializationSchema; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.table.api.TableSchema; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; import org.apache.flink.table.types.logical.RowType; import org.apache.flink.types.Row; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.Properties; public class KafkaObjectNodeToRowData { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env); Properties properties = new Properties(); properties.setProperty("bootstrap.servers", "localhost:9092"); properties.setProperty("group.id", "test"); // create a DeserializationSchema to deserialize ObjectNode to RowData RowType rowType = new RowType( new TableSchema.Builder() .field("id", DataTypes.BIGINT()) .field("name", DataTypes.STRING()) .build().toRowDataType().getChildren()); DeserializationSchema<RowData> deserializationSchema = new JsonRowDataDeserializationSchema(rowType); // create a FlinkKafkaConsumer to read ObjectNode from Kafka FlinkKafkaConsumer<ObjectNode> kafkaConsumer = new FlinkKafkaConsumer<>("my-topic", deserializationSchema, properties); // read from Kafka and convert ObjectNode to RowData DataStream<RowData> rowDataStream = env.addSource(kafkaConsumer) .map(objectNode -> { // convert ObjectNode to RowData Row row = new Row(objectNode.size()); for (int i = 0; i < objectNode.size(); i++) { row.setField(i, objectNode.get(i).asText()); } return row; }) .returns(new RowTypeInfo(rowType.getFields().stream().map(RowType.RowField::getType).toArray(TypeInformation[]::new), rowType.getFieldNames().toArray(new String[0]))); // convert RowData to Tuple2<String, String> DataStream<Tuple2<String, String>> tupleStream = rowDataStream .map(rowData -> Tuple2.of(rowData.getField(0).toString(), rowData.getField(1).toString())) .returns(Types.TUPLE(Types.STRING, Types.STRING)); // do something with the tupleStream tupleStream.print(); env.execute("KafkaObjectNodeToRowData"); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值