Flink源码解析(十八)——Flink Task读数据过程解析

上一篇随笔解析到Flink Task写数据过程,数据最终被写入ResultPartition结果分区模型中。每个ResultPartition实例都包含一到多个ResultSubPartition结果子分区,经过RecordWriter分区器分配后数据以NetworkBuffer形式存放在特定结果子分区buffers集合中,等待被下游Task消费。本篇随笔继续解析Flink Task读数据过程及用到的数据模型。这两篇随笔只涉及到Flink Task线程的数据读写过程,不涉及上下游Task之间的网络数据请求及传输,上下游Task之间数据请求及传输过程基于信用的Netty通信机制实现,该机制将在下篇随笔讲解。

一、StreamTask

StreamTask是算子的执行容器,可以从StreamTask执行中解析Flink Task读数据过程。随笔十六讲解到MailBox线程执行模型默认动作是StreamTask类的processInput(...)方法,该方法承担着StreamTask读数据的入口。本篇随笔就以processInput(...)方法为起点解析Task读数据构成。在Task启动初始化后触发processInput(...)方法的循环调用。

下图可知读数据的关键步骤是StreamInputProcessor.processInput()方法。

以StreamOneInputProcessor实现为例,processInput()方法中StreamTaskInput.emitNext(output)方法用来读取数据。

AbstractStreamTaskNetworkInput类实现中,emitNext(...)方法内第一次循环时currentRecordDeserializer为null,直到执行第124行时才会给currentRecordDeserializer赋值。processElement(...)方法借助output入参将数据传递到具体算子的UDF函数中执行。而数据读取操作在checkpointedInputGate.pollNext()方法中实现。

在StreamTask初始化时,根据checkpointMode配置参数的不同会创建不同的CheckpointBarrierHandler,如果是EXACTLY_ONCE时则创建SingleCheckpointBarrierHandler实例,进行barrier对齐。如果是AT_LEAST_ONCE时则创建CheckpointBarrierTracker实例,不会进行barrier对齐。具体CheckpointBarrierHandler实例类型初始化完成时会赋值给CheckpointedInputGate实例的barrierHandler成员。下述pollNext()方法通过InputGate.pollNext()方法读取数据。

以上是StreamTask阶段读数据过程,下面会转入到SingleInputGate读数据执行过程中。

二、SingleInputGate

InputGate的实现类主要有两个,一是SingleInputGate、二是UnionInputGate。读数据过程常见的是SingleInputGate。UnionInputGate是将多个SingleInputGate合并在一起的InputGate,例如CoStreamMap等算子从上游两个流中输入数据,用的就是UnionInputGate,下面着重解析SingleInputGate。

1、SingleInputGate
先来看下SingleInputGate成员结构,下面是SingleInputGate成员变量的含义:

private final int gateIndex:代表当前Task消费的上游Task的下标,大部分情况下一个算子只有一个上游输入,如果有多个上游输入,gateIndex变量标识哪个上游输入。
private final IntermediateDataSetID consumedResultId:代表当前Task消费的上游算子的中间结果集。
private final ResultPartitionType consumedPartitionType:代表当前InputGate的消费分区类型,Flink流式应用一般都是PIPELINED_BOUNDED模式,采用有限个buffer缓存来支持上下游同时生产和消费数据。
private final IndexRange subpartitionIndexRange:代表消费上游Task的子分区下标。
private final int numberOfInputChannels:代表有多少个上游结果子分区输入。
private final Map<SubpartitionInfo, InputChannel> inputChannels:代表上游结果子分区输入明细。
private final InputChannel[] channels:代表上游结果子分区输入明细。
private final PrioritizedDeque<InputChannel> inputChannelsWithData = new PrioritizedDeque<>():当前可读取数据的InputChannel。
private BufferPool bufferPool:代表SingleInputGate的本地buffer池,用来缓存读取到的数据。

2、SingleInputGate创建回顾

在随笔十五Flink Task部署过程中解析过InputGateDeploymentDescriptor组件的创建过程,它包含InputGate的部署描述信息。在随笔十六Flink Task启动过程中解析到Flink系统利用InputGateDeploymentDescriptor描述信息创建SingleInputGate的过程。可以回顾看下。

3、SingleInputGate读数据过程。

继续解析SingleInputGate.pollNext()方法读取数据过程。下图可知通过waitAndGetNextData(...)方法获取可用的InputChannel、数据Buffer等信息。

getChannel(...)方法获取当前可读取数据的InChannel信息,readBufferFromInputChannel(...)方法从可读取数据InputChannel中开始获取数据。

可知转入到具体的InputChannel中读取数据。

三、InputChannel

在实际生产环境中,上下游Task有可能被部署到同一个机器节点上,这时InputChannel类型就是LocalInputChannel。也有可能被部署到不同的机器节点上,这时InputChannel类型就是RemoteInputChannel。下面分别解析这两种情况下的InputChannel类型。

1、LocalInputChannel关键成员结构如下:

private final ResultPartitionManager partitionManager:收集管理结果分区
private volatile ResultSubpartitionView subpartitionView:结果子分区视图,封装了ResultSubpartition中读取数据、释放资源等行为。其中getNextBuffer()方法定义了获取Buffer数据的过程。

2、RemoteInputChannel关键成员结构如下:

private final ConnectionManager connectionManager:与其他节点的通信连接管理组件。

private final PrioritizedDeque<SequenceBuffer> receivedBuffers = new PrioritizedDeque<>():当前从上游接收到的Buffer队列,后续会被算子消费处理。

private volatile PartitionRequestClient partitionRequestClient:和上游Task的连接客户端,Flink是用Netty通信框架实现节点之间的数据通信过程。

private final int initialCredit:初始信用凭证,代表该InputChannel独有的初始Buffer数量。

private final BufferManager bufferManager:Buffer管理器。

3、InputChannel读取数据过程

上节SingleInputGate最后解析到InputChannel.getNextBuffer()开始获取Buffer数据。下面就两种InputChannel类型分别介绍getNextBuffer()方法实现过程。

(1)、LocalInputChannel.getNextBuffer()方法

LocalInputChannel类型意味着上下游Task在同一个机器节点上,上篇随笔讲解到数据Buffer最后会被存储在PipelinedSubpartition.buffers队列集合中。而LocalInputChannel类型会以同步的形式从PipelinedSubpartition.buffers获取数据,实现同一个机器节点上下游Task数据写和读操作。下图为LocalInputChannel的读数据过程。

可知最终转入到PipelinedSubpartition.pollBuffer()方法中获取buffers队列的数据。

(2)、RemoteInputChannel.getNextBuffer()方法

下图可知RemoteInputChannel获取Buffer数据操作比较简单,就是以同步的方式从receivedBuffers队列里获取一个Buffer数据并返回,而receivedBuffers队列Buffer数据添加动作是在Netty通信过程中发生的。通过CreditBasedPartitionRequestClientHandler处理器解析获取的数据并添加到receivedBuffers队列。

以上即为Flink Task读数据过程解析。通过Flink Task读写数据随笔介绍,我们知道了Task数据的基本操作,下篇随笔继续介绍ResultSubPartition的buffers数据是怎么发送给下游的,RemoteInputChannel的数据是怎么通过网络传输得到的。

posted @ 2024-03-03 14:23  有一个娃  阅读(17)  评论(0编辑  收藏  举报