Flink1.8.0重大更新-Flink中State的自动清除详解

本文围绕Flink状态TTL功能展开。Flink 1.8.0改进了状态TTL,可减轻手动清理状态的麻烦。介绍了状态暂时性的原因,如控制状态大小、数据保密等。还阐述了应用状态持续清理的相关配置,以及避免取出‘垃圾数据’的策略,包括完整快照自动删除、堆状态后端增量清理等,最后提及未来改进方向。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

导读:

基于时间的状态访问和对应用程序状态大小进行控制是有状态流处理领域中的常见问题和挑战。


Flink的1.8.0版本通过添加对过期状态对象的连续后台清理的支持,显著改进了状态TTL功能。新的清理机制可以减轻手动执行状态清理的麻烦。


状态TTL使您可以控制应用程序状态的大小,以便开发者可以更加专注于应用程序的核心逻辑。

在我们开发Flink应用时,许多有状态流应用程序的一个常见要求是自动清理应用程序状态以有效管理状态大小,或控制应用程序状态的访问时间。 TTL(Time To Live)功能在Flink 1.6.0中开始启动,并在Apache Flink中启用了应用程序状态清理和高效的状态大小管理。

在这篇文章中,我们将讨论状态(State)的TTL并且给出用例。 此外,我们将展示如何使用和配置状态的TTL。

状态的暂时性

State只能在有限的时间内维持有两个主要原因。例如,假设一个Flink应用程序为每个用户提取用户登录事件并且存储每个用户的上次登录时间实现下次免登陆来提升用户体验。

控制状态的大小

控制状态的大小,能够有效地管理不断增长的State的规模,这个TTL应用的主要场景。通常来说,数据需要暂时保留,例如用户处在一次访问的session中。当用户访问的事件结束后,我们就没有必要保存该用户的状态,但是用户的State仍占用存储空间。 Flink1.8.0引入了基于TTL的对于过期状态的清理,让我们能够对这些无效数据进行清除。在此以前,开发人员必须采取额外操作来删除无用状态以释放存储空间。这种手动清理程序不仅容易出错,而且效率低下。根据我们上述用户登录的案例,我们不再需要手动去清理。

基于对数据的保密需要

假设我们有对数据的时效性的要求,例如用户在某个时间段内不允许访问。我们都可以通过TTL功能来实现。

对应用状态的持续清理(Continuous Cleanup)

Apache Flink的1.6.0版本引入了State TTL功能。它使流处理应用程序的开发人员配置过期时间,并在定义时间超时(Time to Live)之后进行清理。在Flink 1.8.0中,该功能得到了扩展,包括对RocksDB和堆状态后端(FSStateBackend和MemoryStateBackend)的历史数据进行持续清理,从而实现旧条目的连续清理过程(根据TTL设置)。

在Flink的DataStream API中,应用程序状态由状态描述符(State Descriptor)定义。通过将StateTtlConfiguration对象传递给状态描述符来配置状态TTL。 以下Java示例演示如何创建状态TTL配置并将其提供给状态描述符,该状态描述符将上述案例中的用户上次登录时间保存为Long值:

import org.apache.flink.api.common.state.StateTtlConfig;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.api.common.state.ValueStateDescriptor;

StateTtlConfig ttlConfig = StateTtlConfig
.newBuilder(Time.days(7))
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
.build();

ValueStateDescriptor<Long> lastUserLogin =
new ValueStateDescriptor<>("lastUserLogin", Long.class);

lastUserLogin.enableTimeToLive(ttlConfig);

Flink提供了多个选项来配置TTL功能的行为。

什么时候重置?

默认情况下,当数据的状态修改会更新数据的TTL时间。我们还还可以在读取访问数据时对它进行更新,这样做的代价是会出现额外的写入操作以更新时间戳的操作。

已经过期的数据是否可以访问?

State TTL采用惰性策略来清理过期状态。这可能导致我们的应用程序会去尝试读取已过期但处于尚未删除状态的数据。我们可以观察此类读取请求是否返回了过期状态。无论哪种情况,数据被访问后会立即清除过期状态。

哪个时间语义被用于定义TTL?

使用Flink 1.8.0,用户只能根据处理时间(Processing Time)定义状态TTL。未来的Apache Flink版本中计划支持事件时间(Event Time)。

Flink内部,状态TTL功能是通过存储上次相关状态访问的附加时间戳以及实际状态值来实现的。虽然这种方法增加了一些存储开销,但它允许Flink程序在查询数据、checkpointing,数据恢复的时候访问数据的过期状态。

如何避免取出'垃圾数据'

在读取操作中访问状态对象时,Flink将检查其时间戳并清除状态是否已过期(取决于配置的状态可见性,是否返回过期状态)。由于这种延迟删除的特性,永远不会再次访问的过期状态数据将永远占用存储空间,除非被垃圾回收。

那么如何在没有应用程序逻辑明确的处理它的情况下删除过期的状态呢?通常,我们可以配置不同的策略进行后台删除。

完整快照自动删除过期状态

当获取检查点或保存点的完整快照时,Flink 1.6.0已经支持自动删除过期状态。大家注意,过期状态删除不适用于增量检查点。必须明确启用完全快照的状态删除,如以下示例所示:

StateTtlConfig ttlConfig = StateTtlConfig
.newBuilder(Time.days(7))
.cleanupFullSnapshot()
.build();

上述代码会导致本地状态存储大小保持不变,但Flink任务的完整快照的大小减小。只有当用户从快照重新加载其状态到本地时,才会清除用户的本地状态。

由于上述这些限制,FLink应用程序仍需要在Flink 1.6.0中过期后主动删除状态。为了改善用户体验,Flink1.8.0引入了两种自主清理策略,分别针对Flink的两种状态后端类型。

堆状态后端的增量清理

此方法特定于堆状态后端(FSStateBackend和MemoryStateBackend)。它的实现方法是存储后端在所有状态条目上维护一个惰性全局迭代器。某些事件(例如状态访问)会触发增量清理。每次触发增量清理时,迭代器都会向前迭代删除已遍历的过期数据。以下代码示例演示如何启用增量清理:

StateTtlConfig ttlConfig = StateTtlConfig
.newBuilder(Time.days(7))
// check 10 keys for every state access
.cleanupIncrementally(10, false)
.build();

如果启用,则每次进行状态访问都会触发清理步骤。对于每个清理步骤,都会检查一定数量的数据是否过期。

有两个参数:第一个参数是检查每个清理步骤的状态条目数。第二个参数是一个标志,用于数据处理后触发清理步骤,此外对于每次状态访问同样有效。

关于这种方法有两点需要注意:第一个是增量清理所花费的时间增加了数据处理延迟。第二个应该可以忽略不计,但仍然值得一提:如果没有状态访问或没有数据处理记录,则不会删除过期状态。

RocksDB后台压缩可以过滤掉过期状态

如果你的Flink应用程序使用RocksDB作为状态后端存储,则可以启用另一个基于Flink特定压缩过滤器的清理策略。RocksDB定期运行异步压缩以合并状态更新并减少存储。Flink压缩过滤器使用TTL检查状态条目的到期时间戳,并丢弃所有过期值。

激活此功能的第一步是通过设置以下Flink配置选项来配置RocksDB状态后端:

state.backend.rocksdb.ttl.compaction.filter.enabled

配置RocksDB状态后端后,将为状态启用压缩清理策略,如以下代码示例所示:

StateTtlConfig ttlConfig = StateTtlConfig
.newBuilder(Time.days(7))
.cleanupInRocksdbCompactFilter()
.build();
使用定时器删除(Timers)

手动清除状态的另一种方法是基于Flink定时器。这是社区目前正在评估未来版本的想法。通过这种方法,为每个状态访问注册清理定时器。这种方法更容易预测,因为状态一旦到期就会被删除。但是,这种方法代价很大,因为定时器消耗存储资源,并且会频繁读取状态信息。

未来展望

除了上面提到的基于计时器的清理策略外,Flink社区还计划进一步改进状态TTL功能。可能的改进点包括为事件时间(Event Time)添加TTL支持(目前仅支持Processing Time)。

640?wx_fmt=png

640?wx_fmt=jpeg

"C:\Program Files (x86)\Java\jdk1.8.0_102\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:3082,suspend=y,server=n -javaagent:C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2021.2\captureAgent\debugger-agent.jar=file:/C:/Users/Administrator/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath C:\Users\Administrator\AppData\Local\Temp\classpath255817062.jar com.tongchuang.realtime.mds.ULEDataanomalyanalysis 已连接到目标 VM, 地址: ''127.0.0.1:3082',传输: '套接字'' SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/F:/flink/flinkmaven/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.10.0/log4j-slf4j-impl-2.10.0.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/F:/flink/flinkmaven/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory] Exception in thread "main" org.apache.flink.api.common.functions.InvalidTypesException: The return type of function 'main(ULEDataanomalyanalysis.java:87)' could not be determined automatically, due to type erasure. You can give type information hints by using the returns(...) method on the result of the transformation call, or by letting your function implement the 'ResultTypeQueryable' interface. at org.apache.flink.api.dag.Transformation.getOutputType(Transformation.java:479) at org.apache.flink.streaming.api.datastream.DataStream.getType(DataStream.java:193) at org.apache.flink.streaming.api.datastream.DataStream.map(DataStream.java:577) at com.tongchuang.realtime.mds.ULEDataanomalyanalysis.main(ULEDataanomalyanalysis.java:101) Caused by: org.apache.flink.api.common.functions.InvalidTypesException: The generic type parameters of 'Map' are missing. In many cases lambda methods don't provide enough information for automatic type extraction when Java generics are involved. An easy workaround is to use an (anonymous) class instead that implements the 'org.apache.flink.api.common.functions.MapFunction' interface. Otherwise the type has to be specified explicitly using type information. at org.apache.flink.api.java.typeutils.TypeExtractionUtils.validateLambdaType(TypeExtractionUtils.java:371) at org.apache.flink.api.java.typeutils.TypeExtractor.getUnaryOperatorReturnType(TypeExtractor.java:565) at org.apache.flink.api.java.typeutils.TypeExtractor.getMapReturnTypes(TypeExtractor.java:151) at org.apache.flink.streaming.api.datastream.DataStream.map(DataStream.java:576) at com.tongchuang.realtime.mds.ULEDataanomalyanalysis.main(ULEDataanomalyanalysis.java:87) 与目标 VM 断开连接, 地址为: ''127.0.0.1:3082',传输: '套接字'' 进程已结束,退出代码为 1
08-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王知无(import_bigdata)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值