关于什么是有状态的flink计算,官方给出的回答是这样的:在flink程序内部存储计算产生的中间结果,并提供给Function或算子计算结果使用。

Operator State

Operator State可以用在所有算子上,每个算子子任务或者说每个算子实例共享一个状态,流入这个算子子任务的数据可以访问和更新这个状态。下图展示了Operator State,算子子任务1上的所有数据可以共享第一个Operator State,以此类推,每个算子子任务上的数据共享自己的状态。

img

如何使用Operator State呢?我们可以通过实现CheckpointedFunction接口来实现,或者实现ListCheckpointed<T extends Serializable>接口来实现,它们之间主要的区别是:实现CheckpointedFunction接口,有两种形式的ListState API可以使用,分别是getListState以及getListUnionState,它们都会返回一个ListState,但是他们在重新分区的时候会有区别,后面会详细介绍。。。

Operator State的实际应用场景不如Keyed State多,它经常被用在Source或Sink等算子上,用来保存流入数据的偏移量或对输出数据做缓存,以保证Flink应用的Exactly-Once语义。这里我们来看一个Flink官方提供的Sink案例以了解CheckpointedFunction的工作原理。

import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.runtime.state.FunctionInitializationContext;
import org.apache.flink.runtime.state.FunctionSnapshotContext;
import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction;
import org.apache.flink.streaming.api.functions.sink.SinkFunction;
import scala.Tuple2;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author Natasha
 * @Description 输出到Sink之前,先将数据放在本地缓存中,并定期进行snapshot。即使程序崩溃,状态中存储着还未输出的数据,下次启动后还会将这些未输出数据读取到内存,继续输出到外部系统。
 * @Date 2020/10/19 16:30
 **/


public class BufferingSink implements SinkFunction<Tuple2<String, Integer>>, CheckpointedFunction {

    private final int threshold;
    //托管状态
    private transient ListState<Tuple2<String, Integer>> checkpointedState;
    //原始状态(本地缓存)
    private List<Tuple2<String, Integer>> bufferedElements;

    public BufferingSink(int threshold) {
        this.threshold = threshold;
        this.bufferedElements = new ArrayList();
    }

    @Override
    //Sink的核心处理逻辑,将上游数据value输出到外部系统:在invoke方法里头先将value缓存到bufferedElements,缓存个数触发阈值时,执行sink操作,然后清空bufferedElements
    public void invoke(Tuple2<String, Integer> value) throws Exception {
        // 先将上游数据缓存到本地的缓存
        bufferedElements.add(value);
        // 当本地缓存大小到达阈值时,将本地缓存输出到外部系统
        if (bufferedElements.size() == threshold) {
            for (Tuple2<String, Integer> element: bufferedElements) {
                // send it to the sink
            }
            // 清空本地缓存
            bufferedElements.clear();
        }
    }

    @Override
    // Checkpoint触发时会调用这个方法,对bufferedElements进行snapshot本地状态持久化
    public void snapshotState(FunctionSnapshotContext context) throws Exception {
        checkpointedState.clear();
        //将最新的数据写到状态中
        for (Tuple2<String, Integer> element : bufferedElements) {
            checkpointedState.add(element);
        }
    }

    @Override
    // 第一次初始化时会调用这个方法,或者从之前的检查点恢复时也会调用这个方法
    public void initializeState(FunctionInitializationContext context) throws Exception {
        ListStateDescriptor<Tuple2<String, Integer>> descriptor =
                new ListStateDescriptor(
                        "buffered-elements",
                        TypeInformation.of(new TypeHint<Tuple2<Long, Long>>() {}));

        checkpointedState = context.getOperatorStateStore().getListState(descriptor);
        // 如果是作业重启,读取存储中的状态数据并填充到本地缓存中
        if (context.isRestored()) {
            for (Tuple2<String, Integer> element : checkpointedState.get()) {
                //从托管状态将数据到移动到原始状态
                bufferedElements.add(element);
            }
        }
    }
}

Keyed State

Keyed State是KeyedStream上的状态。假如输入流按照id为Key进行了keyBy分组,形成一个KeyedStream,数据流中所有id为1的数据共享一个状态,可以访问和更新这个状态,以此类推,每个Key对应一个自己的状态。下图展示了Keyed State,因为一个算子子任务可以处理一到多个Key,算子子任务1处理了两种Key,两种Key分别对应自己的状态。

img

它主要提供了以下的state:

  • Value State:ValueState 分区的单值状态。
  • Map State:MapState<UK,UV> 分区的键值状态。
  • List State:ListState 分区的列表状态。
  • Reducing State:ReducingState 每次调用 add(T) 添加新元素,会调用 ReduceFunction 进行聚合。传入类型和返回类型相同。
  • Aggregating State:AggregatingState<IN,OUT> 每次调用 add(T) 添加新元素,会调用ReduceFunction 进行聚合。传入类型和返回类型可以不同。

下面是一个简单的示例,计算每一个key中平均每3个数据的平均值,如:

import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.common.state.StateTtlConfig;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;


/**
 * @Author Natasha
 * @Description 计算不同key的平均每三个之间的平均值
 * @Date 2020/10/19 17:46
 **/
public class KeyedStateDemo {


    public static void main(String[] args) throws Exception {
        final StreamExecutionEnvironment  env=StreamExecutionEnvironment.getExecutionEnvironment();
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        DataStream<Tuple2<Long,Long>> input=env.fromElements(
                Tuple2.of(1L,4L),
                Tuple2.of(1L,2L),
                Tuple2.of(1L,6L),
                Tuple2.of(2L,4L),
                Tuple2.of(2L,4L),
                Tuple2.of(3L,5L),
                Tuple2.of(2L,3L),
                Tuple2.of(1L,4L)
        );

        input.keyBy(0)
                .flatMap(new KeyedStateAgvFlatMap())
                .setParallelism(10)
                .print();

        env.execute();
    }


    public static class KeyedStateAgvFlatMap extends RichFlatMapFunction<Tuple2<Long,Long>,Tuple2<Long,Long>> {

        private ValueState<Tuple2<Long,Long>> valueState;

        @Override
        public void flatMap(Tuple2<Long, Long> value, Collector<Tuple2<Long, Long>> collector) throws Exception {
            Tuple2<Long,Long> currentValue=valueState.value();
            if(currentValue==null){
                currentValue=Tuple2.of(0L,0L);
            }
            currentValue.f0+=1;
            currentValue.f1+=value.f1;
            valueState.update(currentValue);
            //大于三个
            if(currentValue.f0>=3){
                collector.collect(Tuple2.of(value.f0,currentValue.f1/currentValue.f0));
                valueState.clear();
            }
        }

        @Override
        public void open(Configuration parameters) throws Exception {
            super.open(parameters);

            //keyedState可以设置TTL过期时间
            StateTtlConfig config=StateTtlConfig
                    .newBuilder(Time.seconds(30))
                    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
                    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
                    .build();

            ValueStateDescriptor valueStateDescriptor=new ValueStateDescriptor("agvKeyedState",
                    TypeInformation.of(new TypeHint<Tuple2<Long,Long>>() {}));

            //设置支持TTL配置
            valueStateDescriptor.enableTimeToLive(config);

            valueState=getRuntimeContext().getState(valueStateDescriptor);
        }
    }
}

Natasha
21 声望7 粉丝