【Flink系列】- RocksDB增量模式checkpoint大小持续增长的问题及解决

背景


Flink版本:1.13.5

一个使用FlinkSQL开发的生产线上任务, 使用Tumble Window做聚和统计,并且配置table.exec.state.ttl为7200000,设置checkpoint周期为5分钟,使用rocksdb的增量模式。

正常情况下,任务运行一段时间以后,新增和过期的状态达到动态的平衡,随着RocksDB的compaction,checkpoint的大小会在小范围内上下起伏。

实际观察到,checkpoint大小持续缓慢增长,运行20天以后,从最初了100M左右,增长到了2G,checkpoint的时间也从1秒增加到了几十秒。

源码分析


我们看一下RocksIncrementalSnapshotStrategy.RocksDBIncrementalSnapshotOperation类中的get()方法:

public SnapshotResult<KeyedStateHandle> get(CloseableRegistry snapshotCloseableRegistry) throws Exception {
            boolean completed = false;
            SnapshotResult<StreamStateHandle> metaStateHandle = null;
            Map<StateHandleID, StreamStateHandle> sstFiles = new HashMap();
            HashMap miscFiles = new HashMap();
            boolean var15 = false;

            SnapshotResult var18;
            try {
                var15 = true;
                metaStateHandle = this.materializeMetaData(snapshotCloseableRegistry);
                Preconditions.checkNotNull(metaStateHandle, "Metadata was not properly created.");
                Preconditions.checkNotNull(metaStateHandle.getJobManagerOwnedSnapshot(), "Metadata for job manager was not properly created.");
                this.uploadSstFiles(sstFiles, miscFiles, snapshotCloseableRegistry);
                synchronized(RocksIncrementalSnapshotStrategy.this.materializedSstFiles) {
                    RocksIncrementalSnapshotStrategy.this.materializedSstFiles.put(this.checkpointId, sstFiles.keySet());
                }

                IncrementalRemoteKeyedStateHandle jmIncrementalKeyedStateHandle = new IncrementalRemoteKeyedStateHandle(RocksIncrementalSnapshotStrategy.this.backendUID, RocksIncrementalSnapshotStrategy.this.keyGroupRange, this.checkpointId, sstFiles, miscFiles, (StreamStateHandle)metaStateHandle.getJobManagerOwnedSnapshot());
                DirectoryStateHandle directoryStateHandle = this.localBackupDirectory.completeSnapshotAndGetHandle();
                SnapshotResult snapshotResult;
                if (directoryStateHandle != null && metaStateHandle.getTaskLocalSnapshot() != null) {
                    IncrementalLocalKeyedStateHandle localDirKeyedStateHandle = new IncrementalLocalKeyedStateHandle(RocksIncrementalSnapshotStrategy.this.backendUID, this.checkpointId, directoryStateHandle, RocksIncrementalSnapshotStrategy.this.keyGroupRange, (StreamStateHandle)metaStateHandle.getTaskLocalSnapshot(), sstFiles.keySet());
                    snapshotResult = SnapshotResult.withLocalState(jmIncrementalKeyedStateHandle, localDirKeyedStateHandle);
                } else {
                    snapshotResult = SnapshotResult.of(jmIncrementalKeyedStateHandle);
                }

                completed = true;
                var18 = snapshotResult;
                var15 = false;
            } finally {
                if (var15) {
                    if (!completed) {
                        List<StateObject> statesToDiscard = new ArrayList(1 + miscFiles.size() + sstFiles.size());
                        statesToDiscard.add(metaStateHandle);
                        statesToDiscard.addAll(miscFiles.values());
                        statesToDiscard.addAll(sstFiles.values());
                        this.cleanupIncompleteSnapshot(statesToDiscard);
                    }

                }
            }

重点关注uploadSstFiles()方法的实现细节:

            Preconditions.checkState(this.localBackupDirectory.exists());
            Map<StateHandleID, Path> sstFilePaths = new HashMap();
            Map<StateHandleID, Path> miscFilePaths = new HashMap();
            Path[] files = this.localBackupDirectory.listDirectory();
            if (files != null) {
                this.createUploadFilePaths(files, sstFiles, sstFilePaths, miscFilePaths);
                sstFiles.putAll(RocksIncrementalSnapshotStrategy.this.stateUploader.uploadFilesToCheckpointFs(sstFilePaths, this.checkpointStreamFactory, snapshotCloseableRegistry));
                miscFiles.putAll(RocksIncrementalSnapshotStrategy.this.stateUploader.uploadFilesToCheckpointFs(miscFilePaths, this.checkpointStreamFactory, snapshotCloseableRegistry));
            }

进入到createUploadFilePaths()方法:

        private void createUploadFilePaths(Path[] files, Map<StateHandleID, StreamStateHandle> sstFiles, Map<StateHandleID, Path> sstFilePaths, Map<StateHandleID, Path> miscFilePaths) {
            Path[] var5 = files;
            int var6 = files.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                Path filePath = var5[var7];
                String fileName = filePath.getFileName().toString();
                StateHandleID stateHandleID = new StateHandleID(fileName);
                if (!fileName.endsWith(".sst")) {
                    miscFilePaths.put(stateHandleID, filePath);
                } else {
                    boolean existsAlready = this.baseSstFiles != null && this.baseSstFiles.contains(stateHandleID);
                    if (existsAlready) {
                        sstFiles.put(stateHandleID, new PlaceholderStreamStateHandle());
                    } else {
                        sstFilePaths.put(stateHandleID, filePath);
                    }
                }
            }

        }

  这里是问题的关键,我们可以归纳出主要逻辑:

1. 扫描rocksdb本地存储目录下的所有文件,获取到所有的sst文件和misc文件(除sst文件外的其他所有文件);

2. 将sst文件和历史checkpoint上传的sst文件做对比,将新增的sst文件路径记录下来;

3. 将misc文件的路径记录下来;

这里就是增量checkpoint的关键逻辑了, 我们发现一点,增量的checkpoint只针对sst文件, 对其他的misc文件是每次全量备份的,我们进到一个目录节点看一下有哪些文件被全量备份了:

[hadoop@fsp-hadoop-1 db]$ ll
总用量 8444
-rw-r--r-- 1 hadoop hadoop       0 3月  28 14:56 000058.log
-rw-r--r-- 1 hadoop hadoop 2065278 3月  31 10:17 025787.sst
-rw-r--r-- 1 hadoop hadoop 1945453 3月  31 10:18 025789.sst
-rw-r--r-- 1 hadoop hadoop   75420 3月  31 10:18 025790.sst
-rw-r--r-- 1 hadoop hadoop   33545 3月  31 10:18 025791.sst
-rw-r--r-- 1 hadoop hadoop   40177 3月  31 10:18 025792.sst
-rw-r--r-- 1 hadoop hadoop   33661 3月  31 10:18 025793.sst
-rw-r--r-- 1 hadoop hadoop   40494 3月  31 10:19 025794.sst
-rw-r--r-- 1 hadoop hadoop   33846 3月  31 10:19 025795.sst
-rw-r--r-- 1 hadoop hadoop      16 3月  30 19:46 CURRENT
-rw-r--r-- 1 hadoop hadoop      37 3月  28 14:56 IDENTITY
-rw-r--r-- 1 hadoop hadoop       0 3月  28 14:56 LOCK
-rw-rw-r-- 1 hadoop hadoop   38967 3月  28 14:56 LOG
-rw-r--r-- 1 hadoop hadoop 1399964 3月  31 10:19 MANIFEST-022789
-rw-r--r-- 1 hadoop hadoop   10407 3月  28 14:56 OPTIONS-000010
-rw-r--r-- 1 hadoop hadoop   13126 3月  28 14:56 OPTIONS-000012

1. CURRENT、IDENTIFY、LOCK、OPTIONS-*, 这些文件基本是固定大小,不会有变化;

2. LOG文件, 这个文件是rocksdb的日志文件,默认情况下,flink设置的rocksdb的日志输出级别是HEAD级别,几乎不会有日志输出,但是如果你配置了state.backend.rocksdb.log.level,比如说配置为了INFO_LEVEL,那么这个LOG文件会持续输出并且不会被清理;

3. MANIFEST-*,这是rocksdb的事务日志,在任务恢复重放过程中会用到, 这个日志也会持续增长,达到阈值以后滚动生成新的并且清楚旧文件;

原因总结


在增量checkpoint过程中,虽然sst文件所保存的状态数据大小保持动态平衡,但是LOG日志和MANIFEST文件仍然会当向持续增长,所以checkpoint会越来越大,越来越慢。

解决办法


1. 在生产环境关闭Rocksdb日志(保持state.backend.rocksdb.log.level的默认配置即可);

2. 设置manifest文件的滚动阈值,我设置的是10485760byte;

问题解决。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
注:下文中的 *** 代表文件名中的组件名称。 # 包含: 中文-英文对照文档:【***-javadoc-API文档-中文(简体)-英语-对照版.zip】 jar包下载地址:【***.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【***.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【***.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: 中文-英文对照文档,中英对照文档,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【***.jar中文文档.zip】,再解压其中的 【***-javadoc-API文档-中文(简体)版.zip】,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·本文档为双语同时展示,一行原文、一行译文,可逐行对照,避免了原文/译文来回切换的麻烦; ·有原文可参照,不再担心翻译偏差误导; ·边学技术、边学英语。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;
Flink使用RocksDB作为其状态后端存储引擎,默认情况下会在内存中维护一部分数据结构,以加速读写操作。然而,由于Flink的处理速度往往远快于数据的写入速度,导致RocksDB在高负载情况下内存占用不断增加的问题。 当Flink处理大量数据时,RocksDB会从磁盘读取更多数据并缓存在内部的结构中。这种缓存过程是为了减少I/O操作,但也导致了内存占用的增加。这可能导致应用程序的性能下降,甚至引发内存溢出错误。 为了解决RocksDB内存占用不断增加的问题,我们可以采取以下措施: 1. 调整RocksDB的缓存大小:通过调整Flink配置中的相关参数,如`state.backend.rocksdb.block-cache-size`控制RocksDB使用的缓存大小。可以根据应用程序的需求来适当增加或减少这个值,以平衡性能和内存占用。 2. 提高Flink的并发度:增加Flink应用程序的并发度可以减少每个任务处理的数据量,从而减少RocksDB内存占用。可以通过增加任务并行度和子任务并行度,以及调整网络并行度等参数来提高Flink的并发度。 3. 优化数据倾斜:如果应用程序中存在数据倾斜的情况,即某些键或分区的数据量明显大于其他键或分区,可以采取分区策略、键间重分配等方法来解决数据倾斜,减少RocksDB内存的占用。 4. 增加或减少状态的保留时间:根据应用程序的需要,可以增加或减少状态的保留时间。较短的保留时间会导致状态中的数据减少,从而减少RocksDB内存的占用。 总之,解决Flink RocksDB内存占用不断增加的问题可以通过调整RocksDB的缓存大小、提高Flink的并发度、优化数据倾斜以及调整状态的保留时间等方法来实现。根据具体的应用场景和需求,可以采取不同的措施来平衡性能和内存占用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值