Flink 状态管理

学习内容:

  • 状态的功能
  • 状态的分类
  • 状态的开发
  • Checkpoint
  • 重启策略
  • StateBackend
  • Savepoint

什么是状态

后面批次的计算结果是基于前面批次的计算结果进行处理,而相对的Spark/MR有固定的数据源批次,是无状态的。

状态的分类

状态主要分为3类:

  • Keyed State
  • Operator State
  • Broadcast State

Keyed State

键控状态接口提供对不同类型状态的访问,这些状态的范围都限定在当前输入元素的键内。这意味着这种类型的状态只能在 KeyedStream 上使用,它可以通过 Java/Scala API 中的 stream.keyBy(…) 或 Python API 中的 stream.key_by(…) 创建。

可用的不同类型的状态如下:

  • ValueState<T> getState(ValueStateDescriptor<T>)
  • ReducingState<T> getReducingState(ReducingStateDescriptor<T>)
  • ListState<T> getListState(ListStateDescriptor<T>)
  • AggregatingState<IN, OUT> getAggregatingState(AggregatingStateDescriptor<IN, ACC, OUT>)
  • MapState<UK, UV> getMapState(MapStateDescriptor<UK, UV>)

以下为使用FlatMapFunction实现ValueState:

package com.imooc.flink.state;

import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.client.program.StreamContextEnvironment;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;

import java.util.ArrayList;

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

        test01(env);

        env.execute("WindowApp");
    }

    /**
     * 使用ValueState实现平均数
     *
     * @param env
     */
    public static void test01(StreamExecutionEnvironment env) {

        ArrayList<Tuple2<Long, Long>> list = new ArrayList<>();

        list.add(Tuple2.of(1L, 4L));
        list.add(Tuple2.of(1L, 5L));
        list.add(Tuple2.of(2L, 8L));
        list.add(Tuple2.of(2L, 4L));
        list.add(Tuple2.of(3L, 4L));
        list.add(Tuple2.of(3L, 6L));

        env.fromCollection(list)
                .keyBy(x -> x.f0)
                .flatMap(new AvgWithValueState())
                .print();
    }
}

class AvgWithValueState extends RichFlatMapFunction<Tuple2<Long, Long>, Tuple2<Long, Double>> {

    // 求平均数: 记录条数,求和
    private transient ValueState<Tuple2<Long, Long>> sum;

    // 在open时,获得描述器对象
    @Override
    public void open(Configuration parameters) throws Exception {
        ValueStateDescriptor<Tuple2<Long, Long>> descriptor = new ValueStateDescriptor<>(
                "avg",
                Types.TUPLE(Types.LONG, Types.LONG)
        );
        sum = getRuntimeContext().getState(descriptor);
    }

    // 实现flatMap的算子操作
    @Override
    public void flatMap(Tuple2<Long, Long> value, Collector<Tuple2<Long, Double>> out) throws Exception {
        // TODO.. ==> state 次数和总和

        Tuple2<Long, Long> currentState = sum.value();

        if (null == currentState) currentState = Tuple2.of(0L, 0L);

        currentState.f0 += 1;  // 次数
        currentState.f1 += value.f1;  // 求和

        sum.update(currentState);

        // 达到3条数据 ==> 求平均数  clear
        if (currentState.f0 >= 2) {
            out.collect(Tuple2.of(value.f0, currentState.f1/currentState.f0.doubleValue()));
            sum.clear();
        }
    }
}

Operator State

操作符状态(或非键控状态)是绑定到一个并行操作符实例的状态。 Kafka Connector 是在 Flink 中使用 Operator State 的一个很好的激励示例。 Kafka 消费者的每个并行实例都维护一个主题分区和偏移量的映射作为其操作员状态。

public static class CounterSource
        extends RichParallelSourceFunction<Long>
        implements CheckpointedFunction {

    /**  current offset for exactly once semantics */
    private Long offset = 0L;

    /** flag for job cancellation */
    private volatile boolean isRunning = true;
    
    /** Our state object. */
    private ListState<Long> state;

    @Override
    public void run(SourceContext<Long> ctx) {
        final Object lock = ctx.getCheckpointLock();

        while (isRunning) {
            // output and state update are atomic
            synchronized (lock) {
                ctx.collect(offset);
                offset += 1;
            }
        }
    }

    @Override
    public void cancel() {
        isRunning = false;
    }

    @Override
    public void initializeState(FunctionInitializationContext context) throws Exception {
        state = context.getOperatorStateStore().getListState(new ListStateDescriptor<>(
                "state",
                LongSerializer.INSTANCE));
                
        // restore any state that we might already have to our fields, initialize state
        // is also called in case of restore.
        for (Long l : state.get()) {
            offset = l;
        }
    }

    @Override
    public void snapshotState(FunctionSnapshotContext context) throws Exception {
        state.clear();
        state.add(offset);
    }
}

Broadcast State

广播状态是一种特殊类型的算子状态。引入它是为了支持需要将一个流的记录广播到所有下游任务的用例,这些记录用于在所有子任务之间维护相同的状态。然后可以在处理第二个流的记录时访问此状态。
广播状态和算子状态的不同在于:

  1. 有一个map格式
  2. 它仅适用于具有广播流和非广播流作为输入的特定算子
  3. 这样的操作符可以有多个不同名称的广播状态。
posted @ 2021-08-17 08:05  stone_la  阅读(210)  评论(0编辑  收藏  举报