Flink之状态管理与容错机制

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使用示例

简单状态机代码示例:

https://github.com/apache/flink/blob/master/flink-examples/flink-examples-streaming/src/main/java/org/apache/flink/streaming/examples/statemachine/StateMachineExample.java​github.com

 

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运行,需要应对不可靠因素带来的影响

如何选择状态的类型和存储方式?

分析自己的业务场景,比对各方案的利弊,选择合适的,够用即可

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章