Table of Contents
监控State and Checkpoints
调优Checkpointing
网络缓存调优
异步Checkpointing
RocksDB调优
增量备份
RocksDB计时器
预定义选项
将选项工厂传递给RocksDB
容量规划
压缩
本地恢复任务
触发
捷径
主(分布式存储)和次(任务-本地)状态快照的关系
task-local 配置恢复
关于不同状态后端任务-本地恢复的详细信息
配置保存调度
要使 Flink 应用程序大规模可靠地运行,必须满足两个条件:
第一部分讨论如何在大规模上很好地执行检查点。最后一节解释了一些关于计划使用多少资源的最佳实践。
监视检查点行为的最简单方法是通过 UI 的检查点部分。检查点监视的文档显示了如何访问可用的检查点指标。
Flink Debugging & Monitoring - Monitoring Checkpointing(监控 Checkpointing 指标)
在扩展检查点时,有两个数字特别值得注意:
checkpoint_start_delay = end_to_end_duration - synchronous_duration - asynchronous_duration
当触发检查点的时间非常长时,这意味着检查点 barrier 需要很长时间才能从源传输到算子函数中。这通常表明系统在恒定的背压下工作。请注意,当存在瞬态背压、数据倾斜或网络问题时,此处指示的数字可能偶尔很高。然而,如果数字一直很高,这意味着检查点占用了很多的 Flink 资源。
检查点按应用程序可以配置的定期间隔触发。当检查点的完成时间比检查点间隔的时间长时,在进行中的检查点完成之前不会触发下一个检查点。默认情况下,下一个检查点将在正在进行的检查点完成后立即触发。
当检查点结束时通常花费的时间比基本间隔长(例如,因为状态比计划的大,或者检查点存储的存储空间暂时变慢),系统就会不断地接受检查点(一旦完成,新的检查点就会立即启动)。这可能意味着在检查点上有太多的资源被占用,而算子取得的资源太少。此行为对异步使用检查点状态的流应用程序影响较小,但仍可能对整个应用程序性能产生影响。
为了防止这种情况,应用程序可以定义检查点之间的最小持续时间:
StreamExecutionEnvironment.getCheckpointConfig().setMinPauseBetweenCheckpoints(milliseconds)
这个持续时间是最近一个检查点结束到下一个检查点开始之间必须经过的最小时间间隔。下图说明了这对检查点的影响。
在 Flink 1.3之前,网络缓冲区数量的增加也会导致检查点时间的增加,因为保持更多的动态数据意味着检查点 barrier 会被延迟。从 Flink 1.3开始,每个传出/传入通道使用的网络缓冲区的数量是有限的,因此可以在不影响检查点时间的情况下配置网络缓冲区(请参阅网络缓冲区配置)。
当状态异步快照时,检查点比状态同步快照时伸缩性更好。特别是在具有多个 join、co-function 或 window 的更复杂的流应用程序中,这可能会有很大的影响。
要异步存储状态,需要使用一个支持异步存储的状态后端。从 Flink 1.3开始,基于 rocksdb 和基于堆的状态后端(文件系统)都支持异步快照,并且默认使用它。这既适用于 managed operator state,也适用于 managed keyed state(包括 timers state)。
注意,RocksDB状态后端与基于堆的计时器的组合当前不支持计时器状态的异步快照。像键控状态这样的其他状态仍然是异步快照的。
许多大型 Flink Stream 应用程序的状态存储主要是 RocksDB 状态后端。后端可扩展到主内存之外,并可靠地存储大的键控状态。
缺点是,RocksDB 的性能可能会随着配置的不同而变化,而且几乎没有关于如何正确调优 RocksDB 的文档。例如,默认配置是针对 ssd 硬盘定制的,并且在旋转的磁盘上执行次优配置。
与完整的检查点相比,增量检查点可以显著减少检查点时间,代价是(潜在的)更长的恢复时间。其核心思想是,增量检查点只记录对前一个已完成检查点的所有更改,而不是生成状态后端完整的、自包含的备份。就像这样,增量检查点建立在以前的检查点上。Flink 利用 RocksDB 的内部备份机制,以一种随时间推移而自我整合的方式。因此,Flink 中的增量检查点历史不会无限增长,旧的检查点最终会自动包含和修剪。
虽然我们强烈建议对大型状态使用增量检查点,但请注意,这是一个新特性,目前在默认情况下没有启用。为了启用这个特性,用户可以实例化一个 rocksdbstateback,并在构造函数中将相应的布尔标志设置为true,例如:
RocksDBStateBackend backend =
new RocksDBStateBackend(filebackend, true);
对于 RocksDB,用户可以选择将计时器存储在堆(默认)中还是存储在 RocksDB 中。基于堆的计时器可以为更少的计时器提供更好的性能,而将计时器存储在 RocksDB 中可以提供更高的可伸缩性,因为 RocksDB 中的计时器数量可能超过可用的主内存(溢出到磁盘)。
当使用 Rock DB 作为状态后端时,可以通过 flink 的配置 state.backend.rocksdb.timer-service.factory 来选择定时器存储的类型。可选类型有:heap 和 rockdb。
RocksDB 状态后端基于堆的计时器的组合目前不支持计时器状态的异步快照。像键控状态这样的其他状态仍然是异步快照的。请注意,这不是以前版本的回归,将用FLINK-10026解决。
Flink 针对不同的设置为 RocksDB 提供了一些预定义的选项集合,有两种方法可以将这些预定义的选项传递给 RocksDB:
state.backend.rocksdb.predefined-options 默认值 DEFAULT,意思是 PredefinedOptions.DEFAULT
RocksDBStateBackend.setPredefinedOptions(PredefinedOptions.SPINNING_DISK_OPTIMIZED_HIGH_MEM)
随着时间的推移,我们希望积累更多这样的配置文件。当发现一组运行得很好并且似乎可以代表某些工作负载的选项时,请随意提供这些预定义的选项配置文件。
请注意,以编程方式设置的预定义选项将覆盖通过flink-conf.yaml配置的选项。
在 Flink 中有两种方法将 options factory 传递给 RocksDB:
state.backend.rocksdb.options-factory 用来设置 option factory 的类名,默认值 org.apache.flink.contrib.streaming.state.DefaultConfigurableOptionsFactory,
所有候选的可配置选项都在RocksDBConfigurableOptions 中定义。此外,还可以像下面这样定义定制的可配置选项工厂类,并将类名传递给 state.backend.rocksdb.options-factory。 public class MyOptionsFactory implements ConfigurableOptionsFactory {
private static final long DEFAULT_SIZE = 256 * 1024 * 1024; // 256 MB
private long blockCacheSize = DEFAULT_SIZE;
@Override
public DBOptions createDBOptions(DBOptions currentOptions) {
return currentOptions.setIncreaseParallelism(4)
.setUseFsync(false);
}
@Override
public ColumnFamilyOptions createColumnOptions(ColumnFamilyOptions currentOptions) {
return currentOptions.setTableFormatConfig(
new BlockBasedTableConfig()
.setBlockCacheSize(blockCacheSize)
.setBlockSize(128 * 1024)); // 128 KB
}
@Override
public OptionsFactory configure(Configuration configuration) {
this.blockCacheSize =
configuration.getLong("my.custom.rocksdb.block.cache.size", DEFAULT_SIZE);
return this;
}
}
RocksDBStateBackend.setOptions(new MyOptionsFactory())
以编程方式设置的选项工厂将覆盖通过 flink-conf 配置的选项工厂。如果进行了配置或设置,则与预定义选项相比,选项工厂具有更高的优先级。
RocksDB 是一个本地库,它直接从进程分配内存,而不是从 JVM 分配内存。任何分配给 RocksDB 的内存都必须考虑在内,通常是通过将 taskmanager 的JVM堆大小减少相同的数量。如果不这样做,可能会导致纱线/Mesos/等终止JVM进程,以分配比配置更多的内存。
容量规划的基本原则:
重要提示:为了允许以后添加资源,请确保将数据流程序的最大并行度设置为一个合理的数字。最大并行度定义了在重新扩展程序时(通过一个保存点)可以将程序并行度设置到多高。
Flink 内部跟踪并行状态的最大粒度。Flink 的设计致力于使其高效地获得最大并行度的极高值,即使是在执行并行度较低的程序时也是如此。
Flink 为所有检查点和保存点提供了可选的压缩(默认值:off)。目前,压缩总是使用 snappy 压缩算法(版本1.1.4),计划在未来支持自定义压缩算法。压缩作用于键控状态下键组的粒度,即每个键组可以单独解压缩,这对于扩缩容非常重要。
配置压缩代码样例:
ExecutionConfig executionConfig = new ExecutionConfig();
executionConfig.setUseSnapshotCompression(true);
以上配置对增量快照没有影响,因为它们使用的是 RocksDB 的内部格式,而该格式总是使用 snappy 压缩。
在 Flink 的检查点中,每个任务生成其状态的快照,然后将其写入分布式存储。每个任务通过发送一个描述状态在分布式存储中的位置的句柄,向作业管理器确认状态的成功写入。然后,作业管理器从所有任务收集句柄,并将它们绑定到检查点对象中。
在进行恢复时,作业管理器打开最新的检查点对象并将句柄发送回相应的任务,然后这些任务可以从分布式存储中恢复它们的状态。使用分布式存储来存储状态有两个重要的优点。首先,存储是容错的,其次,分布式存储中的所有状态对所有节点都是可访问的,并且可以很容易地进行重新分配(例如,重新缩放)。
然而,使用远程分布式存储也有一个很大的缺点:所有任务都必须通过网络从远程位置读取它们的状态。在许多场景中,恢复可以将失败的任务重新安排到与前一次运行相同的任务管理器中(当然也有例外,比如机器故障),但是我们仍然必须读取远程状态。这可能导致大型状态的恢复时间很长,即使一台机器上只有一个小故障。
Task-local 状态复苏目标这个问题的恢复时间长,主要观点如下:对于每个检查点,每个任务不仅写任务状态的分布式存储,但也保持中等状态快照副本的存储本地的任务(例如,在本地磁盘或内存)。请注意,快照的主存储仍然必须是分布式存储,因为本地存储不能确保节点故障下的持久性,也不能为其他节点提供访问以重分发状态,所以此功能仍然需要主存储
但是,对于每个可以重新调度到前一个位置进行恢复的任务,我们可以从次要的本地副本恢复状态,从而避免了远程读取状态的开销。由于许多故障不是节点故障,而且节点故障通常一次只影响一个或很少几个节点,因此在恢复过程中,大多数任务很可能返回到它们以前的位置,并发现它们的本地状态完好无损。这就是使局部恢复在减少恢复时间方面有效的原因。
请注意,根据所选的状态后端和检查点策略,对于创建和存储辅助本地状态副本的每个检查点,可能会产生一些额外的成本。例如,在大多数情况下,实现只是将对分布式存储的写入复制到本地文件。
限制:目前,任务-本地恢复仅覆盖键控状态后端。键控状态通常是状态中最大的部分。在不久的将来,我们还将介绍操作符状态和计时器。
任务本地状态通常被认为是一个次要副本,检查点状态的基本事实是分布式存储中的主要副本。这对检查点和恢复期间的本地状态问题有影响:
任务本地恢复在默认情况下是不激活的,可以通过 Flink 的配置和 state.backend.local-recovery 指定 CheckpointingOptions.LOCAL_RECOVERY 来激活使用。
此设置的值可以为 true 以启用,也可以为 false (默认值)以禁用本地恢复。
以下状态后端可以支持任务-本地恢复。
任务本地恢复假设在故障情况下保留分配的任务调度,其工作原理如下。每个任务都会记住它之前的分配,并在恢复时请求重新启动相同的槽位。如果这个插槽不可用,任务将从资源管理器请求一个新的插槽。这样,如果任务管理器不再可用,则无法返回到以前位置的任务将不会将其他正在恢复的任务赶出它们以前的位置。
我们的理由是,前一个插槽只能在任务管理器不再可用时消失,在这种情况下,一些任务必须请求一个新的插槽。在我们的调度策略中,我们为最大数量的任务提供了从它们的本地状态恢复的机会,并避免了任务从另一个任务窃取它们之前的插槽的级联效应。
原文地址:https://ci.apache.org/projects/flink/flink-docs-release-1.9/ops/state/large_state_tuning.html