文章目录
一. Taskmanager之间传递数据细节
Flink作业最终会被转换为ExecutionGraph
并拆解成Task,在TaskManager中调度并执行,Task实例之间会发生跨TaskManager节点的数据交换,尤其是在DataStream API中使用了物理分区操作
的情况。
ResultPartition组件存放中间结果等待下游节点消费:
从ExecutionGraph到物理执行图的转换中可以看出,
ExecutionVertex最终会被转换为Task实例运行
,在ExecutionGraph中上游节点
产生的数据被称为IntermediateResult,物理执行图对应ResultPartition组件
。在ResultPartition组件中会根据分区的数量
再细分为ResultSubPartition。在ResultSubPartition中主要有BufferConsumer队列,用于本地存储Buffer数据
,供下游的Task节点消费使用。
InputChannel读取上游数据
对下游的Task实例来讲,主要
依赖InputGate组件读取上游数据
,在InputGate组件中InputChannel和上游
的ResultSubPartition数量相同(发送逻辑是?起到shuffle的作用
)。
因此RecordWriter
向ResultPartition中的ResultSubPartition写入Buffer数据,就是在向下游的InputChannel写入数据,因为最终会从ResultSubPartition的队列中读取Buffer数据再经过TCP网络
连接发送到对应的InputChannel中。
ResultPartition(存储中间结果集)和InputGate(读取中间结果集)组件的创建
TaskManager接收到JobManager的Task创建请求时,会根据TaskDeploymentDescriptor中的参数创建并初始化ResultPartition和InputGate组件。
Task启动成功并开始接入数据后,使用ResultPartition和InputGate组件实现上下游算子之间的跨网络数据传输
。
ShuffleMaster管理ResultPartition和InputGate。
在TaskManager实例中,主要通过
ShuffleEnvironment统一创建
ResultPartition和InputGate组件。在JobMaster中也会创建ShuffleMaster统一管理和监控作业中所有的ResultPartition和InputGate组件
。
因此在介绍ResultPartition和InputGate之前,我们先了解一下ShuffleMaster和ShuffleEnvironment的主要作用和创建过程。
二. ShuffleService的设计与实现
如图,创建ShuffleMaster和ShuffleEnvironment组件主要依赖ShuffleServiceFactory
实现。同时为了实现可插拔
的ShuffleService服务,ShuffleServiceFactory的实现类通过Java SPI的方式加载到ClassLoader中,即通过ShuffleServiceLoader从配置文件中加载系统配置的ShuffleServiceFactory实现类,因此用户也可以自定义实现Shuffle服务。
基于SPI的方式加载ShuffleServiceFactory
在JobManager内部创建JobManagerRunner实例的过程中会创建ShuffeServiceLoader,用于通过Java SPI服务的方式加载配置的ShuffleServiceFactory,同时在TaskManager的TaskManagerServices中创建ShuffeServiceLoader并加载ShuffleServiceFactory。
ShuffleServiceFactory提供了创建ShuffleMaster和ShuffleEnvironment的能力
ShuffleServiceFactory接口定义中包含创建ShuffleMaster和ShuffleEnvironment的方法。Flink提供了基于Netty通信框架实现的NettyShuffleServiceFactory,作为ShuffleServiceFactory接口的默认实现类。
ShuffleEnvironment组件提供了创建Task实例中ResultPartition和InputGate组件的方法,同时Flink中默认提供了NettyShuffleEnvironment实现。
ShuffleMaster组件实现了对ResultPartition和InputGate的注册功能
ShuffleMaster组件实现了对ResultPartition和InputGate的注册功能,同时每个作业都有ShuffleMaster管理当前作业的ResultPartition和InputGate等信息,Flink中提供了NettyShuffleMaster默认实现。
ShuffleService UML关系图
三. 在JobMaster中创建ShuffleMaster
创建ShuffleMaster,ShuffleEnvironment的大致过程
- 通过ShuffleServiceFactory可以创建ShuffleMaster和ShuffleEnvironment服务,其中ShuffleMaster主要用在JobMaster调度和执行Execution时,维护
当前作业中的ResultPartition信息,例如ResourceID、ExecutionAttemptID等
。- 紧接着JobManager会将ShuffleMaster创建的NettyShuffleDescriptor参数信息发送给对应的TaskExecutor实例,在TaskExecutor中就会基于NettyShuffleDescriptor的信息,通过ShuffleEnvironment组件创建ResultPartition、InputGate等组件。
分配slot资源,并将分区信息注册到ShuffleMaster中
如代码清单,在JobMaster开始向Execution分配Slot资源时,会通过分配的Slot计算资源获取TaskManagerLocation信息,然后调用Execution.registerProducedPartitions()方法将分区信息注册到ShuffleMaster中。
CompletableFuture<Execution> allocateResourcesForExecution(
SlotProviderStrategy slotProviderStrategy,
LocationPreferenceConstraint locationPreferenceConstraint,
@Nonnull Set<AllocationID> allPreviousExecutionGraphAllocationIds) {
return allocateAndAssignSlotForExecution(
slotProviderStrategy,
locationPreferenceConstraint,
allPreviousExecutionGraphAllocationIds)
.thenCompose(slot -> registerProducedPartitions(slot.getTaskManagerLocation()));
}
Execution.registerProducedPartitions()方法逻辑如下。
- 创建ProducerDescriptor对象,其中包含了分区生产者的基本信息,例如
网络连接地址和端口以及TaskManagerLocation信息
。- 获取当前ExecutionVertex节点对应的IntermediateResultPartition信息,在IntermediateResultPartition结构中包含了
ExecutionVertex、IntermediateResultPartitionID以及ExecutionEdge
等逻辑分区信息。- 遍历IntermediateResultPartition列表,将IntermediateResultPartition转换为PartitionDescriptor数据结构,然后调用ExecutionGraph的ShuffleMaster服务,
将创建的PartitionDescriptor和ProducerDescriptor注册到ShuffleMaster服务中
。- 根据ShuffleDescriptor创建ResultPartitionDeploymentDescriptor并添加到partitionRegistrations集合中。(
producedPartitions信息会被TaskManager的ShuffleEnvironment用于创建ResultPartition和InputGate等组件。
)
static CompletableFuture<Map<IntermediateResultPartitionID, ResultPartitionDep
loymentDescriptor>> registerProducedPartitions(
ExecutionVertex vertex,
TaskManagerLocation location,
ExecutionAttemptID attemptId,
boolean sendScheduleOrUpdateConsumersMessage) {
// 创建ProducerDescriptor
ProducerDescriptor producerDescriptor =
ProducerDescriptor.create(location, attemptId);
// 获取当前节点的partition信息
Collection<IntermediateResultPartition> partitions =
vertex.getProducedPartitions().values();
Collection<CompletableFuture<ResultPartitionDeploymentDescriptor>>
partitionRegistrations =
new ArrayList<>(partitions.size());
// 向ShuffleMaster注册partition信息
for (IntermediateResultPartition partition : partitions) {
PartitionDescriptor partitionDescriptor = PartitionDescriptor.from(partition);
int maxParallelism = getPartitionMaxParallelism(partition);
// 调用ShuffleMaster注册partitionDescriptor和producerDescriptor
CompletableFuture<? extends ShuffleDescriptor> shuffleDescriptorFuture = vertex
.getExecutionGraph()
.getShuffleMaster()
.registerPartitionWithProducer(partitionDescriptor, producerDescriptor);
Preconditions.checkState(shuffleDescriptorFuture.isDone(),
"ShuffleDescriptor future is incomplete.");
// 创建ResultPartitionDeploymentDescriptor实例
CompletableFuture<ResultPartitionDeploymentDescriptor>
partitionRegistration =
shuffleDescriptorFuture
.thenApply(shuffleDescriptor -> new ResultPartitionDeploymentDescriptor(
partitionDescriptor,
shuffleDescriptor,
maxParallelism,
sendScheduleOrUpdateConsumersMessage));
// 添加到partitionRegistrations集合中
partitionRegistrations.add(partitionRegistration);
}
// 转换存储结构
return FutureUtils.combineAll(partitionRegistrations).thenApply(rpdds -> {
Map<IntermediateResultPartitionID, ResultPartitionDeploymentDescriptor>
producedPartitions =
new LinkedHashMap<