Flink第七章:Flink中的状态和检查点

文章目录


前言

在实时流处理领域,Apache Flink 凭借其卓越的容错机制和精确的状态管理能力,成为业界公认的标准。状态(State)是流处理中承载计算逻辑与历史信息的核心载体,它决定了算子能否在事件驱动的场景下实现聚合、窗口、去重等复杂功能。本文围绕 Flink 的状态管理展开系统梳理:首先厘清无状态与有状态算子的区别,继而深入剖析托管状态(Managed State)的两大分支------算子状态(Operator State)与按键分区状态(Keyed State),并重点演示 ValueState 的代码实战;随后,文章详细介绍了保障故障恢复的两大机制:自动触发的检查点(Checkpoint)与手动管理的保存点(Savepoint),包括配置方式、重启流程及实际命令行操作。通过本文,读者将建立起对 Flink 状态体系的全方位认知,为编写健壮的流式应用打下坚实基础。


一、状态概述

在Flink中,算子任务可以分为无状态和有状态两种情况。

(一)无状态

  • 无状态的算子任务只需要观察每个独立事件,根据当前输入的数据直接转换输出结果 。我们之前讲到的基本转换算子,如map、filter、flatMap计算时不依赖其他数据,就都属于无状态的算子。

(二)有状态

1.概念

  • 有状态的算子任务 ,则除当前数据之外,还需要一些其他数据来 得到计算结果。这里的**"其他数据",就是所谓的状态(state)**。我们之前讲到的算子中,聚合算子、窗口算子都属于有状态的算子。

2.步骤

有状态算子的一般处理流程,具体步骤如下:

  • 1.算子任务接收到上游发来的数据;
  • 2.获取当前状态;
  • 3.根据业务逻辑进行计算,更新状态;
  • 4.得到计算结果,输出发送到下游任务

(三)状态分类

1.托管状态(Managed State)

托管状态就是由Flink统一管理 的,状态的存储访问、故障恢复和重组等一系列问题都由Flink实现,我们只要调接口 就可以;

通常我们采用Flink托管状态来实现需求。

2.原始状态(Raw State)

原始状态则是自定义 的,相当于就是开辟了一块内存,需要我们自己管理,实现状态的序列化和故障恢复。

  • 在Flink中,一个算子任务会按照并行度分为多个并行子任务执行 ,而不同的子任务会占据不同的任务槽(task slot)
  • 由于不同的slot在计算资源上是物理隔离的,所以Flink能管理的状态在并行任务间是无法共享的,每个状态只能针对当前子任务的实例有效。
  • 很多有状态的操作(比如聚合、窗口)都是要先做keyBy进行按键分区的。按键分区之后,任务所进行的所有计算都应该只针对当前key有效,所以状态也应该按照key彼此隔离。
  • 基于这样的想法,我们又可以将托管状态分为两类:算子状态和按键分区状态。

3.算子状态(Operator State)

  • 状态作用范围限定为当前的算子任务实例,也就是只对当前并行子任务实例有效 。这就意味着对于一个并行子任务,占据了一个"分区",它所处理的所有数据都会访问到相同的状态,状态对于同一任务而言是共享的。
  • 算子状态可以用在所有算子上,使用的时候其实就跟一个本地变量没什么区别------因为本地变量的作用域也是当前任务实例。在使用时,我们还需进一步实现CheckpointedFunction接口。
  • FLIP-27的新Source架构,则是需要继承SourceReaderBase抽象类。

4.按键分区状态(Keyed State)

  • 状态是根据输入流中定义的键(key)来维护和访问的,所以只能定义在**按键分区流(KeyedStream)**中,也就keyBy之后才可以使用。
  • 按键分区状态应用非常广泛。之前讲到的聚合算子必须在keyBy之后才能使用 ,就是因为聚合的结果是以Keyed State的形式保存的。
(2)Keyed State概念
  • 按键分区状态(Keyed State):是任务按照键(key)来访问和维护的状态。它的特点非常鲜明,就是以key为作用范围进行隔离。
  • 需要注意,使用Keyed State必须基于KeyedStream。没有进行keyBy分区的DataStream,即使转换算子实现了对应的富函数类,也不能通过运行时上下文访问Keyed State。
  • Flink 提供了以下数据格式来管理和存储键控状态 (Keyed State) :·
    • ValueState:存储单值类型 的状态。可以使用 update(T) 进行更新,并通过 T value() 进行检索。
    • ListState:存储列表类型 的状态。可以使用 add(T) addAll(List)添加元素;并通过 get() 获得整个列表。
    • ReducingState:用于存储经过 ReduceFunction 计算后的结果,使用 add(T) 增加元素。
    • AggregatingState:用于存储经过 AggregatingState 计算后的结果,使用 add(IN) 添加元素。
    • FoldingState已被标识为废弃,会在未来版本中移除,官方推荐使 AggregatingState 代替。
    • MapState:维护 Map 类型的状态。
(3)代码
java 复制代码
package chapter07;

import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

// 使用KeyState中的ValueState获取数据中的最大值(获取每个key的最大值)(实际中直接使用max/maxby即可)

public class KeyedStateDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env.setParallelism(1);

        DataStreamSource<Tuple2<String, Long>> tupleDS = env.fromElements(
                Tuple2.of("北京", 1L),
                Tuple2.of("上海", 2L),
                Tuple2.of("北京", 6L),
                Tuple2.of("上海", 8L),
                Tuple2.of("北京", 3L),
                Tuple2.of("上海", 4L),
                Tuple2.of("北京", 7L)
        );

        tupleDS
                .keyBy(v -> v.f0)
                        .map(new RichMapFunction<Tuple2<String, Long>, Tuple2<String, Long>>() {
                            // 状态采用ValueState用于保存最大值
                            ValueState<Long> maxValueState = null;

                            @Override
                            public void open(Configuration parameters) throws Exception {
                                ValueStateDescriptor<Long> stateDescriptor = new ValueStateDescriptor<>("valueState", Long.class);
                                maxValueState = getRuntimeContext().getState(stateDescriptor);
                            }

                            @Override
                            public Tuple2<String, Long> map(Tuple2<String, Long> tuple2) throws Exception {
                                Long maxValue = maxValueState.value();
                                Long currentValue = tuple2.f1;

                                if(maxValue == null || currentValue > maxValue){
                                    maxValueState.update(currentValue);
                                    maxValue = currentValue;
                                }
                                return Tuple2.of(tuple2.f0, maxValue);
                            }
                        }).print();

        env.execute();
    }
}

无论是Keyed State还是Operator State,它们都是在本地实例上维护的,也就是说每个并行子任务维护着对应的状态,算子的子任务之间状态不共享。


二、检查点(Checkpoint)

(一)概念

  • 在流处理中,我们可以用存档读档 的思路,就是将之前某个时间点所有的状态保存下来 ,这份"存档"就是所谓的"检查点"(checkpoint)。
  • 遇到故障重启 的时候,我们可以从检查点中"读档",恢复出之前的状态,这样就可以回到当时保存的一刻接着处理数据了。
  • 这里所谓的 "检查",其实是针对故障恢复的结果而言的 :故障恢复之后继续处理的结果,应该与发生故障前完全一致,我们需要"检查"结果的正确性。所以,有时又会把checkpoint叫做"一致性检查点"。

(二)设置CheckPoint

1. 开启快照,每隔1s保存一次快照:

java 复制代码
env.enableCheckpointing(1000);        

2. 设置快照保存的位置

java 复制代码
env.setStateBackend(new FsStateBackend("hdfs://bigdata01:9820/flink/checkpoint"));   

3. 取消flink的job时,不删除HDFS的checkpoint目录

java 复制代码
env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

4.具体示例完整代码

java 复制代码
package chapter07;

import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.runtime.state.filesystem.FsStateBackend;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.CheckpointConfig;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

public class CheckPointDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
 				//在windows下运行,将数据提交到hdfs,会出现权限问题
        System.setProperty("HADOOP_USER_NAME", "atguigu");
        // 1. 开启快照,每隔1s保存一次快照
        env.enableCheckpointing(1000);
        // 2. 设置快照保存的位置
        env.setStateBackend(new FsStateBackend("file:///D:/flink/chk"));
        // 3. 取消flink的job时,不删除hdfs的checkpoint目录
        env.getCheckpointConfig()
                .enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

        DataStreamSource<String> dataStreamSource = env.socketTextStream("hadoop102", 7777);

        SingleOutputStreamOperator<Tuple2<String, Integer>> datastream = dataStreamSource
                .map(new MapFunction<String, Tuple2<String, Integer>>() {
                    @Override
                    public Tuple2<String, Integer> map(String s) throws Exception {
                        String[] words = s.split(",");
                        return Tuple2.of(words[0], Integer.valueOf(words[1]));
                    }
                })
                .setParallelism(2)
                .keyBy(0)
                .sum(1);

        datastream.print();
        env.execute("Socket Word Count");
    }
}

(三)从CheckPoint重启应用

powershell 复制代码
flink run -c 全类名  -s CheckPoint位置  jar包
powershell 复制代码
如:flink run -c com.bigdata.day06._01CheckPointDemo -s hdfs://bigdata01:9820/flink/checkpoint/bf416df7225b264fc34f8ff7e3746efe/chk-603  /opt/app/FlinkDemo-1.0-SNAPSHOT.jar

三、保存点(Savepoint)

(一)概念

  • 除了检查点外,Flink还提供了另一个非常独特的镜像保存功能------保存点(savepoint)。
  • 从名称就可以看出,这也是一个存盘的备份,它的原理和算法与检查点完全相同,只是多了一些额外的元数据

(二)与检查点的区别------触发时机

  • 检查点 是由Flink自动管理的,定期创建,发生故障之后自动读取进行恢复,这是一个**"自动存盘"**的功能;
  • 保存点不会自动创建 ,必须由用户明确地手动触发 保存操作,所以就是 "手动存盘"

(三)使用保存点

保存点的使用非常简单,我们可以使用命令行工具来创建保存点,也可以从保存点恢复作业。

1.创建保存点

要在命令行中为运行的作业创建一个保存点镜像,只需要执行:

powershell 复制代码
bin/flink savepoint :jobId [:targetDirectory]
  • 这里jobId需要填充要做镜像保存的作业ID,目标路径targetDirectory可选,表示保存点存储的路径。
  • 例如:flink savepoint 79f53c5c0bb3563b6b6ed3011176c411 hdfs://hadoop102:8020/flink/savepoint

2.从保存点重启应用

要从保存点重启一个应用只需要执行

powershell 复制代码
bin/flink run -s :savepointPath [:runArgs]

##如:
bin/flink run -c chapter07.CheckPointDemo -s hdfs://hadoop102:8020/flink/savepoint/savepoint-254aa4-c601bc38410d FlinkDemos_BD1-1.0-SNAPSHOT.jar
  • 这里只要增加一个-s参数,指定保存点的路径就可以了,其它启动时的参数还是完全一样的。
  • 如果使用web UI 进行作业提交时,可以填入的参数除了入口类、并行度和运行参数,还有一个 "Savepoint Path",这就是从保存点启动应用的配置。

总结

本文全面梳理了 Flink 中状态管理的核心知识体系。我们首先明确了有状态与无状态算子的区分,指出聚合、窗口等操作依赖于按键分区状态(Keyed State)来实现按 key 隔离的数据存储;而算子状态(Operator State)则作用于并行子任务实例,适用于更通用的场景。在托管状态的基础上,文章重点介绍了检查点(Checkpoint)这一自动容错机制,通过周期性保存状态快照,确保故障后能够精确恢复;同时,保存点(Savepoint)作为手动触发的镜像备份,为版本升级、作业迁移提供了极大的灵活性。通过配置 enableCheckpointing、FsStateBackend 以及从检查点/保存点重启应用的具体命令,读者可以轻松将理论付诸实践。状态管理是 Flink 流处理的基石,深入理解这些概念,将有助于您设计出高可靠、易维护的实时数据管道。