初始Flink

1. 什么是Flink?

        Flink是一个框架和分布式处理引擎,用于在无边界和有边界数据流上进行有状态的计算。能以内存速度和任意规模进行计算。

  • 分布式:存储或计算交由多台服务器完成,最后汇总起来达到最终的效果;
  • 实时:处理速度是毫秒级或者秒级的;
  • 计算:对数据进行处理,比如清洗数据(对数据进行规整,取出有用的数据)。

Flink优于Storm的地方有哪些呢?

1.1 有边界和无边界?

像MQ这种没有做任何处理的消息(来一条,处理一条),默认就是无边界的;

无边界的基础上加上条件,那就是有边界的。

比如要做数据统计:每个小时的pv(page view)是多少,那就设置1小时的边界,攒着一小时的数据来处理一次。

Flink上,设置“边界”这种操作叫做开窗口(Windows),窗口可简单分为两种类型:

  • 时间窗口(TimeWindows):按照时间窗口进行聚合,累计一个小时的数据处理一次。
  • 计数窗口(CountWindows):按照指定的条数来进行聚合,如: 每来10条数据处理一次。

        Flink使用窗口聚合时,指定”时间语义“来保证数据的准确性。可以指定聚合的时间以Event Time(事件发生的时间--日志真正记录的时间)来进行处理;不指定时默认是Processing Time(数据到Flink的时间)来进行聚合处理。

        虽然指定了聚合的时间为Event Time,但还是没解决数据乱序的问题(比如06分产生了5条数据,实际上06分只收到了3条,而剩下的2条在07分才收到,那此时怎么办呢?在06分时该不该聚合,07分收到的两条06分数据怎么办?)

   Flink提出设置水位线(waterMarks),即存在网络延迟等情况导致数据接收不是有序的,可根据实际情况,设置一个延迟时间(如1min),等延迟的时间到了,再统一聚合。

        因为设置了Event Time,所以Flink可以检测到每一条记录发生的时间,而waterMarks设置延迟一分钟,等到Flink发现07分:59秒的数据来到了Flink,那就确信06分的数据都来了(因为设置了1分钟延迟),此时才聚合06分的窗口数据。

1.2 有状态和无状态?

无状态:每次的执行都不依赖上一次或上N次的执行结果,每次的执行都是独立的。

有状态:某次的执行需要依赖前面事件的处理结果。

一个例子:要统计文章的阅读PV(page view),现在只要有一个点击了文章,在Kafka就会有一条消息。现在要在流式处理平台上进行统计,那此时是有状态的还是无状态的?

         可以依赖Flink的“存储”,将每次的处理结果交由Flink管理,执行计算的逻辑。可以简单的认为:Flink本身提供了”存储“的功能,而每次执行是可以依赖Flink的”存储”的,所以它是有状态的。

 1.3 Flink有状态数据的存储

  • MemoryStateBackend  内存
  • FsStateBackend  文件系统(HDFS)
  • RocksDBStateBackend  本地数据库(RocksDB数据库)

2. 精确一次性(exactly once)

    Flink遇到意外事件挂了以后,有什么机制来尽可能保证处理数据不重复和不丢失呢?

        假设Flink挂了,可能内存的数据没了,磁盘可能存储了部分的数据,那再重启的时候(比如MQ会重新拉取),就不怕会丢了或多了数据吗?

流的语义性有三种:

  • 精确一次性(exactly once):有且只有一条,不多不少
  • 至少一次(at least once):最少会有一条,只多不少
  • 最多一次(at most once):最多只有一条,可能会没有

    Flink有一个比较出名的特性:精确一次性,其指的是:状态只持久化一次到最终的存储介质中(本地数据库/HDFS...)。计算的数据可能会重复(无法避免),但状态在存储介质上只会存储一次!!!

  • 数据源需要可回放,发证故障可以重新读取未确认的数据;
  • Flink需要把数据存到磁盘介质(不能用内存),发生故障可以恢复;
  • 发送源需要支持事务(从读到写需要事务的支持保证中途不失败)。

2.1 一个例子

Source数据流[21,13,8,5,3,2,1,1],需在Flink累加操作(求和)。

Step.1:  处理完2,1,1,累加值是4,现在Flink把累积后的状态4已经存储起来了(认为前面2,1,1这几个数字已经完全处理过了)。

Step.2: 处理了[5,3],现在累加值是12,但Flink还没来得及把12存储到最终的介质,系统就挂掉了。

Step.3:  Flink重启后会重新把系统恢复到累加值是4的状态,所以[5,3]得继续计算一遍,程序继续往下走。

2.2 CheckPoint容错机制

         CheckPoint其实就是Flink在指定的时间段上保存状态的信息,假设Flink挂了可以将上一次状态信息再捞出来,重放还没保存的数据来执行计算,最终实现exactly once。其中Flink多长时间存储一次是由自己手动配置的。

 1. 在Kafka在业务上实现“至少一次”是怎么做的?

        从Kafka把数据拉下来,处理完业务之后,手动提交offset (告诉Kafka已经处理完了)做完了业务规则才将offset进行commit的。

 2. CheckPonit是怎么办到的呢?

    与Kafka机制一样,等拉下来该条数据所有的流程走完,才进行真正的checkponit

checkpoint是怎么知道拉下来的数据已经走完了呢?

    Flink在流处理过程中插入了barrier,每个环节处理到barrier都会上报(给JobManager),等到sink都上报了barrier,就说明这次checkpoint已经走完了,即JobManager 就去完成一次checkpoint。

注意:Flink实现的精确一次性只是保证内部的状态是精确一次的,如果想要端到端精确一次,需要端的支持。

3. Flink 作业流程

  1.  Flink根据提交代码,生成一个StreamGraph图,来代表程序的拓扑结构;

  2.  在提交前会对StreamGraph进行优化(可以合并的任务进行合并),变成JobGraph;

  3.  将JobGraph提交给JobManager;

  4.  JobManager根据JobGraph生成ExecutionGraph(JobGraph 的并行化版本);

  5.  TaskManager接收到任务之后会将ExecutionGraph生成为真正的物理执行图。

    物理执行图真正运行在TaskManagerTransformSink之间都会有ResultPartition(用来发送数据)和InputGate(接收数据)两个组件。

         屏蔽掉这些Graph,发现Flink的架构是:

 checkpoint是由JobManager发出。

    checkpointFlink实现容错机制的关键,在实际使用中往往要进行相关的配置,如下:

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(5000);
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
env.getCheckpointConfig().setCheckpointTimeout(60000);
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

4. 流程模块拆解

4.1 Checkpoint触发

        JobManager会启动一个定时任务,触发 triggerCheckpoint()方法。

  1. 前置检查(是否可以触发checkpoint,距离上一次checkpoint的间隔时间是否符合...);

  2. 检查是否所有需要做checkpoint的Task都处于running状态;

  3. 生成checkpointIdPendingCheckpoint对象来代表待处理的检查点;

  4. 注册一个定时任务,如果checkpoint超时后取消checkpoint。

注意:检查task的任务状态时,只会把sourcetask封装给进Execution[]数组,

JobManager侧只会给sourcetask发送checkpoint。

4.2 JobManager发送

    JobManager 收到Client提交的JobGraph,然后根据该JobGraph生成ExecutionGraph,这个过程中会触发checkpoint的逻辑。

  1. 定时任务会进行前置检查(检查配置的各种参数是否符合);

  2. 判断checkpoint相关的task是否都是running状态,将source的任务封装到Execution数组中;

  3. 创建checkpointID、checkpointStorageLocation(checkpoint保存的地方)、PendingCheckpoint(待处理的checkpoint);

  4. 创建定时任务(如果checkpoint超时,会将相关状态清除,重新触发);

  5. 真正触发checkPointTaskManager(只会发给sourcetask);

  6. 找出所有source和需要ack的Task;

  7. 创建checkpointCoordinator 协调器;

  8. 创建CheckpointCoordinatorDeActivator监听器,监听Job状态的变更;

  9. Job启动时,会触发ScheduledTrigger 定时任务。

4.3 TaskManager接收

4.3.1 source Task接收

        JobManager 在生成ExcutionGraph时,会给所有的source 任务发送checkpoint,而source收到barrier后会交由TaskExecutor进行处理。Source任务接收到Checkpoint会广播到下游,进行快照处理。

4.3.2 非source Task接收

Flink接收数据用的是InputGate,其包括两个类BarrierTracker、BarrierBuffer

  • BarrierTrackerat-least-once模式,只要inputChannel接收到barrier,就直接通知完成处理checkpoint;

  • BarrierBufferexactly-once模式,当所有的inputChannel接收到barrier才通知完成处理checkpoint,如果有的inputChannel还没接收到barrier,那已接收到barrierinputChannel会读数据到缓存中,直到所有的inputChannel都接收到barrier,这有可能会造成反压。即BarrierBuffer会有对齐barrier的处理。

一个例子:

        有一个包含有两个partition的Topic,现在要拉取Kafka这两个分区的数据,由算子Map进行消费转换,期间在转化的时候可能会存储信息到State,最终输出到Sink

        在Flinkcheckpoint的时候JobManager往每个Source任务(图中两个paritiion) 发送checkpointId,然后做快照存储。其中Source任务存储最主要的内容就是消费分区的offset。比如现在source1offerset=100source2offset=105

         假设source2的数据会比source1先到达Map且使用的是BarrierBuffer(exactly-once模式),那么source2barrier到达Map算子后,source2之后的数据只能停下来,放到buffer上,等source1barrier到达之后,再真正处理source2放在buffer的数据。即barrier对齐。

        假设使用的是BarrierTracker(at-least-once模式),那么source2barrier到达Map算子后,source2后面的数据会继续处理,而非停下来等待source1

         无论是BarrierTracker还是BarrierBuffer此时Checkpoint都没做(source1barrier还没到sink端),这时如果Flink挂了,重启之后会重新拉取数据(source1offerset<100source2offset<105),State的最终信息不会保存,对数据不会产生任何的影响。

        但如果使用的是BarrierTracker (at-least-once)模式,没有任何问题,程序继续执行。等到source1barrier也走到了slink,最后完成了一次checkpoint

        由于source2barriersource1barrier要快,那么source1所处理的State的数据实际是包括offset>105的数据的,自然Flink保存的时候也会把这部分保存进去。程序继续运行,刚好保存完checkpoint后,此时系统出了问题,挂了。因为checkpoint已经做完了,所以Flink会从source1offerset=100,而source2offset=105重新消费。

但是,由于使用的是 at-least-once模式,所以State里边的保存状态实际上有过source2offset>105 的记录了。那source2重新从offset=105开始消费,就会重复消费!

4.3.3 TaskManager总结

    TaskExecutor接收到JobManager下发的checkpoint,会触发triggerCheckpoint()调用performCheckpoint()对checkpoint做前置处理,barrier广播到下游,处理State状态做快照,最后返回成功消息给JobManager。

    Source和普通算子最终处理checkpoint的逻辑是一致的,只是会source会直接通过TaskExecutor处理,而普通算子会根据不同的配置交由不同的实例(BarrierTrackerBarrierBuffer)处理。

4.4 JobManager接收回应

        无论是source还是普通算子,都会调用performCheckpoint()进行处理。

5. Flink 反压原理

5.1 一个例子

         从各个数据源借助Flink清洗出数据,组装成一个宽模型,最后交由kylin做近实时数据统计和展示,供运营实时查看。

        迁移过程中,发现订单的topic消费延迟了好久,初步怀疑是因为订单上游的并发度不够所影响的,调整了两端的并行度重新发布,系统起来后,topic 消费延迟丝毫没有下降。排查发现:checkpoint一直没做上,都堵住了,重新发布时只会在上一次checkpoint开始,由于checkpoint长时间没完成掉,所以重新发布数据量会很大。只能在这个堵住的环节下扔掉吧,估计是业务逻辑出了问题。

        接收到订单的数据,会去溯源点击,判断该订单从哪个业务来,经过了哪些的业务,最终是哪块业务致使该订单成交。外部真正使用时,依赖「订单结果HBase」数据。

        有可能点击数据会比订单数据处理要慢一些,而找不到的数据会间隔一段时间轮询,又因为Flink提供状态Statecheckpoint机制,所以把找不到的数据放入ListState按一定的时间轮询就好了(即便系统由于重启或其他原因挂了,也不会把数据丢了)。

        但订单数据报来了之后,一小批量数据一直在「订单结果HBase」没找到数据,就放置到ListState上,然后来一条数据就去遍历ListState,这样会导致:

  • 数据消费不过来,形成反压;

  • checkpoint一直没成功

        当时处理的方式就是把ListState清空掉,暂时丢掉这一部分的数据,让数据追上进度。后来排查发现是上游在消息报字段上做了「手脚」,解析失败导致点击丢失,造成这一连锁的后果。

5.2  反压 backpressure

        反压意味着数据管道中某个节点成为瓶颈,处理速率跟不上上游发送数据的速率,上游需要进行限速,是流式计算中很常见的问题。

下游是怎么通知上游要发慢点的呢?

        而Flink在一个TaskManager内部读写数据的时候,会有一个BufferPool(缓冲池)供该TaskManager读写使用(一个TaskManager共用一个BufferPool),每个读写ResultPartition(用来发送数据)/InputGate(用来接收数据)都会去申请自己的LocalBuffer。

5.2.1 一个TaskManager情况下的反压

        假设下游处理不过来,InputGateLocalBuffer被填满,那ResultPartition就没办法往InputGate发了,此时ResultPartition本身的LocalBuffer 也迟早会被填满,一直到Source就不会拉数据了...

5.2.2 多个TaskManager情况下的反压

Flink通信的总体数据流向架构图:

可以发现:

        远程通信用的Netty,底层是TCP Socket来实现的。从宏观的角度看,多个TaskManager只不过多了两个Buffer(缓冲区)。

        只要InputGateLocalBuffer被打满,Netty Buffer也迟早被打满,而Socket Buffer同样也会被打满(TCP 本身就带有流量控制),再反馈到ResultPartition上,数据又发不出去了,这就导致整条数据链路都存在反压的现象。

        一个TaskManagertask可是有很多的,它们都共用一个TCP Buffer/Buffer Pool,那只要其中一个task的链路存在问题,那整个TaskManager跟着遭殃。

5.3 Credit机制

        为解决以上问题,Flink1.5版本之后引入了credit机制。以上Flink所实现的反压,宏观上就是直接依赖各个Buffer是否满了,如果满了则无法写入/读取导致连锁反应,直至Source端。而credit机制,实际上可以理解为以"更细粒度"去做流量控制:

1.  每次InputGate(接收端)会告诉ResultPartition(发送端)自己还有多少的空闲量可以接收,让ResultPartition看着发;

2.  如果InputGate已经没有空闲量了,那ResultPartition就不发了。

6. Flink 背压原理

        下游的处理速度跟不上上游的发送速度,从而降低了整体处理速度。在Flink里,背压再加上Checkponit机制,很有可能导致State状态一直变大,拖慢完成checkpoint速度甚至超时失败。当checkpoint处理速度延迟时,会加剧背压的情况(很可能大多数时间都在处理checkpoint了)。

一个例子:

一个Flink任务,只有一台TaskManager去执行任务,在更新DB时发现会有并发的问题。

        排查发现:更新DB的 Sink 并行度调高了,若并行度设置为1,没有并发问题,但处理速度太慢。然后在Sink之前根据userId进行keyBy(相同的userId都由同一个Thread处理,这样就没并发的问题了)。但如果userId存在热点数据问题,会导致下游数据处理形成反压。原本一次checkpoint执行只需要30~40ms反压后一次checkpoint需要2min+

     checkpoint执行间隔相对频繁(6s/次),执行时间2min+,最终导致数据一直处理不过来,整条链路的消费速度从原来的3000qps到背压后的300qps,一直堵住(程序没问题,就是处理速度大大下降,影响到数据的最终产出)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值