老周最近遇到一个生产问题,这个问题比较诡异。我先来说下背景,应用启动的时候,起了一个下载download线程,并且是守护线程,去离线拉取云端的数据到本地的RocksDb数据库进行存储,其中呢有个open方法,它是用来打开RocksDb数据库的,download线程下载数据的时候进程存库的时候会先去调用open方法打开数据库,然后再去执行put操作也就是保存数据的操作。问题来了,打开了数据库后都有日志,但唯独保存的时候没看到任何日志,也没有看到任何报错,这个download线程好像消失掉了。
看了下唯一的日志 rocksDB compactRange failed, reason: 进程中断
我以为是RocksDb手动合并文件,由于文件太大,导致IO负载一直很高,进而导致进程中断,再触发钩子函数应用程序结束导致put操作没反应了,也进而解释了后续也没有日志了。
其实上面的分析是错的,下面认真分析了下才是对的:
第一次打开数据库的时候调open方法是ok,第二次put的时候由于手动合并文件导致数据库hang住了,一直put不进去,这也解释了为啥没有日志。那为啥会出现进程中断这种错误日志呢?是因为用户手动结束应用程序,触发了钩子函数,进而导致RocksDb手动合并文件进程中断结束的日志。整体流程也说得通了。
private RocksDB open() throws RocksDBException {
if (rocksDB == null) {
synchronized (lock) {
if (rocksDB == null) {
final int availableProcessors = Runtime.getRuntime().availableProcessors();
final Options options = new Options();
options.setCreateIfMissing(true);
options.setIncreaseParallelism(availableProcessors);
options.setMaxBackgroundFlushes(1);
options.setMaxBackgroundCompactions(availableProcessors);
options.setParanoidChecks(false);
options.setWalRecoveryMode(WALRecoveryMode.SkipAnyCorruptedRecords);
options.setKeepLogFileNum(5);
options.setMaxLogFileSize(1024L * 1024);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (rocksDB != null) {
rocksDB.close();
}
options.close();
}));
String path = Environment.path() + PATH;
rocksDB = TtlDB.open(options, path, SECONDS_OF_TTL, false);
CompletableFuture.runAsync(() -> {
try {
rocksDB.compactRange();
} catch (RocksDBException e) {
logger.error("rocksDB compactRange failed, reason: ", e);
}
});
}
}
}
return rocksDB;
}
3.1 是否可以不进行手动合并?
大多数情况下:对于大多数应用来说,依赖 RocksDB 的自动合并机制已经足够。自动合并机制能够有效地管理数据库状态,确保性能和空间利用率。
特殊情况: 高负载场景:在高负载场景下,自动合并可能会增加系统负担。此时,可以通过手动合并来避免在关键时间段内进行合并操作。
特定优化需求: 如果有特定的优化需求,例如需要在某个时间窗口内进行数据整理,手动合并可以提供更精细的控制。 结合我们自身的业务,人员刚上的时候,系统负载不是那么高,交给RocksDB自动合并就行。
所以RocksDb手动合并文件得代码直接移除掉
CompletableFuture.runAsync(() -> {
try {
rocksDB.compactRange();
} catch (RocksDBException e) {
logger.error("rocksDB compactRange failed, reason: ", e);
}
});
3.2 RocksDb参数设置的不合理
3.2.1 setKeepLogFileNum
含义
用于设置 RocksDB 保留的日志文件的最大数量。默认情况下,setKeepLogFileNum 的值为 1000。
问题
虽然 setKeepLogFileNum 主要影响日志文件的管理,但它间接影响文件合并的频率。如果日志文件被频繁删除,可能会导致更多的小文件积累,从而增加文件合并的频率。
影响
频繁的文件合并会增加 CPU 和磁盘 I/O 负载,从而影响整体性能。
3.2.2 setMaxLogFileSize
含义
setMaxLogFileSize 用于设置单个日志文件的最大大小。
默认情况下,setMaxLogFileSize 的值为 10485760 字节(即 10 MB)。
但看之前设置的参数setKeepLogFileNum只有5,setMaxLogFileSize只有1M。简单的说,RocksDb只有5个文件,每个文件只有1M,当合并的时候到达5个文件的时候就会根据底层设置的淘汰策略把旧的淘汰掉。5M的文件明显不够,就会导致手动合并的时候一直会合并,导致数据库hang住。所以我针对下面两个参数做了优化:
// 优化前
options.setKeepLogFileNum(5);
options.setMaxLogFileSize(1024L * 1024);
// 优化后
options.setKeepLogFileNum(500);
options.setMaxLogFileSize(10L * 1024 * 1024);欢迎大家关注我的公众号【老周聊架构】,AI、大数据、云原生、物联网等相关领域的技术知识分享。