Flink 有两种常见的 State 类型,分别是:
Keyed State:基于 KeyedStream 上的状态,这个状态是跟特定的 Key 绑 定的。KeyedStream 流上的每一个 Key,都对应一个 State。Flink 针对 Keyed State 提供了 以下可以保存 State 的数据结构:
demo: 计算每个手机的呼叫时间间隔时间
package com.flink.primary.State
import com.flink.primary.DataSource.StationLog
import org.apache.flink.api.common.functions.RichFlatMapFunction
import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.util.Collector
/**
* 第一种方法的实现
* 统计每个手机呼叫时间间隔
* @author xjh 2020.4.7
*/
//定义一个外部类
case class StationLog(sid: String, callOut: String, callIn: String, callType: String, callTime: Long, duration: Long)
object TestKeyedState1 {
def main(args: Array[String]): Unit = {
val environment = StreamExecutionEnvironment.getExecutionEnvironment
import org.apache.flink.streaming.api.scala._
//读取数据源
val filePath=getClass.getResource("/station.log").getPath
val stream = environment.readTextFile(filePath)
.map(line => {
val strings = line.split(",")
new StationLog(strings(0).trim, strings(1).trim, strings(2).trim, strings(3).trim, strings(4).trim.toLong, strings(5).trim.toLong)
})
stream.keyBy(_.callOut) //分组
.flatMap(new CallIntervalFunction) //flatmap中传一个富函数类
.print()
environment.execute()
}
//自定义一个类,继承富函数类(富函数类可在上下文环境中管理状态)
class CallIntervalFunction extends RichFlatMapFunction[StationLog, (String, Long)] { //输出的一个二元组(String,Long) 手机号码,时间间隔
private var preCallTimeState: ValueState[Long] = _
override def open(parameters: Configuration): Unit = {
preCallTimeState = getRuntimeContext.getState(new ValueStateDescriptor[Long]("pre", classOf[Long]))
}
override def flatMap(value: StationLog, out: Collector[(String, Long)]): Unit = {
//从状态中取得前一次的时间
var preCallTime = preCallTimeState.value()
if (preCallTime == null || preCallTime == 0) {
//状态中没有,当前为第一次呼叫
preCallTimeState.update(value.callTime)
} else {
//状态中有数据,则要计算时间间隔
var interval = Math.abs(value.callTime - preCallTime)
out.collect((value.callOut,interval)) //返回一个二元组
}
}
}
}
Operator State 与 Key 无关,而是与 Operator 绑定,整个 Operator 只对应一个 State。比如:Flink 中的 Kafka Connector 就使用了 Operator State,它会在每个 Connector实例中,保存该实例消费Topic 的所有(partition, offset)映射。
当程序出现问题需要恢复 State 数据的时候,只有程序提供支持才可以实现 State 的容错。State 的容错需要依靠 CheckPoint 机制,这样才可以保证 Exactly-once 这种语义,但是注意,它只能保证 Flink系统内的 Exactly-once,比如 Flink 内置支持的算子。针对 Source和Sink 组件,如果想要保证Exactly-once的话,则这些组件本身应支持这种语义。
1.CheckPoint 原理
Flink 中基于异步轻量级的分布式快照技术提供了 Checkpoints 容错机制,分布式快照可以将同一时间点Task/Operator 的状态数据全局统一快照处理,包括前面提到的 Keyed State 和 Operator State。Flink 会在输入的数据集上间隔性地生成 checkpoint barrier,通过栅栏(barrier)将间隔时间段内的数据划分到相应的 checkpoint 中。如下图:
2.CheckPoint参数和设置
默认情况下 Flink 不开启检查点的,用户需要在程序中通过调用方法配置和开启检查 点,另外还可以调整其他相关参数:
streamEnv.enableCheckpointing(1000);
streamEnv.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACT LY_ONCE);
//或者
streamEnv.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.AT_LE AST_ONCE)
streamEnv.getCheckpointConfig.setCheckpointTimeout(50000)
streamEnv.getCheckpointConfig.setMinPauseBetweenCheckpoints(600)
streamEnv.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
//删除
streamEnv.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckp ointCleanup.DELETE_ON_CANCELLATION)
//保留
streamEnv.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckp ointCleanup.RETAIN_ON_CANCELLATION)
streamEnv.getCheckpointConfig.setTolerableCheckpointFailureNumber(1)
(2020.6.17更新)
这一小节转载自Apache Flink 进阶教程(三):Checkpoint 的应用实践 作者 | 唐云(茶干)
默认情况下,State 会保存在 TaskManager 的内存中,CheckPoint 会存储在 JobManager 的内存中。State 和 CheckPoint 的存储位置取决于 StateBackend 的配置。Flink 一共提供 了 3 种 StateBackend 。 包 括 基 于 内 存 的 MemoryStateBackend 、 基 于 文 件 系 统 的 FsStateBackend,以及基于 RockDB 作为存储介质的 RocksDBState-Backend。
MemoryStateBackend
基于内存的状态管理具有非常快速和高效的特点,但也具有非常多的限制,最主要的是内存的容量限制,一旦存储的状态数据过多就会导致系统内存溢出等问题,从而影响整个应用的正常运行。同时如果机器出现问题,整个主机内存中的状态数据都会丢失,进而无法恢复任务中的状态数据。因此从数据安全的角度建议用户尽可能地避免在生产环境中使用 MemoryStateBackend。 streamEnv.setStateBackend(new MemoryStateBackend(10*1024*1024))
FsStateBackend
和 MemoryStateBackend 有所不同,FsStateBackend 是基于文件系统的一种状态管理器, 这里的文件系统可以是本地文件系统,也可以是 HDFS 分布式文件系统。FsStateBackend 更 适合任务状态非常大的情况,例如应用中含有时间范围非常长的窗口计算,或 Key/value State 状态数据量非常大的场景。
streamEnv.setStateBackend(new FsStateBackend("hdfs://hadoop101:9000/checkpoint/cp1"))
RocksDBStateBackend RocksDBStateBackend 是 Flink 中内置的第三方状态管理器,和前面的状态管理器不同, RocksDBStateBackend 需要单独引入相关的依赖包到工程中。
org.apache.flink
flink-statebackend-rocksdb_2.11
1.9.1
RocksDBStateBackend 采用异步的方式进行状态数据的 Snapshot,任务中的状态数据首 先被写入本地 RockDB 中,这样在 RockDB 仅会存储正在进行计算的热数据,而需要进行 CheckPoint 的时候,会把本地的数据直接复制到远端的 FileSystem 中。
与 FsStateBackend 相比,RocksDBStateBackend 在性能上要比 FsStateBackend 高一些,主要是因为借助于 RocksDB 在本地存储了最新热数据,然后通过异步的方式再同步到文件系 统中,但 RocksDBStateBackend 和 MemoryStateBackend 相比性能就会较弱一些。RocksDB 克服了 State 受内存限制的缺点,同时又能够持久化到远端文件系统中,推荐在生产中使用。
streamEnv.setStateBackend( new RocksDBStateBackend ("hdfs://hadoop101:9000/checkpoint/cp2"))
全局配置 StateBackend
上述都是单 job 配置状态后端,我们也可以全局配置状态后端,这需要修改 flink-conf.yaml 配置文件: state.backend: filesystem
其中: filesystem 表示使用 FsStateBackend;jobmanager 表示使用 MemoryStateBackend; rocksdb 表示使用 RocksDBStateBackend。
例如我们设置:state.checkpoints.dir: hdfs://hadoop101:9000/checkpoints
默认情况下,如果设置了 CheckPoint 选项,则 Flink 只保留最近成功生成的 1 个 CheckPoint,而当 Flink 程序失败时,可以通过最近的 CheckPoint 来进行恢复。但是,如 果希望保留多个 CheckPoint,并能够根据实际需要选择其中一个进行恢复,就会更加灵活。 添加如下配置,指定最多可以保存的 CheckPoint 的个数。 state.checkpoints.num-retained: 2
Savepoints 是检查点的一种特殊实现,底层实现其实也是使用 Checkpoints 的机制。 Savepoints 是用户以手工命令的方式触发 Checkpoint,并将结果持久化到指定的存储路径 中,其主要目的是帮助用户在升级和维护集群过程中保存系统中的状态数据,避免因为停机 运维或者升级应用等正常终止应用的操作而导致系统无法恢复到原有的计算状态的情况,从 而无法实现从端到端的 Excatly-Once 语义保证。
配置 Savepoints 的存储路径 在 flink-conf.yaml 中配置 SavePoint 存储的位置,设置后,如果要创建指定 Job 的 SavePoint,可以不用在手动执行命令时指定 SavePoint 的位置。
state.savepoints.dir: hdfs:/hadoop101:9000/savepoints
在代码中设置算子 ID 为了能够在作业的不同版本之间以及 Flink 的不同版本之间顺利升级,强烈推荐程序员 通过手动给算子赋予 ID,这些 ID 将用于确定每一个算子的状态范围。如果不手动给各算子 指定 ID,则会由 Flink 自动给每个算子生成一个 ID。而这些自动生成的 ID 依赖于程序的结 构,并且对代码的更改是很敏感的。因此,强烈建议用户手动设置 ID。
object TestSavepoints {
def main(args: Array[String]): Unit = {
//初始化Flink的Streaming(流计算)上下文执行环境
val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
import org.apache.flink.streaming.api.scala._
//读取数据得到DataStream
val stream: DataStream[String] = streamEnv.socketTextStream("hadoop101",8888) .uid("mySource-001")
stream.flatMap(_.split(" "))
.uid("flatMap-001")
.map((_,1))
.uid("map-001")
.keyBy(0)
.sum(1)
.uid("sum-001")
.print()
streamEnv.execute("wc") //启动流计算
}
}
触发 SavePoint
//先启动Job
[root@hadoop101 bin]# ./flink run -c com.bjsxt.flink.state.TestSavepoints -d /home/Flink-Demo-1.0-SNAPSHOT.jar
//再取消Job ,触发SavePoint
[root@hadoop101 bin]# ./flink savepoint 6ecb8cfda5a5200016ca6b01260b94ce
[root@hadoop101 bin]# ./flink cancel 6ecb8cfda5a5200016ca6b01260b94ce
[root@hadoop101 bin]# ./flink run -s hdfs://hadoop101:9000/savepoints/savepoint-6ecb8c-e56ccb88576a -c com.bjsxt.flink.state.TestSavepoints -d /home/Flink-Demo-1.0-SNAPSHOT.jar