Flink(五) State Time-To-Live(TTL)

一、简介

State Time-To-Live(TTL) Flink中状态的存活时间。
在开发Flink应用时,对于许多有状态流应用程序的一个常见要求是自动清理应用程序状态,以有效管理状态大小。或控制应用程序状态的访问时间。从 Flink 1.6 版本开始,社区为状态引入了TTL(time-to-live,生存时间)机制,支持Keyed State 的自动过期,有效解决了状态数据在无干预情况下无限增长导致 OOM 的问题。

1.1 为啥要清理状态

  1. 状态也是数据,数据就会占用空间,那Flink是做大数据的,如果将以前的数据都存起来的话肯定是存不下的,所以需要清理
  2. 有一些具体的应用场景中,如果计算时使用之前保留的状态会影响用户体验

具体的一些场景:

  1. 假设一个Flink应用程序为每个用户提取用户登录事件并且存储每个用户的上次登录时间实现下次免登陆来提升用户体验。
  2. 我们有对数据的时效性的要求,例如用户在某个时间段内不允许访问。就可以通过TTL功能来实现。

本质上来讲,State TTL 功能给每个 Flink 的 Keyed 状态增加了一个“时间戳”,而 Flink 在状态创建、写入或读取(可选)时更新这个时间戳,并且判断状态是否过期。如果状态过期,还会根据可见性参数,来决定是否返回已过期但还未清理的状态等等。状态的清理并不是即时的,而是使用了一种 Lazy 的算法来实现,从而减少状态清理对性能的影响。

二、TTL在Flink 中的使用

2.1 看一下官网中给的例子:

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

StateTtlConfig ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(1))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .build();
    
ValueStateDescriptor<String> stateDescriptor = new ValueStateDescriptor<>("text state", String.class);
stateDescriptor.enableTimeToLive(ttlConfig);

从例子中提取使用的步骤:

  1. 首先要定义一个 StateTtlConfig 对象。这个 StateTtlConfig 对象可以通过构造器模式(Builder Pattern)来创建,典型地用法是传入一个 Time 对象作为 TTL 时间
  2. 然后设置更新类型(Update Type)
  3. 设置状态可见性(State Visibility)
  4. 当 StateTtlConfig 对象构造完成后,即可在后续声明的状态描述符(State Descriptor)中启用 State TTL 功能了。
  5. 注意:上面的代码中State TTL 功能所指定的过期时间并不是全局生效的,而是和某个具体的状态所绑定。如果希望对所有状态都生效,那么就需要对所有用到的状态定义都传入 StateTtlConfig 对象。

参数说明:

  1. TTL:表示状态的过期时间,是一个 org.apache.flink.api.common.time.Time 对象。一旦设置了 TTL,那么如果上次访问的时间戳 + TTL 超过了当前时间,则表明状态过期了。
  2. UpdateType:表示状态时间戳的更新的时机,是一个 Enum 对象。如果设置为 Disabled,则表明不更新时间戳;如果设置为 OnCreateAndWrite,则表明当状态创建或每次写入时都会更新时间戳;如果设置为 OnReadAndWrite,则除了在状态创建和写入时更新时间戳外,读取也会更新状态的时间戳。
  3. StateVisibility:表示对已过期但还未被清理掉的状态如何处理,也是 Enum 对象。如果设置为 ReturnExpiredIfNotCleanedUp,那么即使这个状态的时间戳表明它已经过期了,但是只要还未被真正清理掉,就会被返回给调用方;如果设置为 NeverReturnExpired,那么一旦这个状态过期了,那么永远不会被返回给调用方,只会返回空状态,避免了过期状态带来的干扰。
  4. TimeCharacteristic 以及 TtlTimeCharacteristic:表示 State TTL 功能所适用的时间模式,仍然是 Enum 对象。前者已经被标记为 Deprecated(废弃)
  5. **CleanupStrategies:表示过期对象的清理策略,**目前来说有三种 Enum 值。当设置为 FULL_STATE_SCAN_SNAPSHOT 时,对应的是 EmptyCleanupStrategy 类,表示对过期状态不做主动清理,当执行完整快照(Snapshot / Checkpoint)时,会生成一个较小的状态文件,但本地状态并不会减小。唯有当作业重启并从上一个快照点恢复后,本地状态才会实际减小,因此可能仍然不能解决内存压力的问题。为了应对这个问题,Flink 还提供了增量清理的枚举值,分别是针对 Heap StateBackend 的 INCREMENTAL_CLEANUP(对应 IncrementalCleanupStrategy 类),以及对 RocksDB StateBackend 有效的 ROCKSDB_COMPACTION_FILTER(对RocksdbCompactFilterCleanupStrategy 类). 对于增量清理功能,Flink 可以被配置为每读取若干条记录就执行一次清理操作,而且可以指定每次要清理多少条失效记录;对于 RocksDB 的状态清理,则是通过 JNI 来调用 C++ 语言编写的 FlinkCompactionFilter 来实现,底层是通过 RocksDB 提供的后台 Compaction 操作来实现对失效状态过滤的。

总结:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、State TTL 的实现原理

首先我们来看一下 flink-runtime 模块是如何定义和实现 TTL 功能的,这里面有多个类可以特别留意:

TtlValue 类
这个类是一个包装类,它可以为任意的值对象增加一个 lastAccessTimestamp 的时间戳,并且可以获取传入的对象以及时间戳。但需要注意的是,一旦初始化,所有参数就不可以改变。它是 State TTL 状态保存的基本单元,可以通过 TtlUtils 工具类提供的 wrapWithTs(value, timestamp) 方法将一个普通值对象包装为 TtlValue 对象。

AbstractTtlDecorator 及子类
这是一个抽象的包装类。把 Flink 原有的状态(State Handler)与用户设置的 StateTtlConfig 对象一起传入这个类的构造方法后,将会根据前面介绍的多个参数,对这个类的若干布尔常量做赋值,例如 updateTsOnRead 表示是否在读取记录时更新时间戳,returnExpired 表示是否允许返回已过期的状态等,从而返回一个支持 TTL 的状态对象。
它有若干子类,例如对我们常见的 Aggregating / List / Map / Reducing / Folding / Value 等六种状态类型,均提供了具体的实现。这个抽象类还提供了若干工具方法,例如判断状态值是否过期、将普通的值包装为带时间戳的状态值等,同时还提供了 TTL 检查是否过期以及过期后的增量清理等逻辑。
AbstractTtlState 类的子类
例如在下面的 getWrappedWithTtlCheckAndUpdate 方法中,首先会调用传入的 getter 对象来获取 TtlValue 对象,它是一个普通的状态加上了时间戳,然后判断它是否为 null 以及是否过期,如果过期就调用传入的 stateClear 对象来做清理,它是一个 ThrowableRunnable 对象,约等于 Java 自带的 Runnable,只是允许抛出给定的异常。正如之前所言,这个方法会根据之前传入的 StateTtlConfig 的参数而决定,是否在读取时更新时间戳,以及在过期后是否返回过期的状态等。updater 对象则负责处理更新时间戳等操作。
getter、updater、stateClear 对象的定义方式非常简单,采用 Lambda 表达式,清晰地描述了各自的动作。original 表示原始的 Flink 状态(State Handler),可以看到具体的操作还是在原始状态对象上进行的,这个类只是一个装饰器,给原始状态对象增加了时间戳以及过期判断等逻辑。

然后再看下 TtlMapState 的 put 方法:

@Override
public void put(UK key, UV value) throws Exception {
    accessCallback.run();
    original.put(key, wrapWithTs(value));
}

这里的 accessCallback 是一个回调的 Runnable 对象,用来实现过期状态的增量清理逻辑,它会在每个 put 或 get 方法被调用前都执行一次。限于篇幅限制,这里不再展开叙述,感兴趣的同学可以参考 TtlStateFactory 类的 registerTtlIncrementalCleanupCallback() 方法。本文下面的示例代码中也给出了具体实现逻辑。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值