Flink的ExactlyOnce语义与Checkpoint的实现

    本文主要是想了解下Flink如何实现Exactly_Once语义以及它的CheckPoint机制。

 

消息发送三种语义介绍:

我们在一般的流处理程序中,消息发送会有三种处理语义:

At_Most_Once:

    至多一次,表示一条消息不管后续处理成功与否只会被消费处理一次

At_Least_Once:

    至少一次,表示一条消息从消费到后续的处理成功,可能会发生多次

Exactly_Once:

    精确一次,表示一条消息从其消费到后续的处理成功,只会发生一次

    Flink声称实现了Exactly_Once语义,实际上在事件处理过程中,各种异常情况都有可能发生,所以根本不可能保证每条消息真的真会被处理一次。Flink的Exactly_Once真正的含义在于可以保证Flink状态的容灾和只向后端提交一次持久存储(要求后端支持事务,例如Kafka、MySQL)。

 

Flink如何实现Exactly_Once语义的:

Flink通过以下特性实现Exactly_Once:

  1. Source支持数据重读
  2. Sink支持事务。可以是类似二阶段提交,如kafka,或者Sink支持幂等,可以覆盖之前写入的数据,如redis
  3. 基于Checkpoint保证状态的容灾及一致性

    第一点和第三点需要外部系统配合实现,Flink内部主要通过Checkpoint实现Exactly_Once语义,下文主要是对Flink的Checkpoint机制进行说明,Kafka的幂等性之列的后续单独写篇文章分析下。

 

Flink的Checkpoint机制:

    Checkpoint是Flink实现容错机制最核心的功能,它会根据用户的配置周期性地对流中各个算子(Operator)的状态生成快照,持久化到外部存储。Flink程序一旦意外崩溃时,重新运行程序时可以有选择地从这些快照进行恢复,从而修正因为故障带来的程序数据异常。Flink写入到外部存储是异步的,意味着Flink在这个阶段可以继续处理数据。

Checkpoint任务的注册:

    Checkpoint功能是需要显示启用。从源码上看,Checkpoint任务是在ExecutionGraph(JobManager端)中的enableCheckpointing()方法中注册的(Flink任务是一个图的结构,从最初的StreamGraph(算子逻辑视图) –> JobGraph(chaining) -> ExecutionGraph(并行执行逻辑视图) -> 物理执行图)。并且只有job处于running状态,checkpoint任务才会执行

 

从代码上看,它确实是一个定时触发的任务:

    执行CheckPoint任务的类叫做CheckpointCoordinator。

    看了下SchedulerTrigger类中方法,在checkpoint之前首要进行pre-check,比如是否满足最小时间间隔、是否满足只有一定数量的checkpoint在并行执行之类的,每次发起Checkpoint都会生成一个 PendingCheckpoint(表示已经发起但尚未ack的Checkpoint),然后在看下所有的tasks是否都在running状态,还有其他一些列操作…有点细了,直接跳到核心的触发的地方:

 

Checkpoint任务的执行:

代码最终跳到了TaskExecutor类中这里:

    task.triggerCheckpointBarrier(checkpointId, checkpointTimestamp, checkpointOptions, advanceToEndOfEventTime);

    这个TaskExecutor是啥呀…就是TaskManager看起来像是单个Task任务执行的类,,它下面就跳到了Task类中的triggerCheckpointBarrier(),这个CheckpointBarrier是实现checkpoint的关键。从代码的注释中可以看出…其实从代码中已经看出了,checkpoint操作是异步的:

    SourceStreamTask和StreamTask调用的是相同的方法,不同的是SourceStreamTask在所有任务之后会有个返回,报告本次checkpoint任务是否全部执行成功。所以我们只要关注执行checkpoint的流程即可,注释已经说明的很详细了:

    step1这个方法注释罗里吧嗦一大堆,点进去一看啥都么有…贼秀。

    step2是先把一条CheckPointBarrier记录broadcast到下游流,以便让下游也尽快开始checkpoint。也就是说一个CheckPoint操作,Job中所有的算子的状态都根据同一条CheckPointBarrier记录来进行同步,这样就保证了状态的一致性(下面会细说下)。CheckPointBarrier长这样:

    CheckpointBarrier barrier = new CheckpointBarrier(id, timestamp, checkpointOptions);

    step3就是真正进行checkpoint,里面方法很长,后面有时间再细看下…反正就是将状态持久化到后端的backend:

 

下游算子CheckPointBarrier对齐:

    下游算子通过CheckpointedInputGate处理 CheckpointBarrier 消息(还有其他消息,暂时先不去了解),进行CheckpointBarrier对齐(这是实现Exactly_Once的核心)

    处理CheckpointBarrier的类有两种,前者代表了Exactly_Once语义,后者代表了At_least_Once语义,这里主要看前者。

 

CheckpointBarrierAligner中的代码很长就不贴了,流程主要如下:

    1. 如果只有一个input channel,收到的是一个新的barrier,则直接触发checkpoint。

    2. 如果有多个input channel,说明此时需要进行多条流进行对齐(connect算子)

        收到一条流的CheckPointBarrier之后,就要block对应的channel,并更新CurrentCheckpointId。

        如果发现收到的CheckPointBarrier比CurrentCheckpointId大,就会unblock所有的channel,并开启一个新的checkpoint

    3.当所有流的CheckPointBarrier都收到了,说明完成了对齐,unblock所有channel,触发checkpoint。

 

    ps: At Least Once就是barrier 不对齐的情况,即还有其他流的 barrier 还没到达时,为了不影响性能,不会Block Input channel大数据,继续处理 barrier 之后的数据。等到所有流的 barrier 的都到达后,就可以对该 Operator 做 CheckPoint 了:

    但是如果此时继续消费的过程中程序挂了,那么就会从上次CheckPoint中恢复,这样就有可能重复消费没有Block的流中的数据,所以是At Least Once。

 

总结:

    总结下就是CheckPoint是个注册在JobManager上的定时任务,根据用户的配置定时异步的在TaskManager上执行。在TaskManager上执行时,首先生成一条CheckpointBarrier记录,将其BroadCast到下游流中,同时会执行CheckPoint任务,将State信息持久化到后端存储中。下游流收到CheckPointBarrier会阻塞输入(Exactly_Once语义时),如果下游有多条流的还会进行Barrier对齐,然后再执行上面的讲State信息持久化到后端存储这个动作。

    抄一下网上一张经典的图:

image_1ceot7q13apu1a04170af7j1jao34.png-66.6kB

 

 

CheckPoint的配置项:

CheckPoint相关的配置项如下:
    //设置间隔60s触发一次Checkpoint
        ENV.enableCheckpointing(60 * 1000);
    //设置Checkpoint级别(语义)
        ENV.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
    //设置Checkpoint超时时间,超时则abort当前的Checkpoint任务,开始下一个Checkpoint -- 默认是10min
        ENV.getCheckpointConfig().setCheckpointTimeout(30 * 1000);
    //设置Checkpoint出错次数以停止掉Job,默认为0   -- 替代了setFailOnCheckpointingErrors这个配置
        ENV.getCheckpointConfig().setTolerableCheckpointFailureNumber(0);
    //设置Checkpoint之间的间隔 -- 用于指定CheckPoint Coordinator上一个CheckPoint完成之后最少等多久可以触发另一个CheckPoint
    //默认是0,表示可以立即触发下一个  个人觉得调解这个参数还不如调解CheckpointInterval好一点
    //这个是出现CheckPoint排队的情况,从而程序一直执行CheckPoint,这个治标不治本
    //checkpoint间隔应大于这个参数,否则可能一直排队
        ENV.getCheckpointConfig().setMinPauseBetweenCheckpoints(30 * 1000);
    //设置并行的Checkpoint的数量 -- 指定运行中的CheckPoint最多可以有多少个 -- 上面那个参数指定了,这个参数就不起作用了,会使用值1
        ENV.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
    //设置开启Checkpoint外部持久化(即使job失败Checkpoint也会存在,job失败了不会自动清理,需要手工清理了)
    //ExternalizedCheckpointCleanup用于指定job cancelde的时候外部持久化的CheckPoint该如何清理,当前设置的是保留
        ENV.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.

RETAIN_ON_CANCELLATION);
    //设置checkpoint地址
        ENV.setStateBackend(new FsStateBackend("hdfs://test1:8020/flink-checkpoint/"));
    //设置重启策略(job失败后重启3次,每次间隔0.5秒)
        ENV.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, 500));

    //todo 过期的CheckPoint需要手动清理,而且需要判断CheckPoint之间是否有依赖关系

 

PS:Kafka幂等性和事务

    Kafka在0.11版本中除了引入了Exactly Once语义,还引入了事务特性。

    幂等性是指对接口的多次调用产生的结果和调用一次是一致的,在Kafka中生产者在重试的时候有可能重复写入消息,而Kafka的幂等性功能就可以避免这种情况,但是幂等性不能扩多个分区运作

    Kafka的事务可以弥补这个缺陷,事务可以保证多个分区写入操作的原子性,级多个操作要么全部成功,要么全部失败。

   后续有时间再细看源码吧…

 

 

 

参考:

    https://blog.csdn.net/u012871914/article/details/85519663(流式处理术语解释 Exactly-once与Effectively-once)

    http://flink.apache.org/features/2018/03/01/end-to-end-exactly-once-apache-flink.html

    https://blog.csdn.net/leileibest_437147623/article/details/102773556(Flink Graph)

    https://blog.csdn.net/haibucuoba/article/details/96926090(Barrier介绍)

    https://blog.csdn.net/yuchuanchen/article/details/106123588(Flink checkpoint分析)

    https://zhuanlan.zhihu.com/p/92867784 (ABS算法在Flink中的实现)

    https://www.jianshu.com/p/165a1bf33e4a(CheckPoint需要手动清理)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值