一、jobmanage
JobManager负责接收 flink 的作业,调度 task,收集 job 的状态、管理 TaskManagers。jobmanage启动,再启动task。
二、taskmanage
所有执行任务的基本容器,提供了内存管理、IO管理、通信管理等。
将所有对象序列化后放在自己的MemorySegment上进行管理。IOManager flink通过IOManager管理磁盘IO的过程,提供了同步和异步两种写模式。NetworkEnvironment 是TaskManager的网络 IO 组件,包含了追踪中间结果和数据交换的数据结构。执行task就是把收到的TaskDeploymentDescriptor
对象转换成一个task并执行的过程。输入的InputGate和输出的ResultPartition的定义,该task要作为几个subtask执行。
invokable.invoke();
方法。为什么这么说呢,因为这个方法就是用户代码所真正被执行的入口。
三、为执行保驾护航——Fault Tolerant与保证Exactly-Once语义
离线任务,如果失败了只需要清空已有结果,重新跑一次就可以了。对于流任务,如果要保证能够重新处理已处理过的数据,就要把数据保存下来;而这就面临着几个问题:比如一是保存多久的数据?二是重复计算的数据应该怎么处理,怎么保证幂等性?
分布式快照应运而生,快速记录下来当前的operator的状态、当前正在处理的元素。当整个程序的最后一个算子sink都收到了这个barrier,也就意味着这个barrier和上个barrier之间所夹杂的这批元素已经全部落袋为安。这时,最后一个算子通知JobManager整个流程已经完成,而JobManager随后发出通知,要求所有算子删除本次快照内容,以完成清理。这整个部分,就是Flink的两阶段提交的checkpoint过程。
要完成一次checkpoint,第一步必然是发起checkpoint请求。那么,这个请求是哪里发出的,怎么发出的,又由谁控制呢?CheckpointCoordinator
四、保存barrirs的State、StateBackend
State分为 KeyedState和OperatorState
StateBackend目前提供了三个backend,MemoryStateBackend,FsStateBackend,RocksDBStateBackend
五、数据交换
MemorySegment
就是Flink的内存抽象。默认情况下,一个MemorySegment可以被看做是一个32kb大的内存块的抽象。NetworkBuffer
,是对MemorySegment
的包装。Flink在各个TaskManager之间传递数据时,使用的是这一层的抽象。
最底层内存抽象是MemorySegment,用于数据传输的是Buffer,那么,承上启下对接从Java对象转为Buffer的中间对象是什么呢?是StreamRecord
。
六、数据在task之间交换
数据在各个task之间exchange的过程。
- 第一步必然是准备一个ResultPartition;
- 通知JobMaster;
- JobMaster通知下游节点;如果下游节点尚未部署,则部署之;
- 下游节点向上游请求数据
- 开始传输数据
数据在task之间传递有如下几步:
- 数据在本operator处理完后,交给
RecordWriter
。每条记录都要选择一个下游节点,所以要经过ChannelSelector
。 - 每个channel都有一个serializer(我认为这应该是为了避免多线程写的麻烦),把这条Record序列化为ByteBuffer
- 接下来数据被写入ResultPartition下的各个subPartition里,此时该数据已经存入DirectBuffer(MemorySegment)
- 单独的线程控制数据的flush速度,一旦触发flush,则通过Netty的nio通道向对端写入
- 对端的netty client接收到数据,decode出来,把数据拷贝到buffer里,然后通知
InputChannel
- 有可用的数据时,下游算子从阻塞醒来,从InputChannel取出buffer,再解序列化成record,交给算子执行用户代码
背压问题
Flink来说,就是在数据的接收端和发送端放置了缓存池,用以缓冲数据,并且设置闸门阻止数据向下流。
当数据发送太多,下游处理不过来了,那么首先InputChannel会被填满,然后是InputChannel能申请到的内存达到最大,于是下游停止读取数据,上游负责发送数据的nettyServer会得到响应,停止从ResultSubPartition读取缓存,那么ResultPartition很快也将存满数据不能被消费,从而生产数据的逻辑被阻塞在获取新buffer上,非常自然地形成背压的效果。