1 状态管理的基本概念
1.1 什么是状态
1.1.1 无状态的例子:消费延迟计算
消息队列:
一个生产者持续写入,多个消费组分别读取,如何实时统计每个消费者落后多少条数据?
//输入
{
"timestamp": 1555516800,
"offset":
{
"producer": 16,
"consumer0": 10,
"consumer1": 7,
"consumer2": 12
}
}
//输出
{
"timestamp": 1555516800,
"lag":
{
"consumer0": 5,
"consumer1": 8,
"consumer2": 3
}
}
- 单条输入包含所需的所有信息
- 相同输入可以得到相同输出
1.1.2 有状态计算的例子:访问量统计
Nginx访问日志,每个请求访问一个URL地址,如何实时统计每个地址总共被访问了多少次?
输入输出:
{
"@timestamp": "18/Apr/2019:00:00:00",
"remote_addr": "127.0.0.1",
"request": "GET",
"url": "/api/a"
}
{
"url": "/api/a",
"count": 1
}
{
"@timestamp": "18/Apr/2019:00:00:00",
"remote_addr": "127.0.0.1",
"request": "POST",
"url": "/api/b"
}
{
"url": "/api/b",
"count": 1
}
{
"@timestamp": "18/Apr/2019:00:00:00",
"remote_addr": "127.0.0.1",
"request": "GET",
"url": "/api/a"
}
{
"url": "/api/a",
"count": 2
}
- 单挑输入仅包含所需的部分信息:当前请求信息
- 相同输入可能得到不同输出:当前请求之前的累计访问量
1.1.3 需要使用状态的场景
- 去重:记录所有的主键
- 窗口计算:已进入的未触发的数据
- 机器学习|深度学习:训练的模型及参数
- 访问历史数据:需要与昨日进行对比
1.2 状态管理
最直接的方式:内存
- 存储容量限制
- 备份与恢复
- 横向扩展
对流式作业的要求
- 7*24小时运行,高可靠
- 数据不丢不重,恰好计算一次
- 数据实时产出,不延迟
理想的状态管理
- 易用:丰富的数据结构;多样的组织形式;简洁的扩展接口
- 高效:读写快、恢复快;可以方便地横向扩展;备份不影响处理性能
- 可靠:持久化;不丢不重;具备容错能力
2 状态的类型和使用方式
2.1 Managed State & Raw State
Managed State Raw State
状态管理方式 Flink Runtime管理 用户自己管理
- 自动存储,自动回复 - 需要自己序列化
- 内存管理上有优化
状态数据结构 已知的数据结构 字节数组
- value,list,map等 - byte[]
推荐使用场景 大多数情况下均可使用 自定义Operator时可使用
2.2 Keyed State & Operator State
Keyed State
- 只能用在KeyedSteam上的算子中
- 每个Key对应一个State
- 一个Operator实例处理多个Key,访问响应的多个State
- 并发改变,State随着Key在实例间迁移
- 通过RuntimeContext访问
- Rich Function
- 支持的数据结构
- ValueState、ListState、ReducingState、AggeragatingState、MapState
Operator State
- 可以用于所有算子
- 常用于source,例如FlinkKafkaConsumer
- 一个Operator实例对应一个State
- 并发改变时有多重重新分配方式可选
- 均匀分配
- 合并后每个得到全量
- 实现CheckpointedFunction或ListCheckpointed接口
- 支持的数据结构
- ListState
Keyed State使用示例
状态数据类型 访问接口
ValueState 单个值 update(T)/T value()
MapState Map put(UK key,UV value)/putAll(Map<UK,UV> map)
remove(UK key)
boolean contains(UK key)/UV get(UK key)
iterable<Map,Entry> entries()/iterator<Map,Entry> iterator()
iterable<UK> keys()/iterable<UV> values()
ListState List add(T)/addAll(List<T>)
update(List<T>)/iterable<T> get()
ReducingState 单个值 add(T)/addAll(List<T>)
update(List<T>)/T get()
AggregatingState 单个值 add(IN)/OUT get()
2.3 Keyed State使用示例
简单状态机代码示例:
DataStream<Event> events = env.addSource(source);
DataStream<Alert> alerts = events
// partition on the address to make sure equal addresses
// end up in the same state machine flatMap function
.keyBy(Event::sourceAddress)
// the function that evaluates the state machine over the sequence of events
.flatMap(new StateMachineMapper());
@SuppressWarnings("serial")
static class StateMachineMapper extends RichFlatMapFunction<Event, Alert> {
/** The state for the current key. */
private ValueState<State> currentState;
@Override
public void open(Configuration conf) {
// get access to the state object
currentState = getRuntimeContext().getState(
new ValueStateDescriptor<>("state", State.class));
}
@Override
public void flatMap(Event evt, Collector<Alert> out) throws Exception {
// get the current state for the key (source address)
// if no state exists, yet, the state must be the state machine's initial state
State state = currentState.value();
if (state == null) {
state = State.Initial;
}
// ask the state machine what state we should go to based on the given event
State nextState = state.transition(evt.type());
if (nextState == State.InvalidTransition) {
// the current event resulted in an invalid transition
// raise an alert!
out.collect(new Alert(evt.sourceAddress(), state, evt.type()));
}
else if (nextState.isTerminal()) {
// we reached a terminal state, clean up the current state
currentState.clear();
}
else {
// remember the new state
currentState.update(nextState);
}
}
}
3 容错机制与故障恢复
3.1 状态如何保存及恢复
Checkpoint
- 定时制作分布式快照、对程序中的状态进行备份
- 发生故障时
- 将整个作业的所有Task都回滚到最后一次成功Checkpoint中的状态,然后从那个店开始继续处理
- 必要条件
- 数据源支持重发
- 一致性语义
- 恰好一次
- 至少一次
//Checkpoint设置
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(1000);
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
env.getCheckpointConfig().setCheckpointTimeout(60000);
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
env.getCheckpointConfig().enableExternalizedCheckpoits(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
Checkpoint Savepoint
触发管理方式 由Flink自动触发并管理 由用户手动触发并管理
主要用途 在Task发生异常时快速恢复 有计划地进行备份,使作业能停止后再恢复
- 例如网络抖动 - 例如修改改吗、调整并发
特点 轻量 持久
自动从故障中恢复 以标准格式存储,允许代码或配置发生改变
在作业停止后默认清除 手动触发从Savepoint的恢复
3.2 可选的状态存储方式
3.2.1 MemoryStateBackend
//构造方法
MemoryStateBackend(int maxStateSize, boolean asynchronousSnapshots)
存储方式:
- State:TaskManager内存
- Checkpoint:JobManager内存
容量限制:
- 单个State maxStateSize 默认5M
- maxStateSize <= akka.framesize 默认10M
- 总大小不超过JobManager的内存
推荐使用的场景
- 本地测试;几乎无状态的作业,比如ETL;JobManager不容易挂,或挂掉影响不大的情况
- 不推荐在生产使用
3.2.2 FsStateBackend
构造方法:
FsStateBackend(URI checkpointDataUri, boolean asynchronousSnapshots)
存储方式:
- State: TaskManager内存
- Checkpoint: 外部文件系统(本地或HDFS)
容量限制
- 单TaskManager上State总量不超过他的内存
- 总大小不超过配置的文件系统容量
推荐使用的场景
- 常规使用状态的作业,如分钟级窗口聚合、join;需要开启HA的作业
- 可以在生产环境使用
RocksDBStateBackend
//构造方法
RocksDBStateBackend(URI checkpointDataUri, boolean enableIncrementalCheckpointing)
存储方式:
State:TaskManager上的KV数据库(实际使用内存+磁盘)
Checkpoint:外部文件系统(本地或HDFS)
容量限制:
- 单TaskManager上State总量不超过他的内存+磁盘
- 单Key最大2G
- 总大小不超过配置的文件系统容量
推荐使用场景:
- 超大状态的作业,如天级窗口聚合;需要开启HA的作业;对状态读写性能要求不高的作业
- 可以在生产场景使用
相关联的参数在conf/flink-conf.yaml
- state.backend。如果打开,可以用以存储operator的状态的checkpoint。支持的后端有:
- jobmanager In-memory state, backup to JobManager’s/ZooKeeper’s memory. Should be used only for minimal state (Kafka offsets) or testing and local debugging.
- filesystem: State is in-memory on the TaskManagers, and state snapshots are stored in a file system. Supported are all filesystems supported by Flink, for example HDFS, S3, …
- state.backend.fs.checkpointdir:存储checkpoint的目录,文件系统是flink支持的文件系统。注意:State backend必须从jobmanager可访问,使用flie:// 只能在local搭建的情况下。
- state.backend.rocksdb.checkpointdir
- state.checkpoints.dir
- state.checkpoints.num-retained
总结
为什么要使用状态?
数据之间有关联,需要通过状态满足业务逻辑
为什么要管理状态?
实时计算作业需要7*24运行,需要应对不可靠因素带来的影响
如何选择状态的类型和存储方式?
分析自己的业务场景,比对各方案的利弊,选择合适的,够用即可