前言

使用Flink版本 1.13 , 该版本对状态有所改变

  • 删除 state.backend.async
  • 重新设计了状态后端的存储
  • 统一keyState的savePoint的存储格式为二进制
  • FailureRateRestartBackoffTimeStrategy 允许比配置少重启一次
  • 支持未对齐检查点的重新调整:从未对齐检查点恢复的时候支持改变作业的并行度



什么是状态?

  对我们进行记住多个event的操作的时候,比如说窗口,那么我们称这种操作是有状态的,是需要缓存数据的。Flink 需要知道状态,以便使用检查点保存点使其容错 。简单的说就是Flink的一个存储单元。

  从 Flink 1.11 开始,检查点可以在对齐或不对齐的情况下进行。



  状态分为两种:键值状态(KeyState)和(OperatorState)非键值状态(算子状态)。




状态和ck的关系

State一般指一个具体的task/operator的状态。而Checkpoint则表示了一个Flink Job,在一个特定时刻的一份全局状态快照(state snapshots ),即包含了所有task/operator的状态。


什么是状态后端

  Flink 提供了不同的状态后端,用于指定状态的存储方式和位置。默认情况下,配置文件flink-conf.yaml确定所有 Flink 作业的状态后端。

  对于流处理程序,Flink Job的状态后端决定了它的状态如何 存储在每个 TaskManager(TaskManager 的 Java 堆或(嵌入式)RocksDB)上。



State 始终在本地访问(只是访问/使用),这有助于 Flink 应用程序实现高吞吐量和低延迟。您可以选择将状态保留在 JVM 堆上,或者如果它太大,则保留在有效组织的磁盘数据结构中,这取决于状态后端的配置。

java取消flink任务 flink任务状态_state



官方提供了两种静态的(开箱即用)的状态后端:

  • HashMapStateBackend:保存数据在内部作为Java堆的对象。
  • EmbeddedRocksDBStateBackend:在RocksDB数据库中保存动态数据。


通过状态快照的容错 #



1.13版本Flink社区重新设计了状态后端的存储,以便于用户更好的理解状态后端的存储检查点的存储的分离。

Flink详解Exactly-Once机制注意:该文写于1.12的flink版本,那个时候ck存储和状态存储在理解上还是绑定在一起,具体情况看本文的状态后端一节


Keyed State

如果要使用键控状态,则需要先分出键控流:dataStream.keyBy()产生一个键控流(KeyedStream),然后允许使用键控状态的操作。

java取消flink任务 flink任务状态_flink_02

在上图中:Keyed State 被进一步组织成所谓的Key Groups。Key Groups的数量与定义的并行度一样多。在运行并行实例中的一个并行算子的时候,使用到一个或多个Key Groups的keys。


可用的KeydeState

  • ValueState<T>:保留一个可以更新和检索的值:设置update(T)T value()
  • ListState<T>:保留一个元素列表,可以追加、遍历Iterable和覆盖:add(T) , addAll(List) , Iterable Iterable<T> get() , update(List);
  • ReducingState<T>只保留一个值,但是这个值代表添加到State的所有值的聚合,故为reduce。
  • AggregatingState<IN, OUT>只保留一个值,但是这个值代表添加到State的所有值的聚合。与ReducingState不同的是,IN的元素可能会不一样。
  • MapState<UK, UV>:保留一个Map,可以增加(put(UK, UV),putAll(Map<UK, UV>)),检索(get(UK)),遍历(entries()->key()和value())和判空(isEmpty());

所有类型的状态也有一个方法clear()可以清除当前活动键的状态,即输入元素的key。


我们要记住两件事

1、重要的是要记住,这些状态对象仅用于与状态接口。状态不一定存储(状态后端)在内部,但可能驻留在磁盘或其他地方。

2、我们从状态(存储)获取的value,取决于event element流数据的key。流式数据是流动的。


KeydeState的使用

要获得状态句柄(使用State),我们必须创建一个StateDescriptorStateDescriptor包含了:

  • State的名称
  • 保存的值的类型
  • 可能的用户-指定的函数,例如ReduceFunction


我们可以对于上面可用的KeyedState来创建对于的StateDescriptor。我们以官网的例子来示例:



代码创建了一个统计每两个element的平均数的计数窗口,计数到2变求出平均值并清空ValueState:clear()。

public class CountWindowAverage extends RichFlatMapFunction<Tuple2<Long, Long>, Tuple2<Long, Long>> {

    /**
     * The ValueState handle. The first field is the count, the second field a running sum.
     */
    private transient ValueState<Tuple2<Long, Long>> sum;

    @Override
    public void flatMap(Tuple2<Long, Long> input, Collector<Tuple2<Long, Long>> out) throws Exception {

        // access the state value
        Tuple2<Long, Long> currentSum = sum.value();

        // update the count
        currentSum.f0 += 1;

        // add the second field of the input value
        currentSum.f1 += input.f1;

        // update the state
        sum.update(currentSum);

        // if the count reaches 2, emit the average and clear the state
        if (currentSum.f0 >= 2) {
            out.collect(new Tuple2<>(input.f0, currentSum.f1 / currentSum.f0));
            sum.clear();
        }
    }

    @Override
    public void open(Configuration config) {
        ValueStateDescriptor<Tuple2<Long, Long>> descriptor =
                new ValueStateDescriptor<>(
                        "average", // the state name
                        TypeInformation.of(new TypeHint<Tuple2<Long, Long>>() {}), // type information
                        Tuple2.of(0L, 0L)); // default value of the state, if nothing was set
        sum = getRuntimeContext().getState(descriptor);
    }
}

// this can be used in a streaming program like this (assuming we have a StreamExecutionEnvironment env)
env.fromElements(Tuple2.of(1L, 3L), Tuple2.of(1L, 5L), Tuple2.of(1L, 7L), Tuple2.of(1L, 4L), Tuple2.of(1L, 2L))
        .keyBy(value -> value.f0)
        .flatMap(new CountWindowAverage())
        .print();

// the printed output will be (1,4) and (1,5)
// 输出两个是因为输入5个<6, 输出1,5是因为 /取整


Keyed State的TTL(Time to live,只支持处理时间)

当前仅支持与处理时间(Progress Time)相关的TTL!!!

设置键值状态的过期时间,尽最大可能的清理存储值。这里我们需要配置一个StateTtlConfig对象:

import org.apache.flink.api.common.state.StateTtlConfig;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.time.Time;

StateTtlConfig ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(1))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .build();
    
ValueStateDescriptor<String> stateDescriptor = new ValueStateDescriptor<>("text state", String.class);
stateDescriptor.enableTimeToLive(ttlConfig);



配置参数

newBuilder:固定就是生存时间



setUpdateType:表明了过期时间什么时候更新:在StateTtlConfig.UpdateType.下有:

  • OnCreateAndWrite - 仅在创建和写访问时
  • OnReadAndWrite - 也在读访问


在State设置为可见情况下,是否还能访问过期的State的值:在StateTtlConfig.StateVisibility.下有:

  • NeverReturnExpired - 永远不会返回过期值
  • ReturnExpiredIfNotCleanedUp - 如果过期值未被清除仍然可用,则返回。


默认情况下,过期State会被删除,设置过期不被删除策略,配置StateTtlConfig:

StateTtlConfig.disableCleanupInBackground(); //此选项不适用于 RocksDB 状态后端的增量检查点。



NeverReturnExpired处理隐私敏感数据的应用程序很有用。


注意

  • TTL 配置不是检查点或保存点的一部分,而是 Flink 在当前运行的作业中如何处理它的一种方式。
  • 会增加状态存储的消耗;


算子状态(Operator State )

  算子状态(or non-keyed state),是被绑定在单并行(并行度为1)的算子实例中的State(状态)的。一般来说它会被用在 Source 或 Sink 等算子上,用来保存流入数据的偏移量或对输出数据做缓存,以保证 Flink 应用的 Exactly-Once 语义。

  KafkaConsumer便是一个很好的例子来说明:KafkaConsumer的每一个并行消费实例都有一个算子状态,它存储着消费主题的分区们(partitions)偏移量们(offsets)的映射集合(Map)。


广播状态

待研习。


状态后端

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(...);



Flink 提供了三种可用的状态后端用于在不同情况下进行状态的保存:

  • MemoryStateBackend
  • FsStateBackend
  • RocksDBStateBackend

1.13版本Flink社区重新设计了状态后端的存储,以便于用户更好的理解状态后端的存储检查点的存储的分离。对于组合策略说明有官网链接可以查询。我们以下面的内存状态后端为例:


MemoryStateBackend

老版本设置

env.setStateBackend(new MemoryStateBackend(DEFAULT_MAX_STATE_SIZE,false));

  false 代表关闭异步快照机制。很明显 MemoryStateBackend 适用于我们本地调试使用,来记录一些状态很小的 Job 状态信息。

  MemoryStateBackend相当于使用HashMapStateBackendJobManagerCheckpointStorage


新版本配置

代码配置
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new HashMapStateBackend());
env.getCheckpointConfig().setCheckpointStorage(new JobManagerStateBackend());



配置文件配置
state.backend: hashmap

# 默认为JobManagerCheckpointStorage
state.checkpoint-storage: jobmanager


FsStateBackend

  FsStateBackend 会把状态数据保存在 TaskManager 的内存中。CheckPoint 时,将状态快照写入到配置的文件系统目录中,少量的元数据信息存储到 JobManager 的内存中。

  FsStateBackend相当于使用HashMapStateBackendFileSystemCheckpointStorage



代码配置

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new HashMapStateBackend());
env.getCheckpointConfig().setCheckpointStorage("file:///checkpoint-dir");



自定义化配置需要自定义实现FileSystemCheckpointStorage类:

// 高级 FsStateBackend 配置,例如写缓冲区大小,可以通过手动实例化 FileSystemCheckpointStorage 对象来设置。
env.getCheckpointConfig().setCheckpointStorage(new FileSystemCheckpointStorage("file:///checkpoint-dir"));



配置文件配置

state.backend: hashmap
state.checkpoints.dir: file:///checkpoint-dir/

# 默认为FileSystemCheckpointStorage
state.checkpoint-storage: filesystem


RocksDBStateBackend

  RocksDBStateBackend 将正在运行中的状态数据保存在 RocksDB 数据库中,RocksDB 数据库默认将数据存储在 TaskManager 运行节点的数据目录下。

需要注意的是,RocksDBStateBackend 是唯一支持增量快照的状态后端。

  RocksDBStateBackend相当于使用EmbeddedRocksDBStateBackendFileSystemCheckpointStorage



代码配置

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new EmbeddedRocksDBStateBackend());
env.getCheckpointConfig().setCheckpointStorage("file:///checkpoint-dir");

或者自定义化配置需要自定义实现FileSystemCheckpointStorage类:

// 高级 FsStateBackend 配置,例如写缓冲区大小,可以通过手动实例化 FileSystemCheckpointStorage 对象来设置。
env.getCheckpointConfig().setCheckpointStorage(new FileSystemCheckpointStorage("file:///checkpoint-dir"));



配置文件配置

state.backend: rocksdb
state.checkpoints.dir: file:///checkpoint-dir/

# 默认为FileSystemCheckpointStorage
state.checkpoint-storage: filesystem


备注

Python DataStream API 仍然不支持 Operator 状态。 不支持广播状态。