【Flink】状態管理

目次

1. ステータスの概要

1.1 ステートレスオペレーター

1.2 ステートフル演算子

2. ステータスの分類

2.1 オペレータステータスの編集

2.1.1 リストステート (ListState)

2.1.2 ユニオンリスト状態(UnionListState)

2.1.3 ブロードキャスト状態(BroadcastState)

2.2 キーパーティションのステータス 

2.2.1 値の状態 (ValueState)

2.2.2 リストステート (ListState)

2.2.3 マップ状態(MapState)

2.2.4 還元状態(ReducingState)

2.2.5 集約状態

2.2.6 状態の生存時間 (TTL)

3. 状態バックエンド

3.1 ステートバックエンドの分類(HashMapStateBackend/RocksDB)

3.1.1 HashMapStateバックエンド

3.1.2 埋め込み RocksDB 状態バックエンド (EmbeddedRocksDBStateBackend)

3.2 適切なステータス バックエンドを選択する方法

3.3 ステータスバックエンドの構成


1. ステータスの概要

1.1 ステートレスオペレーター

現在の入力に応じて出力結果を直接変換できます。この種のノーズは、マップ、フラットマップ、フィルターなどのステートレスなオペレーターです。

1.2 ステートフル演算子

計算結果を取得するには、現在の処理に加えて追加の処理が必要です。集計演算子、ウィンドウ演算子など。

2. ステータスの分類

        Flink には、管理状態未加工状態の2 つの状態があります。管理状態はFlinkにより一元管理され、状態保存へのアクセス、障害回復、再構成などの一連の問題はすべてFlinkにより実装されるため、インターフェースを調整するだけで元の状態はカスタマイズされ、オープン化に等しいメモリの一部であるため、ステータスのシリアル化と障害回復を実現するには、それを自分で管理する必要があります。

        通常、ニーズを達成するために Flink 管理状態を使用します。

 

2.1 オペレータのステータス

          オペレータ タスクは、並列度に応じて実行のために複数の並列サブタスクに分割され、異なるサブタスクは異なるタスク スロットを占有します。異なるスロットはコンピューティング リソースの観点から物理的に分離されているため、Flink が管理できる状態は並列タスク間で共有できません。各状態は現在のサブタスクのインスタンスに対してのみ有効です。

オペレーター状態はオペレーター並列インスタンスで定義された状態であり、その範囲は現在のオペレーター タスクに制限されます。         

オペレーター状態の実際のアプリケーション シナリオはKeyed State ほど多くはありませんが、外部システムに接続される Source や Sink などのオペレーター、またはキー定義がまったくないシナリオで一般的に使用されます。たとえば、Flink の Kafka コネクタはオペレータ ステータスを使用します。

オペレーター状態はさまざまな構造タイプもサポートしており、主に ListState、UnionListState、BroadcastState の 3 つのタイプがあります。

2.1.1 リスト状態(ListState)

キー付き状態のリスト状態との違いは、オペレーター状態のコンテキストでは、状態がキーによって個別に処理されないため、各並列サブタスクで保持される「リスト」 (リスト) が 1 つだけであることです。これが現在のコレクションです。並列サブタスクのすべてのステータス項目の。リスト内のステータス項目は、再割り当て可能な最も詳細な項目であり、互いに完全に独立しています。

実践例:マップ演算子のデータ数を計算します。

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

        env
                .socketTextStream("hadoop102", 7777)
                .map(new MyCountMapFunction())
                .print();


        env.execute();
    }


    // TODO 1.实现 CheckpointedFunction 接口
    public static class MyCountMapFunction implements MapFunction<String, Long>, CheckpointedFunction {

        private Long count = 0L;
        private ListState<Long> state;


        @Override
        public Long map(String value) throws Exception {
            return ++count;
        }

        /**
         * TODO 2.本地变量持久化:将 本地变量 拷贝到 算子状态中,开启checkpoint时才会调用
         *
         * @param context
         * @throws Exception
         */
        @Override
        public void snapshotState(FunctionSnapshotContext context) throws Exception {
            System.out.println("snapshotState...");
            // 2.1 清空算子状态
            state.clear();
            // 2.2 将 本地变量 添加到 算子状态 中
            state.add(count);
        }

        /**
         * TODO 3.初始化本地变量:程序启动和恢复时, 从状态中 把数据添加到 本地变量,每个子任务调用一次
         *
         * @param context
         * @throws Exception
         */
        @Override
        public void initializeState(FunctionInitializationContext context) throws Exception {
            System.out.println("initializeState...");
            // 3.1 从 上下文 初始化 算子状态
            state = context
                    .getOperatorStateStore()
                    .getListState(new ListStateDescriptor<Long>("state", Types.LONG));

            // 3.2 从 算子状态中 把数据 拷贝到 本地变量
            if (context.isRestored()) {
                for (Long c : state.get()) {
                    count += c;
                }
            }
        }
    }
}

2.1.2 ユニオンリスト状態(UnionListState)

ListState と同様に、Union ListState も状態をリストとして表します。これと通常のリスト状態の違いは、演算子の並列処理がスケーリングおよび調整されるときに、状態が異なる方法で割り当てられることです。

UnionListState の焦点は「結合」です。並列処理を調整する場合、通常のリスト状態は状態項目を配布するためにポーリングされますが、リスト状態を結合する演算子は状態の完全なリストを直接ブロードキャストします。

リスト内のステータス項目が多すぎる場合、リソースと効率の理由から、一般に共同再編成を使用することはお勧めできません。

使用法は ListState と同じですが、次の点が異なります。getUnionListState (new ListStateDescriptor<Long>("union-state", Types.LONG));

state = context
              .getOperatorStateStore()
              .getUnionListState(new ListStateDescriptor<Long>("union-state", Types.LONG));

2.1.3 ブロードキャスト状態(BroadcastState)

場合によっては、オペレータの並列サブタスクが、統一された構成とルール設定のために同じ「グローバル」状態を維持することが必要になることがあります。

実践例:水位が指定されたしきい値を超えるとアラームが送信され、しきい値は動的に変更できます

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


        // 数据流
        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("hadoop102", 7777)
                .map(new WaterSensorMapFunction());

        // 配置流(用来广播配置)
        DataStreamSource<String> configDS = env.socketTextStream("hadoop102", 8888);

        // TODO 1. 将 配置流 广播
        MapStateDescriptor<String, Integer> broadcastMapState = new MapStateDescriptor<>("broadcast-state", Types.STRING, Types.INT);
        BroadcastStream<String> configBS = configDS.broadcast(broadcastMapState);

        // TODO 2.把 数据流 和 广播后的配置流 connect
        BroadcastConnectedStream<WaterSensor, String> sensorBCS = sensorDS.connect(configBS);

        // TODO 3.调用 process
        sensorBCS
                .process(
                        new BroadcastProcessFunction<WaterSensor, String, String>() {
                            /**
                             * 数据流的处理方法: 数据流 只能 读取 广播状态,不能修改
                             * @param value
                             * @param ctx
                             * @param out
                             * @throws Exception
                             */
                            @Override
                            public void processElement(WaterSensor value, ReadOnlyContext ctx, Collector<String> out) throws Exception {
                                // TODO 5.通过上下文获取广播状态,取出里面的值(只读,不能修改)
                                ReadOnlyBroadcastState<String, Integer> broadcastState = ctx.getBroadcastState(broadcastMapState);
                                Integer threshold = broadcastState.get("threshold");
                                // 判断广播状态里是否有数据,因为刚启动时,可能是数据流的第一条数据先来
                                threshold = (threshold == null ? 0 : threshold);
                                if (value.getVc() > threshold) {
                                    out.collect(value + ",水位超过指定的阈值:" + threshold + "!!!");
                                }

                            }

                            /**
                             * 广播后的配置流的处理方法:  只有广播流才能修改 广播状态
                             * @param value
                             * @param ctx
                             * @param out
                             * @throws Exception
                             */
                            @Override
                            public void processBroadcastElement(String value, Context ctx, Collector<String> out) throws Exception {
                                // TODO 4. 通过上下文获取广播状态,往里面写数据
                                BroadcastState<String, Integer> broadcastState = ctx.getBroadcastState(broadcastMapState);
                                broadcastState.put("threshold", Integer.valueOf(value));

                            }
                        }

                )
                .print();

        env.execute();
    }
}

2.2 キーパーティションのステータス 

多くのステートフル操作 (集計やウィンドウなど) では、まず keyBy をキーでパーティション化する必要があります。キーで分割した後、タスクによって実行されるすべての計算は現在のキーに対してのみ有効である必要があるため、状態もキーに従って互いに分離される必要があります。

その特徴は非常に特徴的であり、分離の範囲としてキーを使用することです。

Keyed State の使用は KeyedStream に基づく必要があることに注意してください。keyBy パーティショニングのない DataStream の場合、変換演算子が対応するリッチ関数クラスを実装している場合でも、ランタイム コンテキストを介してキー付き状態にアクセスすることはできません。

2.2.1 値の状態 (ValueState)

public interface ValueState<T> extends State {
    T value() throws IOException;
    void update(T value) throws IOException;
}
  • T value(): 現在の状態の値を取得します。
  • update(T value): ステータスを更新します。渡されるパラメータ値は、上書きされるステータス値です。

        特定の使用法では、実行時コンテキストにそれがどの状態であるかを知らせるために、状態に関する基本情報を提供する「状態記述子」(StateDescriptor) を作成する必要もあります。たとえば、ソース コードでは、ValueState の状態記述子の構築方法は次のとおりです。

public ValueStateDescriptor(String name, Class<T> typeClass) {
    super(name, typeClass, null);
}

ケース要件:各センサーの水位値を検出し、連続した2つの水位値が10を超えた場合にアラームを出力します。

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


        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("hadoop102", 7777)
                .map(new WaterSensorMapFunction())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner((element, ts) -> element.getTs() * 1000L)
                );

        sensorDS.keyBy(r -> r.getId())
                .process(
                        new KeyedProcessFunction<String, WaterSensor, String>() {

                            // TODO 1.定义状态
                            ValueState<Integer> lastVcState;


                            @Override
                            public void open(Configuration parameters) throws Exception {
                                super.open(parameters);
                                // TODO 2.在open方法中,初始化状态
                                // 状态描述器两个参数:第一个参数,起个名字,不重复;第二个参数,存储的类型
                                lastVcState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("lastVcState", Types.INT));
                            }

                            @Override
                            public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
//                                lastVcState.value();  // 取出 本组 值状态 的数据
//                                lastVcState.update(); // 更新 本组 值状态 的数据
//                                lastVcState.clear();  // 清除 本组 值状态 的数据


                                // 1. 取出上一条数据的水位值(Integer默认值是null,判断)
                                int lastVc = lastVcState.value() == null ? 0 : lastVcState.value();
                                // 2. 求差值的绝对值,判断是否超过10
                                Integer vc = value.getVc();
                                if (Math.abs(vc - lastVc) > 10) {
                                    out.collect("传感器=" + value.getId() + "==>当前水位值=" + vc + ",与上一条水位值=" + lastVc + ",相差超过10!!!!");
                                }
                                // 3. 更新状态里的水位值
                                lastVcState.update(vc);
                            }
                        }
                )
                .print();

        env.execute();
    }

2.2.2 リスト状態(ListState)

保存が必要なデータをリスト形式で整理します。ListState<T> インターフェイスには、リスト内のデータの型を表す型パラメーター T もあります。ListState は状態を操作するための一連のメソッドも提供しており、その使用法は一般的な List と非常に似ています。

  • Iterable<T> get(): 現在のリストのステータスを取得し、反復可能な型 Iterable<T> を返します。
  • update(List<T>values): 値のリストを渡し、ステータスを直接上書きします。
  • add(T value): ステータスリストに要素値を追加します。
  • addAll(List<T>values): リスト値として渡された複数の要素をリストに追加します。

同様に、ListState の状態記述子は ListStateDescriptor と呼ばれ、その使用法は ValueStateDescriptor とまったく同じです。

ケース:各センサーの水位の上位3値を出力

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


        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("hadoop102", 7777)
                .map(new WaterSensorMapFunction())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner((element, ts) -> element.getTs() * 1000L)
                );

        sensorDS.keyBy(r -> r.getId())
                .process(
                        new KeyedProcessFunction<String, WaterSensor, String>() {

                            ListState<Integer> vcListState;

                            @Override
                            public void open(Configuration parameters) throws Exception {
                                super.open(parameters);
                                vcListState = getRuntimeContext().getListState(new ListStateDescriptor<Integer>("vcListState", Types.INT));
                            }

                            @Override
                            public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
                                // 1.来一条,存到list状态里
                                vcListState.add(value.getVc());

                                // 2.从list状态拿出来(Iterable), 拷贝到一个List中,排序, 只留3个最大的
                                Iterable<Integer> vcListIt = vcListState.get();
                                // 2.1 拷贝到List中
                                List<Integer> vcList = new ArrayList<>();
                                for (Integer vc : vcListIt) {
                                    vcList.add(vc);
                                }
                                // 2.2 对List进行降序排序
                                vcList.sort((o1, o2) -> o2 - o1);
                                // 2.3 只保留最大的3个(list中的个数一定是连续变大,一超过3就立即清理即可)
                                if (vcList.size() > 3) {
                                    // 将最后一个元素清除(第4个)
                                    vcList.remove(3);
                                }

                                out.collect("传感器id为" + value.getId() + ",最大的3个水位值=" + vcList.toString());

                                // 3.更新list状态
                                vcListState.update(vcList);


//                                vcListState.get();            //取出 list状态 本组的数据,是一个Iterable
//                                vcListState.add();            // 向 list状态 本组 添加一个元素
//                                vcListState.addAll();         // 向 list状态 本组 添加多个元素
//                                vcListState.update();         // 更新 list状态 本组数据(覆盖)
//                                vcListState.clear();          // 清空List状态 本组数据
                            }
                        }
                )
                .print();

        env.execute();
    }
}

2.2.3 マップ状態(MapState)

いくつかのキーと値のペアを状態全体として保存することは、キーと値のマッピングのリストと考えることができます。

MapState は、Map の使用と非常によく似た、マッピング状態を操作するメソッドを提供します。

  • UV get(UK key): キーをパラメータとして渡し、対応する値をクエリします。
  • put(UK key, UV value): キーと値のペアを渡し、キーに対応する値を更新します。
  • putAll(Map<UK, UV> map): 受信マッピング マップ内のすべてのキーと値のペアをマッピング状態に追加します。
  • Remove(UK key): 指定されたキーに対応するキーと値のペアを削除します。
  • boolean contains(UK key): 指定されたキーが存在するかどうかを判断し、ブール値を返します。

さらに、MapState はマッピング全体に関連する情報を取得するメソッドも提供します。

  • Iterable<Map.Entry<UK, UV>> events(): マッピング状態にあるすべてのキーと値のペアを取得します。
  • Iterable<UK> key(): マッピング状態にあるすべてのキーを取得し、反復可能な Iterable 型を返します。
  • Iterable<UV>values(): マッピング状態のすべての値 (value) を取得し、反復可能な Iterable 型を返します。
  • boolean isEmpty(): マッピングが空かどうかを判断し、ブール値を返します。

ケース要件:各センサーの各水位値の発生数をカウントします。

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


        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("hadoop102", 7777)
                .map(new WaterSensorMapFunction())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner((element, ts) -> element.getTs() * 1000L)
                );

        sensorDS.keyBy(r -> r.getId())
                .process(
                        new KeyedProcessFunction<String, WaterSensor, String>() {

                            MapState<Integer, Integer> vcCountMapState;

                            @Override
                            public void open(Configuration parameters) throws Exception {
                                super.open(parameters);
                                vcCountMapState = getRuntimeContext().getMapState(new MapStateDescriptor<Integer, Integer>("vcCountMapState", Types.INT, Types.INT));
                            }

                            @Override
                            public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
                                // 1.判断是否存在vc对应的key
                                Integer vc = value.getVc();
                                if (vcCountMapState.contains(vc)) {
                                    // 1.1 如果包含这个vc的key,直接对value+1
                                    Integer count = vcCountMapState.get(vc);
                                    vcCountMapState.put(vc, ++count);
                                } else {
                                    // 1.2 如果不包含这个vc的key,初始化put进去
                                    vcCountMapState.put(vc, 1);
                                }

                                // 2.遍历Map状态,输出每个k-v的值
                                StringBuilder outStr = new StringBuilder();
                                outStr.append("======================================\n");
                                outStr.append("传感器id为" + value.getId() + "\n");
                                for (Map.Entry<Integer, Integer> vcCount : vcCountMapState.entries()) {
                                    outStr.append(vcCount.toString() + "\n");
                                }
                                outStr.append("======================================\n");

                                out.collect(outStr.toString());


//                                vcCountMapState.get();          // 对本组的Map状态,根据key,获取value
//                                vcCountMapState.contains();     // 对本组的Map状态,判断key是否存在
//                                vcCountMapState.put(, );        // 对本组的Map状态,添加一个 键值对
//                                vcCountMapState.putAll();  // 对本组的Map状态,添加多个 键值对
//                                vcCountMapState.entries();      // 对本组的Map状态,获取所有键值对
//                                vcCountMapState.keys();         // 对本组的Map状态,获取所有键
//                                vcCountMapState.values();       // 对本组的Map状态,获取所有值
//                                vcCountMapState.remove();   // 对本组的Map状态,根据指定key,移除键值对
//                                vcCountMapState.isEmpty();      // 对本组的Map状态,判断是否为空
//                                vcCountMapState.iterator();     // 对本组的Map状态,获取迭代器
//                                vcCountMapState.clear();        // 对本组的Map状态,清空

                            }
                        }
                )
                .print();

        env.execute();
    }
}

2.2.4 還元状態(ReducingState)

リダクション ロジックの定義は、リダクション状態記述子 (ReducingStateDescriptor) でリダクション関数 (ReduceFunction) を渡すことによって実装されます。ここでのリダクション関数は、以前にリデュース集計演算子を紹介したときに言及した ReduceFunction であるため、状態型は入力データ型と同じです。

public ReducingStateDescriptor(
    String name, ReduceFunction<T> reduceFunction, Class<T> typeClass) {...}

事例:各センサーの水位の合計を計算する

.process(new KeyedProcessFunction<String, WaterSensor, Integer>() {
    private ReducingState<Integer> sumVcState;
    @Override
    public void open(Configuration parameters) throws Exception {
        sumVcState = this
          .getRuntimeContext()
          .getReducingState(new ReducingStateDescriptor<Integer>("sumVcState",Integer::sum,Integer.class));
    }

    @Override
    public void processElement(WaterSensor value, Context ctx, Collector<Integer> out) throws Exception {
        sumVcState.add(value.getVc());
        out.collect(sumVcState.get());
    }
})

2.2.5 集約状態 (AggregatingState)

集約状態もリダクション状態と非常によく似ており、追加されたすべてのデータの集約結果を保持する値です。ReducingState とは異なり、その集計ロジックは、記述子でより一般的な集計関数 (AggregateFunction) を渡すことによって定義されます。これは、以前に説明した AggregateFunction であり、アキュムレータ (Accumulator) を使用してステータスを表すため、集計ステータスのタイプは次のようになります。追加されたデータ型とはまったく異なるため、より柔軟に使用できます。

ケース要件:各センサーの平均水位を計算する

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


        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("hadoop102", 7777)
                .map(new WaterSensorMapFunction())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner((element, ts) -> element.getTs() * 1000L)
                );

        sensorDS.keyBy(r -> r.getId())
                .process(
                        new KeyedProcessFunction<String, WaterSensor, String>() {

                            AggregatingState<Integer, Double> vcAvgAggregatingState;

                            @Override
                            public void open(Configuration parameters) throws Exception {
                                super.open(parameters);
                                vcAvgAggregatingState = getRuntimeContext()
                                        .getAggregatingState(
                                                new AggregatingStateDescriptor<Integer, Tuple2<Integer, Integer>, Double>(
                                                        "vcAvgAggregatingState",
                                                        new AggregateFunction<Integer, Tuple2<Integer, Integer>, Double>() {
                                                            @Override
                                                            public Tuple2<Integer, Integer> createAccumulator() {
                                                                return Tuple2.of(0, 0);
                                                            }

                                                            @Override
                                                            public Tuple2<Integer, Integer> add(Integer value, Tuple2<Integer, Integer> accumulator) {
                                                                return Tuple2.of(accumulator.f0 + value, accumulator.f1 + 1);
                                                            }

                                                            @Override
                                                            public Double getResult(Tuple2<Integer, Integer> accumulator) {
                                                                return accumulator.f0 * 1D / accumulator.f1;
                                                            }

                                                            @Override
                                                            public Tuple2<Integer, Integer> merge(Tuple2<Integer, Integer> a, Tuple2<Integer, Integer> b) {
//                                                                return Tuple2.of(a.f0 + b.f0, a.f1 + b.f1);
                                                                return null;
                                                            }
                                                        },
                                                        Types.TUPLE(Types.INT, Types.INT))
                                        );
                            }

                            @Override
                            public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
                                // 将 水位值 添加到  聚合状态中
                                vcAvgAggregatingState.add(value.getVc());
                                // 从 聚合状态中 获取结果
                                Double vcAvg = vcAvgAggregatingState.get();

                                out.collect("传感器id为" + value.getId() + ",平均水位值=" + vcAvg);

//                                vcAvgAggregatingState.get();    // 对 本组的聚合状态 获取结果
//                                vcAvgAggregatingState.add();    // 对 本组的聚合状态 添加数据,会自动进行聚合
//                                vcAvgAggregatingState.clear();  // 对 本组的聚合状态 清空数据
                            }
                        }
                )
                .print();

        env.execute();
    }
}

2.2.6 状態の生存時間 (TTL)

実際のアプリケーションでは、多くの状態は時間の経過とともに徐々に増加し、制限しないと最終的にはストレージ スペースの枯渇につながります。

状態の TTL を構成するときは、StateTtlConfig 構成オブジェクトを作成し、状態記述子の .enableTimeToLive() メソッドを呼び出して TTL 関数を開始する必要があります。

StateTtlConfig ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(10))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .build();

ValueStateDescriptor<String> stateDescriptor = new ValueStateDescriptor<>("my state", String.class);

stateDescriptor.enableTimeToLive(ttlConfig);

ここではいくつかの構成項目が使用されます。

  • .newBuilder()

状態 TTL 構成のコンストラクター メソッドを呼び出す必要があります。Builder を返した後、.build() メソッドを呼び出して StateTtlConfig を取得します。このメソッドは、設定された状態の生存時間である Time をパラメーターとして渡す必要があります

  • .setUpdateType()

更新タイプを設定します。更新タイプは、状態の有効期限をいつ更新するかを指定します。ここで OnCreateAndWrite は、状態の作成時と状態の変更 (書き込み操作) のときにのみ有効期限が更新されることを意味します。もう 1 つのタイプである OnReadAndWrite は、読み取りまたは書き込み操作に関係なく有効期限が更新されることを示します。つまり、状態にアクセスされている限り、その状態がアクティブであることを示し、生存時間を延長します。この構成のデフォルトは OnCreateAndWrite です。

  • .setStateVisibility()

ステータスの表示/非表示を設定します。いわゆる「状態の可視性」とは、クリア操作がリアルタイムではないため、状態が期限切れになった後も状態が存在し続ける可能性があり、その際にアクセスした場合に正常に読み取れるかどうかが問題となります。ここで設定された NeverReturnExpired はデフォルトの動作であり、期限切れの値は決して返されないことを意味します。つまり、期限切れである限り、値はクリアされたとみなされ、アプリケーションは読み取りを続行できません。これは、処理する場合により重要です。セッションまたはプライベートデータを使用します。対応するもう 1 つの設定は ReturnExpireDefNotCleanedUp で、期限切れステータスがまだ存在する場合にその値を返します。

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


        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("hadoop102", 7777)
                .map(new WaterSensorMapFunction())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                .withTimestampAssigner((element, ts) -> element.getTs() * 1000L)
                );

        sensorDS.keyBy(r -> r.getId())
                .process(
                        new KeyedProcessFunction<String, WaterSensor, String>() {

                            ValueState<Integer> lastVcState;


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

                                // TODO 1.创建 StateTtlConfig
                                StateTtlConfig stateTtlConfig = StateTtlConfig
                                        .newBuilder(Time.seconds(5)) // 过期时间5s
//                                        .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite) // 状态 创建和写入(更新) 更新 过期时间
                                        .setUpdateType(StateTtlConfig.UpdateType.OnReadAndWrite) // 状态 读取、创建和写入(更新) 更新 过期时间
                                        .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired) // 不返回过期的状态值
                                        .build();

                                // TODO 2.状态描述器 启用 TTL
                                ValueStateDescriptor<Integer> stateDescriptor = new ValueStateDescriptor<>("lastVcState", Types.INT);
                                stateDescriptor.enableTimeToLive(stateTtlConfig);


                                this.lastVcState = getRuntimeContext().getState(stateDescriptor);

                            }

                            @Override
                            public void processElement(WaterSensor value, Context ctx, Collector<String> out) throws Exception {
                                // 先获取状态值,打印 ==》 读取状态
                                Integer lastVc = lastVcState.value();
                                out.collect("key=" + value.getId() + ",状态值=" + lastVc);

                                // 如果水位大于10,更新状态值 ===》 写入状态
                                if (value.getVc() > 10) {
                                    lastVcState.update(value.getVc());
                                }
                            }
                        }
                )
                .print();

        env.execute();
    }
}

3.状態バックエンド

Flink では、状態の保存、アクセス、メンテナンスはすべて、状態バックエンドと呼ばれるプラグイン可能なコンポーネントによって決定されます。状態バックエンドは主に、ローカル状態の保存方法と場所の管理を担当します。

3.1 ステートバックエンドの分類 ( HashMapStateBackend/RocksDB )

Flink は 2 つの異なるタイプの状態バックエンドを提供します。1 つは「HashMapStateBackend」、もう 1 つは「EmbeddedRocksDBStateBackend」です。

システムのデフォルトの状態バックエンドは HashMapStateBackend です。

3.1.1 HashMapStateバックエンド

HashMapStateBackend は状態をメモリに保存します。具体的な実装に関しては、ハッシュ テーブル ステータス バックエンドは内部的にステータスをオブジェクトとして直接処理し、Taskmanager の JVM ヒープに保存します。

通常の状態、およびウィンドウ内で収集されたデータとトリガーは、キーと値のペアの形式で保存されるため、最下層はハッシュ テーブル (HashMap) となり、この状態バックエンドが名前が付けられているのはこのためです。

3.1.2 埋め込み RocksDB 状態バックエンド (EmbeddedRocksDBStateBackend)

RocksDB は、データをローカル ハード ディスクに永続化できる組み込みのキーと値のストレージ メディアです

EmbeddedRocksDBStateBackend を設定した後、処理されるすべてのデータは RocksDB データベースに置かれます。RocksDB はデフォルトで TaskManager のローカル データ ディレクトリに保存されます

3.2 適切なステータス バックエンドを選択する方法

HashMap と RocksDB の 2 つの状態バックエンドの最大の違いは、ローカル状態が保存される場所です。

HashMapStateBackend は、非常に高速な読み取りおよび書き込み速度を備えたインメモリ計算です。ただし、状態のサイズはクラスターの利用可能なメモリによって制限されます。アプリケーションの状態が時間の経過とともに増大し続けると、メモリ リソースが枯渇します。

RocksDB はハードディスク ストレージであるため、利用可能なディスク容量に応じて拡張できるため、超大規模な状態ストレージに非常に適しています。ただし、各状態の読み取りおよび書き込みにはシリアル化/逆シリアル化が必要であり、データをディスクから直接読み取る必要がある場合があるため、パフォーマンスの低下につながります。読み取りおよび書き込みの平均パフォーマンスは、HashMapStateBackend よりも 1 桁遅くなります。

3.3 ステータスバックエンドの構成

3.3.1 デフォルトのステータス バックエンドの構成

#flink-conf.yaml

# 默认状态后端
state.backend: hashmap

# 存放检查点的文件路径
# 这里的state.checkpoints.dir配置项,定义了检查点和元数据写入的目录。
state.checkpoints.dir: hdfs://hadoop102:8020/flink/checkpoints

3.3.2 ジョブごとにステータス バックエンドを個別に構成する (ジョブごと/アプリケーションごと)

環境HashMapStateBackendを実行することで設定されます

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

env.setStateBackend(new HashMapStateBackend());

環境 EmbeddedRocksDBStateBackend を実行することで設定されます。

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

env.setStateBackend(new EmbeddedRocksDBStateBackend());

IDE で EmbeddedRocksDBStateBackend を使用する場合は、Flink プロジェクトに依存関係を追加する必要があることに注意してください。

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-statebackend-rocksdb</artifactId>
    <version>${flink.version}</version>
</dependency>

おすすめ

転載: blog.csdn.net/weixin_38996079/article/details/134587324