State:一般指一个具体的task/operator的状态。
Keyed State(keyBy):托管状态、原始状态 工作中用的较多
Operator State(unKeyBy):托管状态、原始状态 工作中用的较少
原始状态工作中基本用不到
Keyed State六种托管状态
Flatmap正常情况下实现FlatMapFunction然后重写flatMap<>方法,为了更丰富的功能另一种方式是继承RichFlatMapFunction
ValueState保存的是对应的一个key的一个状态值
在flatMap中对上面的countAndSum进行业务逻辑操作
SparkStreaming中updateStateByKey、mapWithState管理状态有限
ListState保存的是对应的一个key出现的所有的元素
MapState:Map数据结构,key相同会覆盖value的值。将map转换成list进行操作Lists.newArrayList(mapState.value());
利用flatMap/map+state自定义出功能丰富的算子
state:一般指一个具体的task/operator的状态。State可以被记录,在失败的情况下数据还可以恢复,Flink中有两种基本类型的State:Keyed State,Operator State,他们两种都可以以两种形式存在:原始状态(raw state)和托管状态(managed state)
托管状态:由Flink框架管理的状态,我们通常使用的就是这种。
原始状态:由用户自行管理状态具体的数据结构,框架在做checkpoint的时候,使用byte[]来读写状态内容,对其内部数据结构一无所知。通常在DataStream上的状态推荐使用托管的状态,当实现一个用户自定义的operator时,会使用到原始状态。但是我们工作中一般不常用,所以我们不考虑他。
用一个简单的例子,来说明flink状态state的具体存在:
/**
* 单词计数
*/
public class WordCount {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> data = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Tuple2<String, Integer>> result = data.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
@Override
public void flatMap(String line, Collector<Tuple2<String, Integer>> collector) throws Exception {
String[] fields = line.split(",");
for (String word : fields) {
collector.collect(new Tuple2<>(word, 1));
}
}
}).keyBy("0")
.sum(1);
result.print();
env.execute("WordCount");
}
}
#输入
hadoop,hadoop
hadoop
hive,hadoop
#输出
4> (hadoop,1)
4> (hadoop,2)
4> (hadoop,3)
1> (hive,1)
4> (hadoop,4)
单词出现的次数有累计的效果。如果没有状态的管理,是不会有累计的效果的,所以Flink里面是有state概念的。
Operator State
Keyed State
dataStreamSource
.keyBy(0)
.flatMap(new CountWindowAverageWithValueState())
//.flatMap(new CountWindowAverageWithListState())
//.flatMap(new CountWindowAverageWithMapState())
//.flatMap(new SumFunction())
//.flatMap(new ContainsValueFunction())
.print();
自定义flatMap方法继承RichFlatMapFunction< T >,然后重写open方法,flatMap方法。
在open方法中注册状态:
一是状态描述,二是从上下文获取状态
ValueStateDescriptor<Tuple2<Long, Long>> descriptor =
new ValueStateDescriptor<Tuple2<Long, Long>>(
"average", // 状态的名字
Types.TUPLE(Types.LONG, Types.LONG)); // 状态存储的数据类型
countAndSum = getRuntimeContext().getState(descriptor);
ListStateDescriptor<Tuple2<Long, Long>> descriptor =
new ListStateDescriptor<Tuple2<Long, Long>>(
"average", // 状态的名字
Types.TUPLE(Types.LONG, Types.LONG)); // 状态存储的数据类型
elementsByKey = getRuntimeContext().getListState(descriptor);
MapStateDescriptor<String, Long> descriptor =
new MapStateDescriptor<String, Long>(
"average", // 状态的名字
String.class, Long.class); // 状态存储的数据类型
mapState = getRuntimeContext().getMapState(descriptor);
ReducingStateDescriptor<Long> descriptor =
new ReducingStateDescriptor<Long>(
"sum", // 状态的名字
new ReduceFunction<Long>() { // 聚合函数
@Override
public Long reduce(Long value1, Long value2) throws Exception {
return value1 + value2;
}
}, Long.class); // 状态存储的数据类型
sumState = getRuntimeContext().getReducingState(descriptor);
AggregatingStateDescriptor<Long, String, String> descriptor =
new AggregatingStateDescriptor<Long, String, String>(
"totalStr", // 状态的名字
new AggregateFunction<Long, String, String>() {
@Override
public String createAccumulator() {
return "Contains:";
}
@Override
public String add(Long value, String accumulator) {
if ("Contains:".equals(accumulator)) {
return accumulator + value;
}
return accumulator + " and " + value;
}
@Override
public String getResult(String accumulator) {
return accumulator;
}
@Override
public String merge(String a, String b) {
return a + " and " + b;
}
}, String.class); // 状态存储的数据类型
totalStr = getRuntimeContext().getAggregatingState(descriptor);
在flatmap方法内,做状态操作
ValueState :这个状态为每一个 key 保存一个值
value() 获取状态值
update() 更新状态值
clear() 清除状态
ListState :这个状态为每一个 key 保存集合的值
get() 获取状态值
add() / addAll() 更新状态值,将数据放到状态中
clear() 清除状态
MapState
put() 将对应的 key 的键值对放到状态中
values() 拿到 MapState 中所有的 value
clear() 清除状态
ReducingState :这个状态为每一个 key 保存一个聚合之后的值
get() 获取状态值
add() 更新状态值,将数据放到状态中
clear() 清除状态
需求:将两个流中,订单号一样的数据合并在一起输出
public class OrderStream {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> info1 = env.addSource(new FileSource(Constants.ORDER_INFO1_PATH));
DataStreamSource<String> info2 = env.addSource(new FileSource(Constants.ORDER_INFO2_PATH));
KeyedStream<OrderInfo1, Long> orderInfo1Stream = info1.map(line -> string2OrderInfo1(line))
.keyBy(orderInfo1 -> orderInfo1.getOrderId());
KeyedStream<OrderInfo2, Long> orderInfo2Stream = info2.map(line -> string2OrderInfo2(line))
.keyBy(orderInfo2 -> orderInfo2.getOrderId());
orderInfo1Stream.connect(orderInfo2Stream)
.flatMap(new EnrichmentFunction())
.print();
env.execute("OrderStream");
}
/**
* IN1, 第一个流的输入的数据类型
IN2, 第二个流的输入的数据类型
OUT,输出的数据类型
*/
public static class EnrichmentFunction extends
RichCoFlatMapFunction<OrderInfo1,OrderInfo2,Tuple2<OrderInfo1,OrderInfo2>>{
//定义第一个流 key对应的state
private ValueState<OrderInfo1> orderInfo1State;
//定义第二个流 key对应的state
private ValueState<OrderInfo2> orderInfo2State;
@Override
public void open(Configuration parameters) {
orderInfo1State = getRuntimeContext()
.getState(new ValueStateDescriptor<OrderInfo1>("info1", OrderInfo1.class));
orderInfo2State = getRuntimeContext()
.getState(new ValueStateDescriptor<OrderInfo2>("info2",OrderInfo2.class));
}
@Override
public void flatMap1(OrderInfo1 orderInfo1, Collector<Tuple2<OrderInfo1, OrderInfo2>> out) throws Exception {
OrderInfo2 value2 = orderInfo2State.value();
if(value2 != null){
orderInfo2State.clear();
out.collect(Tuple2.of(orderInfo1,value2));
}else{
orderInfo1State.update(orderInfo1);
}
}
@Override
public void flatMap2(OrderInfo2 orderInfo2, Collector<Tuple2<OrderInfo1, OrderInfo2>> out)throws Exception {
OrderInfo1 value1 = orderInfo1State.value();
if(value1 != null){
orderInfo1State.clear();
out.collect(Tuple2.of(value1,orderInfo2));
}else{
orderInfo2State.update(orderInfo2);
}
}
}
}
这里的flatmap1,相同orderId才会放在一起,就是orderInfo1数据过来了,orderInfo2数据还没过,就把orderInfo1放入状态中,等到orderInfo2数据过来了,查看相同orderId下的orderInfo1状态是有值,有,则组合输出;这里的flatmap2,和flatmap1同样的道理。
需求: 每两条数据打印一次结果
public class CustomSink
implements SinkFunction<Tuple2<String, Integer>>, CheckpointedFunction {
/**
*
* 数据是不安全。添加上CheckpointedRunction数据更安全
*
* 现在只是要求,每2条数据打印一次。
* 如果改一下需求,每1000条打印一次。
* checkpoint -》 999 保存了 1
*
* 999 -》 突然直接,程序宕机了,那这999条数据会丢失
*/
// 用于缓存结果数据的, 内存 Java 堆内存
private List<Tuple2<String, Integer>> bufferElements;
// 表示内存中数据的大小阈值 / 2
private int threshold;
// 用于保存内存中的状态信息
//ListState:磁盘,内存,?
private ListState<Tuple2<String, Integer>> checkpointState;
// StateBackend
// checkpoint
public CustomSink(int threshold) {
this.threshold = threshold;
this.bufferElements = new ArrayList<>();
}
@Override
public void invoke(Tuple2<String, Integer> value, Context context) throws Exception {
// 可以将接收到的每一条数据保存到任何的存储系统中
bufferElements.add(value);
if (bufferElements.size() == threshold) {//2
// 简单打印
System.out.println("自定义格式:" + bufferElements);
bufferElements.clear();
}
}
// 用于将内存中数据保存到状态中
@Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
checkpointState.clear();
for (Tuple2<String, Integer> ele : bufferElements) {
checkpointState.add(ele);
}
}
// 用于在程序挥发的时候从状态中恢复数据到内存
//open 初始化,只会被调用一次。
@Override
public void initializeState(FunctionInitializationContext context) throws Exception {
ListStateDescriptor<Tuple2<String, Integer>> descriptor =
new ListStateDescriptor<Tuple2<String, Integer>>(
"bufferd -elements",
TypeInformation.of(new TypeHint<Tuple2<String, Integer>>() {}));
// 注册一个 operator state
checkpointState = context.getOperatorStateStore().getListState(descriptor);
if (context.isRestored()) {
for (Tuple2<String, Integer> ele : checkpointState.get()) {
//内存
bufferElements.add(ele);
}
}
}
}
public class TestOperatorStateMain {
public static void main(String[] args) throws Exception{
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<Tuple2<String, Integer>> dataStreamSource =
env.fromElements(Tuple2.of("Spark", 3), Tuple2.of("Hadoop", 5), Tuple2.of("Hadoop", 7),
Tuple2.of("Spark", 4));
dataStreamSource
//print只能一条数据一条数据的打印
//超级print的算子
.addSink(new CustomSink(2)).setParallelism(1);
env.execute("TestStatefulApi");
}
}
Flink支持的StateBackend:
默认情况下,状态信息是存储在 TaskManager 的堆内存中的,checkpoint 的时候将状态保存到 JobManager 的堆内存中。
缺点:
只能保存数据量小的状态
状态数据有可能会丢失
优点:
开发测试很方便
状态信息存储在 TaskManager 的堆内存中的,checkpoint 的时候将状态保存到指定的文件中 (HDFS 等文件系统)
缺点:
状态大小受TaskManager内存限制(默认支持5M)
优点:
状态访问速度很快
状态信息不会丢失
用于: 生产,也可存储状态数据量大的情况
状态信息存储在 RocksDB 数据库 (key-value 的数据存储服务), 最终保存在本地文件中checkpoint 的时候将状态保存到指定的文件中 (HDFS 等文件系统),需要引入jar包,
缺点:
状态访问速度有所下降
优点:
可以存储超大量的状态信息
状态信息不会丢失
用于: 生产,可以存储超大量的状态信息
(1)单任务调整
修改当前任务代码
env.setStateBackend(new FsStateBackend(“hdfs://namenode:9000/flink/checkpoints”));
或者new MemoryStateBackend()
或者new RocksDBStateBackend(filebackend, true);【需要添加第三方依赖】
(2)全局调整
修改flink-conf.yaml
state.backend: filesystem
state.checkpoints.dir: hdfs://namenode:9000/flink/checkpoints
注意:state.backend的值可以是下面几种:jobmanager(MemoryStateBackend), filesystem(FsStateBackend), rocksdb(RocksDBStateBackend)