Flink1.11 有状态算子的使用

本文介绍了Apache Flink中如何在Java API中使用键控状态,重点讲解了ValueState、ReducingState、ListState和AggregatingState的区别,以及State TTL的概念,通过实例演示了配置和操作过程。

1.前言

时间:2020年11月29日

在Flink中所有算子(如map,flatmap,reduce等等)都可以是有状态的。用Scala写起来会有一些骚操作,比如使用lazy定义descriptor等。但是这里暂时不会讲到,本文以Java API为主。

有状态操作大致可以分为Key State(键控状态)和Operator State(算子状态),由于键控状态比较常用,本文会以键控状态为主进行总结。

状态算子跟状态后端有一定的关系(算子的状态是存在状态后端当中的,因此实际开发中可能需要先设置使用何种状态后端),之前写过一篇文章Flink1.11 状态后端说明及部分源码阅读有需要可以参考。

2.键控状态

既然是键控状态,首先一定要进行keyBy操作,将DataStream变成KeyedDataStream。然后才能使用键控状态。下面先使用ValueState的例子入门,然后详细说明。

2.1ValueState demo

package it.kenn.state;

import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.runtime.state.StateBackend;
import org.apache.flink.runtime.state.filesystem.FsStateBackend;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

public class KeyedStateDemo {
    public static void main(String[] args) throws Exception {
        String backendPath = "hdfs://localhost:8020/flink/statebackend/fsState";
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(6);
        //设置状态后端为hdfs
        StateBackend stateBackend = new FsStateBackend(backendPath);
        env.setStateBackend(stateBackend);
        env.fromElements(Tuple2.of(1L, 3L), Tuple2.of(1L, 5L), Tuple2.of(1L, 7L), Tuple2.of(1L, 4L), Tuple2.of(1L, 2L))
                //进行keyBy操作
                .keyBy(value -> value.f0)
                //有状态的flatMap
                .flatMap(new CountWindowAverage())
                //简单的输出
                .print();
        env.execute();
    }
}

编写具体的flatmap函数

package it.kenn.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.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.util.Collector;

/**
 * 下面是一个ValueState的例子
 */
//需要注意的是要使用RichFlatMapFunction,因为Rich抽象类里面有生命周期函数,比如下面要用到的open
public class CountWindowAverage extends RichFlatMapFunction<Tuple2<Long, Long>, Tuple2<Long, Long>> {
    private transient ValueState<Tuple2<Long, Long>> sum;

    @Override
    public void flatMap(Tuple2<Long, Long> input, Collector<Tuple2<Long, Long>> collector) throws Exception {
        //这里编写的是真正的业务代码,我们要做的是对传入的值进行累加,并记录有多少个值进行了累加
        Tuple2<Long, Long> currValue = sum.value();
        currValue.f0 += 1;
        currValue.f1 += input.f1;
        //更新状态
        sum.update(currValue);
        //计算平均值
        if (currValue.f0 >= 2) {
            collector.collect(new Tuple2<>(input.f0, currValue.f1 / currValue.f0));
            sum.clear();
        }
    }

    /**
     * 在open函数里面可以使用getRuntimeContext函数来getState,但在此之前需要定义ValueStateDescriptor
     * ValueStateDescriptor有三个参数,第一个为该state起名字,第二个定义该state的类型,第三个初始化值
     * @param parameters
     */
    @Override
    public void open(Configuration parameters) {
        ValueStateDescriptor<Tuple2<Long, Long>> descriptor = new ValueStateDescriptor<>(
                "average",
                TypeInformation.of(new TypeHint<Tuple2<Long, Long>>() {})
                ,new Tuple2<>(0L,0L)
        );
        //第一次调用的时候完成对ValueState初始化并得到初始值
        sum = getRuntimeContext().getState(descriptor);

    }
}

2.2ValueState使用总结

有些内容在上面例子的注释中已经讲过了,这里再总结一下。

1. 要使用键控状态,首先要执行keyBy操作,指定DataStream的键。但是这个键是一个虚拟的键,即指定字段中哪个字段当做键就可以了,不需要使用真正的键值数据结构(这个在spark中好像使用专门的数据结构的,可以翻一下验证)

2. 状态必须要通过RuntimeContext来获得,而RuntimeContext只能在RichFunction中才能得到(在Scala中可以定义为lazy类型不需要在RichFunction中得到,这里不讲),因此有状态的算子比如上面的flatmap必须继承Rich...Function。

3. 要得到一个状态句柄需要创建一个StateDescriptor,ValueState的StateDescriptor叫做ValueStateDescriptor。创建需要三个参数,上面例子中也解释过了。

4. 获取状态需要使用ValueState<T> getState(ValueStateDescriptor<T>)需要注意的是,这里得到的是ValueState这个结构,并不是存储的状态值

5.得到结构以后就可以通过value()方法得到值了。更新状态值使用update()。清空状态值使用clear()方法。当然这是针对ValueState说的,其他类型的State(下一节说)获取、更新值还有一些区别

2.3键控状态分类

上面已经使用ValueState做了简单的入门,下面键控状态的分类。

2.3.1ValueState

  • 获取ValueState:ValueState<T> getState(ValueStateDescriptor<T>)
  • 得到、更新、清空值:value()方法、update(T)方法、clear()方法

2.3.2ReducingState

它保存一个值,这个值表示所有添加到该状态的汇总值。比如对某个值进行累加,那么存储的就是当前数据过来以后截止目前数据的累加值。需要注意的是,它的入参类型和返回值类型要完全一致。

  • 获取ReducingState:ReducingState<T> getReducingState(ReducingStateDescriptor<T>)
  • 得到、更新、清空值:

2.3.3ListState

它保存了一组值。

  • 获取ListState:ListState<T> getListState(ListStateDescriptor<T>)
  • 添加值:add(T)或者addAll()方法;得到Iterable:Iterable<T> get();更新值:update(List<T>)。

2.3.4AggregatingState

与ReducingState最大的不同是它的入参和出参可以是不同的类型(从下面泛型中也可以看出来)

  • AggregatingState<IN, OUT> getAggregatingState(AggregatingStateDescriptor<IN, ACC, OUT>)

2.3.5MapState

它可以保存一个映射列表。可以向MapState中放入一个键值对数据。

  • 获取MapState:MapState<UK, UV> getMapState(MapStateDescriptor<UK, UV>)
  • 添加元素:put(UK, UV) 或者 putAll(Map<UK, UV>);获取元素:get(UK)或者后面这三个方法entries(),、keys()、 values()跟Java基本API差不多不赘述;清空值clear()
  • 如果MapState的键值本身是很复杂的类型,在创建的时候需要用TypeInformation。具体如下:​​​​​​​
private static MapState<String, Tuple2<Long,String>> behaviorMapState;

    @Override
    public void open(Configuration parameters) throws Exception {
        behaviorMapState = getRuntimeContext().getMapState(new MapStateDescriptor<>
                        ("behaviorMapState", TypeInformation.of(new TypeHint<String>(){}),TypeInformation.of(new TypeHint<Tuple2<Long,String>>(){})));
    }

3.State TTL

随着时间的延续,算子要维护的状态可能非常多,对内存或者磁盘都有一定的压力,Flink设置了一种算子状态过期机制,可以让不再需要的状态在一段时间以后过期。如果对键控状态设置了过期时间,相应的状态会在一段时间以后被清除(不一定是过期以后立即被清除!)。

3.2Demo

package it.kenn.state;

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

/**
 * 状态过期时间配置
 */
public class StateTTLDemo {
    public static void main(String[] args) {
        StateTtlConfig ttlConfig = StateTtlConfig
                //过期时间,必须的值
                .newBuilder(Time.seconds(10))
                //默认值,还可以是OnReadAndWrite
                .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
                //配置如果状态已经过期了是否返回,下面的配置是说如果状态过期了就不返回了
                .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
                .build();
        ValueStateDescriptor<String> text_state = new ValueStateDescriptor<>("text state", String.class);
        //为该状态指定过期配置
        text_state.enableTimeToLive(ttlConfig);
    }
}

上面是一个简单的TTL小demo。更详细的内容看官网吧。

<think> 首先,用户的问题是关于如何在Flink代码中设置状态TTL(Time To Live)。我需要基于提供的引用内容来回答。 回顾系统级指令: - 所有行内数学表达式必须用$...$,但这里可能没有数学表达式,所以可能不适用。 - 独立公式用$$...$$,但同样,这里可能不需要。 - LaTeX语法正确:确保任何数学表达式的格式正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 用户提供的引用: - [^1]: 来自Flink State TTL 详解,展示了设置TTL的代码示例。 - [^2]: 来自Flink之状态TTL机制内容详解,解释了TTL机制。 - [^3]: 来自Flink TTL,另一个代码示例。 - [^4]: 来自Flink状态计算TTL,描述了TTL的应用场景。 用户的问题:"我想查找在Flink代码中设置状态TTL的方法 请问Flink代码中如何设置状态TTL" 所以,我需要解释如何在Flink代码中设置状态TTL。基于引用,设置TTL的步骤如下: 1. 导入必要的类。 2. 创建一个StateDescriptor(如ValueStateDescriptor)。 3. 创建一个StateTtlConfig对象,配置TTL参数。 4. 使用stateDescriptor.enableTimeToLive(ttlConfig)启用TTL。 从[^1]和[^3]中,代码示例类似: - StateTtlConfig.newBuilder(Time.seconds(1))设置TTL时间。 - .setUpdateType(...)设置更新类型(如OnCreateAndWrite)。 - .setStateVisibility(...)设置状态可见性(如NeverReturnExpired)。 - .cleanupFullSnapshot()或其他清理选项。 - 然后build()配置。 - 最后,stateDescriptor.enableTimeToLive(ttlConfig)。 在回答中,我需要结构化: - 逐步解释设置过程。 - 提供代码示例。 - 引用来源。 结构回答: 1. 介绍:简要解释什么是状态TTL(基于[^2])。 2. 设置步骤:分步说明。 3. 代码示例:使用引用中的代码,但可能需要简化或解释。 4. 解释关键配置:如时间单位、更新类型、状态可见性。 5. 生成相关问题。 确保回答在中文。 生成相关问题: - 基于主题,如TTL配置选项、清理策略、应用场景等。 - 参考系统指令的示例:"§§相关问题§§"后列出问题。 添加引用标识: - 在相关部分末尾添加[^1], [^2]等。 最终回答结构: - 回答用户问题。 - 提供清晰的步骤和代码。 - 在末尾添加相关问题。 </think> ### Flink代码中设置状态TTL的方法 在Apache Flink中,状态TTL(Time To Live)是一种自动删除过期状态数据的机制,常用于优化内存使用和防止状态数据累积。设置状态TTL的核心是通过`StateTtlConfig`配置状态描述符(如`ValueStateDescriptor`)。以下是基于官方文档和最佳实践的逐步说明(使用Java API): #### 步骤1: 导入必要的类 首先,导入Flink状态管理相关的类: ```java import org.apache.flink.api.common.state.StateTtlConfig; import org.apache.flink.api.common.state.ValueStateDescriptor; // 或其他状态描述符,如ListStateDescriptor import org.apache.flink.api.common.time.Time; ``` #### 步骤2: 创建状态描述符 定义一个状态描述符,例如`ValueStateDescriptor`,用于存储状态数据的类型和名称: ```java ValueStateDescriptor<Long> stateDescriptor = new ValueStateDescriptor<>( "LastLoginState", // 状态名称 Long.class // 状态数据类型 ); ``` #### 步骤3: 配置TTL参数 使用`StateTtlConfig.newBuilder()`构建TTL配置。关键参数包括: - **TTL时间**:通过`newBuilder(Time.时间单位)`设置过期时间(例如`Time.minutes(1)`表示1分钟后过期)。 - **更新时间类型**:`setUpdateType()`定义何时重置TTL计时器,常用`StateTtlConfig.UpdateType.OnCreateAndWrite`(仅在创建或写入时更新)。 - **状态可见性**:`setStateVisibility()`控制过期数据是否可见,推荐`StateTtlConfig.StateVisibility.NeverReturnExpired`(永不返回过期数据)[^1][^3]。 - **清理策略**:添加清理方法,如`.cleanupFullSnapshot()`(全量快照时清理)或`.cleanupIncrementally()`(增量清理)[^1][^2]。 完整配置示例: ```java StateTtlConfig ttlConfig = StateTtlConfig.newBuilder(Time.minutes(1)) // TTL为1分钟 .setTtlTimeCharacteristic(StateTtlConfig.TtlTimeCharacteristic.ProcessingTime) // 使用处理时间 .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite) // 创建/写入时更新TTL .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired) // 隐藏过期数据 .cleanupFullSnapshot() // 全量快照时清理过期数据 .build(); // 构建配置 ``` #### 步骤4: 启用TTL并应用状态描述符 将TTL配置附加到状态描述符: ```java stateDescriptor.enableTimeToLive(ttlConfig); ``` 然后,在Flink算子(如`RichFlatMapFunction`)中使用该描述符注册状态: ```java public class MyOperator extends RichFlatMapFunction<InputType, OutputType> { private ValueState<Long> state; @Override public void open(Configuration parameters) { state = getRuntimeContext().getState(stateDescriptor); // 启用TTL的状态 } @Override public void flatMap(InputType value, Collector<OutputType> out) { // 使用state.value()读取或state.update()写入状态 // 过期数据会自动处理 } } ``` #### 关键说明 - **时间语义**:TTL支持`ProcessingTime`(处理时间)或`EventTime`(事件时间),但需注意Flink 1.11及更早版本主要支持处理时间[^4]。 - **性能影响**:设置过长TTL可能导致状态膨胀,过短则可能误删数据。建议根据业务需求测试优化。 - **清理机制**:除`.cleanupFullSnapshot()`外,还可使用`cleanupInBackground()`进行后台清理以减少开销[^2]。 通过以上步骤,Flink会在状态数据过期后自动删除,适用于会话管理、窗口计算等场景[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值