Flink源码解析之TM之间的数据传输

目录

一、背景

二、基本概念与设计思想

2.1 图讲解

2.2 Shuffle

2.3 流量控制(反压)

2.3.1 TCP-based Flow Control

2.3.2 Credit-based Flow Control

三、数据的输出源码分析

3.1 ReusltPartition 

​编辑

3.2 ResultSubpartition

3.3 RecordWriter

四、数据的读取源码分析

4.1 InputGate

4.2 InputeChannel

4.2.1 LocalInputChannel

4.2.2 RemoteInputChannel

五、反压机制源码分析

5.1 基本思路

5.2 信用值发送具体实现

5.3 信用值回传具体实现


 一、背景

《Flink核心技术源码剖析与特性开发》第八章数据传输,读书笔记,主要讲解TM之间的数据传输,包括反压的实现 

基于Flink1.10,版本有点老,但思想是没变的

二、基本概念与设计思想

2.1 图讲解

理解这个需要先了解下Flink的execution graph与物理执行图,有这个图才能理清每个类在物理传输之间的作用,这里给个书上的图:不清楚的也可以看我之前写的:

短分享-Flink图构建-优快云博客

备注:

上面图节点的对应关系:

ExecutionVertex:IntermidateResultPartition:ResultPartition = 1:1:1

ExecutionEdge:InputChannel = 1:1

在execution graph中IntermidateResultPartition会连接多个ExecutionEdge,在物理执行图中,IntermidateResultPartition会生成对应个数的ResultSubPartition与Inputchannel一对一连接

Flink中ResultPartition负责数据的输出,InputGate负责数据的输入

数据的重分区发生在每个并行实例将数据写入ResultSubPartition的过程中

2.2 Shuffle

数据传输,要搞懂是谁与谁传输,所以要了解一下其Shuffle机制,可以看我写的

Hadoop、Spark、Flink Shuffle对比-优快云博客

字数较多,就不一一赘述了

2.3 流量控制(反压)

关于这块,书上说的一般,我参考了流处理框架反压机制-优快云博客来说一下

2.3.1 TCP-based Flow Control

Flink 1.5 版本之前基于TCP滑动窗口做反压,这里Flink并没有对TCP做什么特殊开发,只是基于TCP原本的机制

预备知识:InputGate向LocalBufferPool申请内存,LocalBufferPool向NetworkBufferPool申请内存

当数据处理不过来时

NetworkBufferPool内存耗尽

LocalBufferPool内存耗尽

InputGate打满,不再接受Netty数据

Netty内存打满,不再接受Socket数据

Socket内存打满,TCP通过ACK机制中的“窗口大小”通知发送端暂停发送(滑动窗口机制)

然后发送方的Socket、Netty、LocalBufferPool也依次耗尽,Task A阻塞

TaskManager反压内走的是一个线程,一块内存,所以结果见下图:

这样做有两个弊端

1. TM中可能有多个任务,一个任务出现问题,因为基于一个TCP,会影响其它任务

2. 缓存耗尽才会产生背压,并且缓存还有很多级,生效太慢

2.3.2 Credit-based Flow Control

大概是下面原理

ResultPartition会初始化一个值假设为a,表示InputGate的剩余量,每次发送数据a都会减一

InputGate同样也会告诉ResultPartition自动还能接收多少,自己的剩余,这个a也会每次更新

三、数据的输出源码分析

3.1 ReusltPartition 

该类主要是做数据与内存的管理分配,主要讲解下bufferPool,篇幅较长,可以看下我写的:Flink TM数据传输时的内存分配-优快云博客

3.2 ResultSubpartition

可以看到ReusltPartition中有个ResultSubpartition数组,ResultSubpartition存储了该子分区的数据,以及读取的数据的视图

我们以实现类PipelinedSubpartition为例子

buffers是从ReusltPartition的LocalBufferPool申请到的内存,做了一些封装成BufferConsumer,BufferConsumer中存的是即将发送的序列化后的数据

buffersInBacklog的含义见注释

readView是用来读取数buffers中数据的

3.3 RecordWriter

flink一个任务中可能有多个连接在一起的算子,最后一个算子的输出依赖RecordWriter

RecordWriter是一个抽象类,它的属性是如下图,可以看到有一个属性targetPartition,对应类是ResultPartitionWriter,而ResultPartitionWriter的实现类就是ReusltPartition

RecordWriter类的作用是数据的输出

RecordWriter有两个实现类,ChannelSelectorRecordWriter用于将数据写入特定子分区,BroadcastRecordWriter用于广播

我这里分析ChannelSelectorRecordWriter

RecordWriter中最重要的是emit方法 ,将元素进行发送,代码如下:

在如下类中:

1. getBufferBuilder获取了ResultSubpartition中buffers队列中可写入的buffer,并把序列化后的数据copy拷贝其中

2. 如果内存池中申请的内存块用完了,还会通过requestNewBufferBuilder方法,在其中从LocalBufferPool申请新的内存,申请到的内存会加入对应的ResultSubpartition中的buffers队列中,并且也会保存其引用在ChannelSelectorRecordWriter的bufferBuilders中,方便后续使用

四、数据的读取源码分析

4.1 InputGate

InputGate它管理多个 InputChannel(输入通道),协调数据从生产者任务(如 Map)到消费者任务(如 Reduce)的传输过程。

我们这里分析它的实现类SingleInputGate

一些属性如上所示

inputChannelsWithData存储的是有数据的channel

consumedSubpartitionIndex是所有的subpartition数量

numberOfInputChannels是channel的数量

enqueuedInputChannelsWithData表示已进入inputChannelsWithData中的channel,避免inputChannelsWithData中有两个一样的channel

BufferPool实现类依旧是LocalBufferPool,和ResultPartition中的一样,主要用于存储算子网络间交互数据,在该类初始化时,除了申请bufferPool,还会创建与上游各个ResultSubpartition的视图,

视图的作用是读取ResultSubpartition中的数据,以及提醒SingleInputGate有数据了,让SingleInputGate把有数据的channel放入inputChannelsWithData中

SingleInputGate对数据的获取通过waitAndGetNextData方法,具体如下图所示:

通过getChannel获取有数据的channel,如果没有且block为true则会阻塞

获取到数据后,会进行封装返回

应该是有一个方法在持续不断的调用waitAndGetNextData,来获取数据

4.2 InputeChannel

主要负责数据的获取

4.2.1 LocalInputChannel

看名字也知道,LocalInputChannel主要负责TM内算子的交互,具体代码如下图所示:

先获取视图,然后调用视图的getNextBuffer方法,视图的getNextBuffer方法调用ResultSubpartition的pollBuffer,ResultSubpartition返回buffers.peek()值,可以看出来,算子内是没有反压的,只有方法之间的相互调用

4.2.2 RemoteInputChannel

RemoteInputChannel主要负责TM之间的数据交互

收到的数据放在receivedBuffers中

bufferQueue代表的是可用内存,包括独占内存和浮动内存,RemoteInputChannel初始化时独占内存会从NetworkBufferPool中申请,数量为numberOfSegmentsToRequest

在NetworkBufferPool中,numberOfSegmentsToRequest的默认值由Flink的配置参数NettyShuffleEnvironmentOptions.NETWORK_EXCLUSIVE_BUFFERS_PER_REQUEST决定,默认值为2。该参数表示每次请求独占内存段的数量。

浮动内存是当bufferQueue中内存不够用时,小于 初始可用内存+生产者没法送的内存,就会从InputGate中去申请

收到数据代码如下:

首先requestBuffer,然后把请求内容拷贝到bufferQueue中,调用channel的onbuffer处理

然后把buffer放入receivedBuffers中,并调用notifyChannelNonEmpty,提示SingleInputGate去读取数据

SingleInputGate收到数据后,通过视图,调用RemoteInputChannel的getNextBuffer读取数据,可以看到,是从receivedBuffers取出buffer,进行封装后返回

五、反压机制源码分析

5.1 基本思路

开始时,下游给上游初始化一个credit

上游每次向下游发送数据,该credit会减1,并且发送的数据中会附带自己的backlog(即还剩多少个buffer没发出去),如果credit减到0,上游将不会再将下游发送数据

下游收到backlog后,计算自己的可用buffer和上游需要的backlog buffer哪个大,如果上游需要的大,从inputGate中申请buffer

然后下游把自己的新的credit发送给上游,上游更新credit

5.2 信用值发送具体实现

PipelinedSubpartition写入数据时会调用notifyDataAvailable()

视图的notifyDataAvailable会调用CreditBasedSequenceNumberingViewReader类的notifyDataAvailable()方法,然后会调用userEventTriggered,最终会调用如下方法

上图中的getNextBuffer进行发送时,会将信用值减少1

最后通过BufferResponse发送数据时,将backlog(剩余多少缓存未发出)等进行发送

5.3 信用值回传具体实现

InputChannel收到数据backlog,计算自己的可用buffer和上游需要的backlog buffer哪个大,如果上游需要的大,从inputGate中申请buffer

然后把新的credit回传

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值