一、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执行。

  1. 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的过程。

  1. 第一步必然是准备一个ResultPartition;
  2. 通知JobMaster;
  3. JobMaster通知下游节点;如果下游节点尚未部署,则部署之;
  4. 下游节点向上游请求数据
  5. 开始传输数据

数据在task之间传递有如下几步:

  1. 数据在本operator处理完后,交给RecordWriter。每条记录都要选择一个下游节点,所以要经过ChannelSelector
  2. 每个channel都有一个serializer(我认为这应该是为了避免多线程写的麻烦),把这条Record序列化为ByteBuffer
  3. 接下来数据被写入ResultPartition下的各个subPartition里,此时该数据已经存入DirectBuffer(MemorySegment)
  4. 单独的线程控制数据的flush速度,一旦触发flush,则通过Netty的nio通道向对端写入
  5. 对端的netty client接收到数据,decode出来,把数据拷贝到buffer里,然后通知InputChannel
  6. 有可用的数据时,下游算子从阻塞醒来,从InputChannel取出buffer,再解序列化成record,交给算子执行用户代码

背压问题

  Flink来说,就是在数据的接收端和发送端放置了缓存池,用以缓冲数据,并且设置闸门阻止数据向下流。

  当数据发送太多,下游处理不过来了,那么首先InputChannel会被填满,然后是InputChannel能申请到的内存达到最大,于是下游停止读取数据,上游负责发送数据的nettyServer会得到响应,停止从ResultSubPartition读取缓存,那么ResultPartition很快也将存满数据不能被消费,从而生产数据的逻辑被阻塞在获取新buffer上,非常自然地形成背压的效果。