【Flink】【第9章】【 状态管理和容错机制】【02】CheckPoint和 状态后端

1.状态的备份(checkpoint)

  • flink程序恢复是否能够存档,指的就是是否开启checkpoint
  • 程序恢复就是从上一次checkpoint成功的数据

1.Checkpoint是什么

概念

  • 所谓checkpoint,就是在某一时刻,将所有task的状态做一个快照,然后存储到State Backend
  • 是一种周期性绘制数据流状态的机制,该机制确保即使job出现故障,程序的状态最终也将为数据流中的每一条记录提供exactly once的语义保证(只能保证flink系统内部,对于source和sink需要依赖外部组件的一同性保证)
  • 全局快照,持久化保存所有task/operator的state
  • 序列化数据集合

特点

轻量级容错机制

  • 可异步执行 (轻量级:checkpoint 不会耽误job的执行)
  • 全量 vs 增量 (轻量级:可增量保存,只有RocksDB数据库可以用增量)
  • job失败情况可以回滚到最近一次成功的checkpoint(容错:自动)
  • 周期性,无需人工干预

注意事项

  • exactly once语义可配置
  • state 和 checkpoint是两个概念,不要混淆

2.Checkpoint 学习路线

  1. 开启ck 并配置 【怎么存】
  2. 开启重启恢复策略 【怎么恢复】
  3. 设置checkpoint保存路径 【存到哪】
  4. 原理

3. 图解一致性检查点 (怎么存的)

在这里插入图片描述
如图所示,数据流是:1 2 3 4 5 6 7 ……
假设当前checkpoint的设定的是处理完5后,将所有算子状态进行保存:

  • source处理完5后,source的状态保存为5
  • sum_even处理完5后,状态保存为6(2+4)
  • sum_odd处理完5后,状态保存为9(1+3+5)

可以知道,流处理过程中,数据流到算子是有前后时间顺序的,假设上游算子A处理完5后保存了状态,下游算子B还未处理5,此时job挂掉了,那么整个Checkpoint就是失败的,而checkpoint过程也是一个事务,如果ck失败就会回滚;

4. 从检查点恢复状态 (怎么恢复的)

(1)假设在处理7的时候,sum_odd任务挂掉了

在这里插入图片描述
(2)从检查点恢复状态

在这里插入图片描述
source可以恢复偏移量,上游重新消费;
在这里插入图片描述
从检查点恢复状态总结:
1.当flink遇到故障,首先就是重启应用,重启应用的时候,所有的Task的状态会重置为初始值;
2.然后Flink会使用最近的检查点来恢复应用程序中各个Task的状态,重新启动处理流程。JobManager中会记录应用程序的拓扑结构,读取状态,恢复每个Task的状态,Source要根据自己保存的状态重新提交偏移量,从偏移量后面重新读取数据,保证了数据不丢失。
3. source开始消费,应用程序开始处理

5. Flink检查点算法(原理)

一致性检查点:所有任务处理完同一条数据后这一时刻的所有算子的状态;

如何实现所有任务处理完同一条数据后保存状态?
     Flink检查点算法的正式名称是异步分界线快照(asynchronous barrier snapshotting)。该算法大致基于Chandy-Lamport分布式快照算法。检查点是Flink最有价值的创新之一,因为它使Flink可以保证exactly-once,并且不需要牺牲性能。

检查点分界线:barrier

  • flink的检查点算法用到了一种称为分界线的特殊数据;
  • 分界线是有JobManager发起checkpoint命令的时候,往source中插入的一条特殊数据,可以将流上的数据按照不同的检查点分开;
  • 当subTask遇到barrier的时候,就会将当前状态保存
  • barrier和waterMark一样是广播传输的
    在这里插入图片描述
    在这里插入图片描述
    如果source是多并行度的,jobManager会往每一个并行度注入一个barrier;对于下游算子来说,必须所有并行度都接收到barrier之后才能做checkpoint,这叫做barrier对齐算法;
    在这里插入图片描述
    当sink operator的所有并行度都收到barrie后,sink 向checkpoint coordinator确认snapshot已经完成,此时快照在分布式情况下被标识为完成。

barrier对齐 - 精准一次

在这里插入图片描述

在这里插入图片描述

  • 精准一次指的是每条数据只被处理一次;
  • barrier的对齐机制,是保证精准一次的必要条件;到达了,就缓存起来不处理,没到达就继续处理;

在这里插入图片描述

在这里插入图片描述

总结

  • barrier 对齐 保证每条数据只被处理一次;
  • 缺点:每个subTask需要barrier对齐,才能将数据往下游发送,这会导致已经到齐的subTask的数据被缓存

barrier非对齐 - 至少一次

在这里插入图片描述

barrier对齐的缺点: 对于barrier已经到达的分区,继续到达的数据会被缓存,这样的话假如另一个分区的barrier迟迟不来,会导致内存膨胀;flink自带反压机制,当内存不足的时候,会通知前面不要再接收数据了。

flink11中引入了非barrier对齐: 对于barrier已经到达的分区,继续到达的数据会被正常计算处理;这就会导致如果任务挂了,这些barrier后的数据会被重新计算一次,这就导致了at least onece

在这里插入图片描述

6. 全量Checkpoint & 增量Checkpoint

全量Checkpoint

全量Checkpoint会在每个节点做备份数据时,只需要将数据都便利一遍,然后写到外部存储中,这种情况会影响备份性能。
在这里插入图片描述

增量Checkpoint

RockDB的增量Checkpoint;
RockDB的数据会更新到内存,当内存满时,会写入到磁盘中。增量的机制会将新产生的文件COPY持久化中,而之前产生的文件就不需要COPY到持久化中去了。通过这种方式减少COPY的数据量,并提高性能。

7. Checkpoint 使用配置(怎么用)

一个完整的checkpoint分为三步:

  1. 开启ck 并配置 【怎么存】
  2. 开启重启恢复策略 【怎么恢复】
  3. 设置checkpoint保存路径 【存到哪】

怎么存

package No10_Checkpoint;

import org.apache.flink.streaming.api.CheckpointingMode;
import org.apache.flink.streaming.api.environment.CheckpointConfig;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

public class _01_checkpoint的配置 {
    public static void main(String[] args) {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //todo 1.开启ck 每隔1000ms开启一次checkpoint  默认情况下,Checkpoint机制是关闭的
        env.enableCheckpointing(1000);
        //env.enableCheckpointing(1000, CheckpointingMode.EXACTLY_ONCE);
        //env.enableCheckpointing(1000,CheckpointingMode.AT_LEAST_ONCE);

        //todo 2.设置一致性语义  只有at-least-once和 at-exactly-once
        // 默认的Checkpoint配置是支持Exactly-Once投递的
        // 使用Exactly-Once就是进行了Checkpoint Barrier对齐,因此会有一定的延迟。
        // 如果要求作业延迟小,那么应该使用At-Least-Once投递,不进行对齐,但某些数据会被处理多次。
        // 想要端到端exactly-once,source和sink也要保证exactly-once
        env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.AT_LEAST_ONCE);

        //todo 3.设置两次checkpoint的最小时间间隔
        // ck1: 0ms   完成时间:700ms   本来还剩300ms 再次执行一次ck
        // 这个参数就是限制上一次ck结束时间 和 下一次ck开始时间的最小间隔
        // 也就是说本来下一次ck的启动时间是 1000ms,有了这个参数之后,ck2的启动时间 = 700ms + 500ms = 1200ms
        // 保证整个作业最多允许1个Checkpoint
        // 如果不设置这个参数,假设状态很大,完成时间比较长:ck1在1200ms完成的,但是ck2在1000ms就开启了,因此有两个checkpoint同时进行
        env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);


        //todo 4.如果一次Checkpoint超过一定时间仍未完成,直接将其终止,以免其占用太多资源
        env.getCheckpointConfig().setCheckpointTimeout(3600*1000);

        //todo 5.最大同时checkpoint个数
        // 默认情况下一个作业只允许1个Checkpoint执行
        // 如果某个Checkpoint正在进行,另外一个Checkpoint被启动,新的Checkpoint需要挂起等待。
        // 这个参数大于1,将与第3个配置 最短间隔相冲突。
        env.getCheckpointConfig().setMaxConcurrentCheckpoints(3);

        //todo 6.作业cancel 仍然保留checkpoint
        // 作业取消后仍然保存Checkpoint
        // Checkpoint的初衷是用来进行故障恢复,如果作业是因为异常而失败,Flink会保存远程存储上的数据;
        // 如果开发者自己取消了作业,远程存储上的数据都会被删除。如果开发者希望通过Checkpoint数据进行调试,自己取消了作业,同时希望将远程数据保存下来
        // 此模式下,用户需要自己手动删除远程存储上的Checkpoint数据。
        env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);


        //todo 7.默认情况下,如果Checkpoint过程失败,会导致整个应用重启,我们可以关闭这个功能,这样Checkpoint失败不影响作业的运行。
        // checkpoint 线程是异步的,ck失败了,job接着运行
        env.getCheckpointConfig().setFailOnCheckpointingErrors(false);


    }
}

用户可以根据的程序里面的配置将checkpoint打开,给定一个时间间隔后,框架会按照时间间隔给程序的状态进行备份。当发生故障时,Flink会将所有Task的状态一起恢复到Checkpoint的状态。从哪个位置开始重新执行。

  • 增量异步备份:不会因为备份影响流计算
  • 本地恢复:从本地存储的状态数据恢复

在这里插入图片描述

2.状态的存储(状态后端)

在这里插入图片描述

什么是状态后端?

state Backend 就是用来存储快照的地方
在CheckPoint机制中,持久化所有状态的一致性快照,这些状态包含:

  • 非用户定义的状态
  • 用户定义的状态(就是各种State)

1.MemoryStateBackend

在这里插入图片描述

  • MemoryStateBackend 内部将状态作为对象保存在taskManager进程的堆内存,通过checkpoint机制,MemoryStateBackend将状态(state)进行快照并保存Jobmanager(master)的堆内存中。
  • MemoryStateBackend 可以通过配置来使用异步快照(asynchronous snapshots)。通过异步快照可以避免阻塞管道(blocking pipelines),目前是默认开启,当然也可以通过MemoryStateBackend的构造函数配置进行关闭:

局限性

  • 状态的大小不能超过akka的framesize大小。参考:配置 ;
  • 聚合状态(aggregate state )必须放入JobManager的内存。

适用场景

  • 本地调试
  • flink任务状态数据量较小的场景

实战演示

  • 每个独立的状态(state)默认限制大小为5MB, 可以通过构造函数增加容量;
//new MemoryStateBackend(MAX_MEM_STATE_SIZE, false);
env.setStateBackend(new MemoryStateBackend(5242880,false));

额外说明

  • JobManager如果挂了,状态不会丢失;flink JobManager有HA

2.FsStateBackend

在这里插入图片描述

写到TaskManager的磁盘文件中;也可以是hdfs;文件路径由master保管

//参数是checkpoint路径
env.setStateBackend(new FsStateBackend("hdfs:hadoop102:8020/flink/ck"));

3.RocksDBBackend

在这里插入图片描述
在这里插入图片描述

  • RocksDB是一种集成在flink内部的key-value型数据库;
  • RocksDBStateBackend将工作状态保存在RocksDB数据库(位置在taskManagerd的数据目录)。通过checkpoint, 整个RocksDB数据库被复制到配置的文件系统或目录中,并且元数据保存jobManager的内存中。

特点:

  • RocksDBStateBackend可以通过enableIncrementalCheckpointing参数配置是否进行增量Checkpoint(而MemoryStateBackend 和 FsStateBackend不能)。
  • 跟FsStateBackend 不同的是,RocksDBStateBackend仅支持异步快照(asynchronous snapshots)。

适用场景

  • 大状态、长窗口、大key/value状态的的任务
  • 全高可用配置
    由于RocksDBStateBackend将工作状态存储在taskManger的本地文件系统,状态数量仅仅受限于本地磁盘容量限制,对比于FsStateBackend保存工作状态在内存中,RocksDBStateBackend能避免flink任务持续运行可能导致的状态数量暴增而内存不足的情况,因此适合在生产环境使用。

实战

导入依赖

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-statebackend-rocksdb_2.12</artifactId>
    <version>1.10.1</version>
</dependency>
//参数是checkpoint路径
env.setStateBackend(new RicksDBStateBackend("hdfs:hadoop102:8020/flink/ck"));

在这里插入图片描述

在这里插入图片描述
RocksDB可以增量

4.配置方式

4.1 全局配置

flink可以通过flink-conf.yaml 全局配置state backend

# The backend that will be used to store operator state checkpoints
state.backend: filesystem
# Directory for storing checkpoints
state.checkpoints.dir: hdfs://namenode:40010/flink/checkpoints

state backend可选值包括:

  • jobmanager (MemoryStateBackend)
  • filesystem (FsStateBackend)
  • rocksdb (RocksDBStateBackend)

state.checkpoints.dir设置checkpoints数据和元数据文件

4.2 单任务配置

通过在单个flink任务中通过env.setStateBackend(…)单独调整state backend配置,这种方式会覆盖全局配置。例如:

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints"));

5. Flink程序标准开头

在这里插入图片描述

3. 状态一致性

什么是状态一致性?
Flink中算子都是有状态的,在遇到故障宕机的时候可以恢复状态,恢复完之后还能重新计算,结果也是正确的就是状态一致性。

当在分布式系统中引入状态时,自然也引入了一致性问题。

一致性实际上是"正确性级别"的另一种说法,也就是说在成功处理故障并恢复之后得到的结果,与没有发生任何故障时得到的结果相比,前者到底有多正确?举例来说,假设要对最近一小时登录的用户计数。在系统经历故障之后,计数结果是多少?如果有偏差,是有漏掉的计数还是重复计数?所以根据实际情况就可以对一致性设定不同的级别

Flink状态的一致性级别

Flink3种状态一致性

at-most-once: 这其实是没有正确性保障的委婉说法——故障发生之后,计数结果可能丢失。

at-least-once: 这表示计数结果可能大于正确值,但绝不会小于正确值。也就是说, 计数程序在发生故障后可能多算,但是绝不会少算。

exactly-once: 这指的是系统保证在发生故障后得到的计数结果与正确值一致。

Flink 通过barrier对齐实现状态一致性

在这里插入图片描述


Flink的一个重大价值在于,它既保证了exactly-once,也具有低延迟和高吞吐的处理能力。

从根本上说,Flink通过使自身满足所有需求来避免权衡,它是业界的一次意义重大的技术飞跃。尽管这在外行看来很神奇,但是一旦了解,就会恍然大悟。

4.3.2 端到端(end-to-end)状态一致性

上面所描述的一致性保证都是由流处理器实现的,也就是说都是在 Flink 流处理器内部保证的;而在真实应用中,流处理应用除了流处理器以外还包含了数据源(例如 Kafka)和输出到持久化系统。

端到端的一致性保证,意味着结果的正确性贯穿了整个流处理应用的始终;每一个组件都保证了它自己的一致性,整个端到端的一致性级别取决于所有组件中一致性最弱的组件。 具体可以划分如下:

  • source端 —— 需要外部源可重设数据的读取位置
  • flink内部 —— 依赖checkpoint
  • sink端 —— 需要保证从故障恢复时,数据不会重复写入外部系统

而对于sink端,又有两种具体的实现方式:幂等(Idempotent)写入和事务性(Transactional)写入。


不同 Source 和 Sink 的一致性保证可以用下表说明:
在这里插入图片描述

1.Sink端幂等写入

所谓幂等操作,是说一个操作,可以重复执行很多次,但只导致一次结果更改,也就是说,后面再重复执行就不起作用了。

在这里插入图片描述
额外说明: sink的幂等写入出现故障恢复的时候,flink应用程序中算子状态和写出的数据库中数据不一致问题,因为假设在A状态flink做了CheckPoint,然后程序继续运行到B状态,此时数据库中存储的是B状态,然后故障重启,flink从A状态开始恢复,但是数据库中是B状态,这就是短暂的不一致,当再次写入到数据库中,就一致了。

2.Sink端事务写入

在这里插入图片描述

事务写入原理: 构建事务来写入外部系统,构建的事务对应着 checkpoint,等到 checkpoint 真正完成的时候,才把所有对应的结果写入 sink 系统中。

事务性写入两种实现方式: 预写日志(WAL)和两阶段提交(2PC)。

2.1 预写日志

原理:

  • 在JobManager发出Checkpoint任务的时候,Sink启动一个事务,将所接收到的数据放进状态后台进行缓存,而不是直接写出去。等CheckPoint完成之后,提交事务,一次性写出。

特点:

  • 简单容易实现;由于数据在状态后端做了缓存,所以无论什么sink系统,这种方式都能一批次搞定

实现:

  • DataStream API提供了一个模板类:GenericWriteAheadSink,来实现这种预写日志的事务型sink;

实现举例:

  • 搞一个list状态,再搞个时间状态,第一条数据来了,时间状态为null,定义一个定时器十秒后触发,第一条数据直接往List中扔,每条数据都往里面扔,扔完了后判断一下List中个数>=10了,写出并清空时间状态;如果<10,继续写;或者等到定时器触发,再清空;[10s或者10条数据来写到数据库中]
    Qps: query per second 滴滴5-7w qps 所以一般都是批量提交

预写日志存在的问题:

  • 一次性写出为批量写出,牺牲了一定的实时性。
  • 如果想通过预写日志的方式实现Sink的精准一次性写出,这就要求自定义的Sink拥有事务功能,如果没有事务,在往外写出的过程中失败,source端重复消费重写,由于不能撤回,那么会导致数据重复。[注意:预写日志是事务写出的Sink方案,所以不考虑幂等性]
2.2 两阶段提交

原理:

  • 预提交: 在JobManager发出checkpoint任务的时候,sink会启动一个事务,将接下来所有接收的数据添加到事务中;将这些数据直接写到外部Sink系统,但是不提交事务,此时是“预提交”;
  • 提交: 当JobManager真正完成CheckPoint,sink接收到checkPoint完成的通知时,才会正式的提交事务,此时结果真正的写入外部系统。

特点:

  • 每一个barrier都会开启一个独立的事务

2阶段提交对外部Sink系统的要求

事务写入的两种方式对比总结:

共同点:预提交和预写日志都是当Checkpoint完成,才完成对外部系统的写出。
不同点:预写日志是在sink做缓存,等到CK完成后,再将缓存内容批量写出;这个过程中事务从开启到关闭的时间很短暂,开启事务,写出缓存,关闭事务;
2阶段提交是不缓存,而是当sink收集到 barrier之后就开启当前barrier的事务,然后sink往外部系统写出,并等待JobManager通知sink CheckPoint完成,等到CK完成只是提交个事务,数据已经写出去了,预提交效率高一点。
预写日志的事务开启时间短暂,而且和barrier没有关系,两阶段提交和barrier有关系;

5.Flink + Kafka实现端到端的一致性

(1)source+Flink+sink的设置

在这里插入图片描述

(2)两阶段提交流程

1.预提交
Kafka的producer就是两阶段提交,生产者预提交的数据生产者读取不到;
在这里插入图片描述

  1. JobManager往Source中注入Barrier
  2. Source将 消费的偏移量存到状态后端,并且barrier往下流(二者是并行)
  3. 以此类推,直到sink算子存储状态+预提交

2.真正的提交
在这里插入图片描述

  • 当sink收集到一个barrier之后,会向JobManager汇报可以做CheckPoint了,JobManager再向Sink通知CheckPoint完成,在Sink等待收到CK完成的通知的过程中,Sink仍然会不断地接收barrier后面的数据,但是不是做缓存,而是开启一个新的事务,因此barrier后面的数据会进入新的事务。
  • 所以一个barrier对应一个检查点对应一个事务,当JobManager反馈检查点完成,事务才提交。[一个barrier对应一个事务]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值