概述
Flink通过快照机制和Barrier来实现一致性的保证,当任务中途crash或者cancel之后,可以通过checkpoing或者savepoint来进行恢复,实现数据流的重放。从而让任务达到一致性的效果,这种一致性需要开启exactly_once模式之后才行。需要记住的是这边的Flink exactly_once只是说在Flink内部是exactly_once的,并不能保证与外部存储交互时的exactly_once,如果要实现外部存储连接后的exactly_once,需要进行做一些特殊的处理。Flink定义的checkpiont支持两种模式(CheckpointingMode):
- EXACTLY_ONCE
- AT_LEAST_ONCE
EXACTLY ONCE
该模式意味着系统在进行恢复时,每条记录将在Operator状态中只被重现/重放一次。例如在一段数据流中,不管该系统crash或者重启了多少次,该统计结果将总是跟流中的元素的真实个数一致。
当然EXACTLY_ONCE并不是说毫无确定,相比较AT_LEAST_ONCE,整体的处理速度会相对比较慢,因为在开启EXACTLY_ONCE后,为了保证一致性开启了数据对齐,从而影响了一些性能。
AT LEAST ONCE
该模式意味着系统将以一种更加简单的方式来对operator的状态进行快照,系统crash或者cancel后恢复时,operator的状态中有一些记录可能会被重放多次。
例如,以上面的例子讲说,失败后恢复时,统计值将等于或者大于流中元素的真实值。这种模式因为不需要对齐所有对延迟产生的影响很小,处理速度也更加快速,通常应用于接收低延时并且能够容忍重复消息的场景。
一致性实现原理
虽然上面讲到了一致性的保证是通过快照和Brrier机制来实现的,那他们具体是如何实现的呢?阅读中可以通过带入以下几点来进行考虑:
- 快照中保存的是什么?
- 什么时候触发系统进行执行快照?
- 如何在流式计算中既要执行快照又要保证整体的处理速度?
CHECKPOINT
快照记录了系统当前各个task/Operator的状态,这些状态保存了正常处理的元素。这些快照将被定期的删除和更新,系统出现crash后,进行恢复时就会从这些快照中读取数据,恢复crash之前的状态,那么该如何理解状态(STATE)呢?
STATE
State 可以理解为某task/operator在某时刻的一个中间结果,比如在flatmap中在这段时刻处理的数据,State可以被记录,在系统失败的情况可以进行恢复。STATE主要有两种类型operator state和keyed state。
OPERATOR STATE和KEYED STATE
Operator state是一个与key无关,并且在全局中唯一绑定到特定的operator中的state,比如有source或者map算子,如果需要保存这些operator的状态,就可以在这些operator添加状态的处理机制,具体可以看下面的例子。
Operator state只有一种数据结构ListState<T>,具体checkpoint过程中会把该数据结构的数据写入到hdfs中,用于保存该operator在当前的状态。
Keyed State:
- 基于KeyStream之上的状态,如dataStream.keyBy()
- keyby之后的operator state
keyed state的数据结构:
- ValueState<T>
- LisstState<T>
- ReducingState<T>
- MapState<UK,UV>
CHECKPOINT实现例子
这是operator state实现的例子
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<Tuple2<String, Integer>>();
}
@Override
public void invoke(Tuple2<String, Integer> value, Context context) throws Exception {
bufferedElements.add(value);
if(bufferedElements.size() == threshold){
for(Tuple2<String,Integer> element:bufferedElements){
//send it to the sink
}
bufferedElements.clear();
}
}
@Override
public void snapshotState(FunctionSnapshotContext functionSnapshotContext) throws Exception {
/**定期实现checkpoint*/
checkpointedState.clear();
for(Tuple2<String,Integer> element:bufferedElements){
checkpointedState.add(element);
}
}
/**恢复初始化的时候从保存的快照中获取数据,用于恢复到crash之前的状态*/
@Override
public void initializeState(FunctionInitializationContext context)