RocksDB 备份指南:BackupEngine 使用、原理与恢复实战
【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb
导读
RocksDB 作为嵌入式持久化键值存储引擎,为开发者提供了内建的备份(Backup)与恢复(Restore)能力:你只需几行代码即可把数据库的一致性快照增量地写入备份目录,并在灾难发生时完整还原。本文以 RocksDB 官方备份文档为主线,结合当前仓库中 include/rocksdb/utilities/backup_engine.h 的头文件定义与 utilities/backup/backup_engine.cc 的底层实现,系统讲解两种备份 API 的用法、关键配置参数、备份与恢复的完整流程、校验与增量去重机制,以及常见的高级用法,帮助你安全地落地 RocksDB 的备份方案。
一、五分钟上手:两种备份方式
RocksDB 的备份能力演进过程中形成了两种 API:最早的BackupableDB包装器,以及更推荐直接使用的BackupEngine。二者底层共享同一套实现(当前仓库中为BackupEngineImpl,见 utilities/backup/backup_engine_impl.h),核心文件路径均为 utilities/backup/backup_engine.cc。
方式一:BackupableDB 包装器(经典 API)
在早期版本中,通过BackupableDB包装现有DB*即可获得备份能力,创建备份只需三步:
#include "rocksdb/db.h" #include "utilities/backupable_db.h" using namespace rocksdb; DB* db; DB::Open(Options(), "/tmp/rocksdb", &db); BackupableDB* backupable_db = new BackupableDB(db, BackupableDBOptions("/tmp/rocksdb_backup")); backupable_db->Put(...); // do your thing backupable_db->CreateNewBackup(); delete backupable_db; // no need to also delete db这段示例会把数据库备份到/tmp/rocksdb_backup。注意:创建BackupableDB会"接管"传入的DB*,之后所有数据库方法都应调用在backupable_db对象上;同时删除backupable_db时不需要再单独delete db,包装器负责回收底层 DB。
方式二:BackupEngine(现代推荐 API)
BackupableDB目前已不建议新代码使用,现代 API 是独立的BackupEngine,它不包装 DB 对象,而是接受一个DB*参数来创建备份:
#include "rocksdb/db.h" #include "utilities/backupable_db.h" // 当前仓库中位于 include/rocksdb/utilities/backup_engine.h using namespace rocksdb; DB* db; DB::Open(Options(), "/tmp/rocksdb", &db); db->Put(...); // do your thing BackupEngine* backup_engine; BackupEngine::Open(BackupEngineOptions("/tmp/rocksdb_backup"), Env::Default(), &backup_engine); backup_engine->CreateNewBackup(db); delete db; delete backup_engine;在当前仓库中,BackupEngine的Open静态方法定义在 include/rocksdb/utilities/backup_engine.h,其签名支持新旧两种参数顺序以保持向后兼容。BackupEngine的实现位于 utilities/backup/backup_engine.cc,接口类还细分为可读写的BackupEngine、只读的BackupEngineReadOnly与BackupEngineReadOnlyBase。
注意:头文件路径已从历史文档中的
utilities/backupable_db.h迁移到当前仓库的include/rocksdb/utilities/backup_engine.h,编写代码时以当前路径为准。
二、恢复数据库
恢复同样简单,直接针对备份目录构造引擎并调用恢复方法:
RestoreBackupableDB* restore = new RestoreBackupableDB( Env::Default(), BackupableDBOptions("/tmp/rocksdb_backup")); restore->RestoreDBFromLatestBackup("/tmp/rocksdb", "/tmp/rocksdb"); delete restore;使用现代 API 时:
BackupEngine* backup_engine; BackupEngine::Open(BackupEngineOptions("/tmp/rocksdb_backup"), Env::Default(), &backup_engine); backup_engine->RestoreDBFromLatestBackup("/tmp/rocksdb", "/tmp/rocksdb"); delete backup_engine;两个参数的含义分别是:
- 第一个参数
db_dir:数据库目录,恢复后的数据文件落在这里; - 第二个参数
wal_dir:日志(WAL)文件目录。多数场景下两者相同,但若设置了Options::wal_dir将 WAL 与数据分离存放,此处需要分别指定。
恢复时的校验是强制性的:恢复引擎会对每个恢复出来的文件重新计算校验和,并与备份时记录的校验和比对,一旦不匹配,整个恢复过程会中止并返回Status::Corruption,避免把损坏的数据写回数据库。
恢复指定的备份而非最新备份
RestoreDBFromLatestBackup()恢复最新的一致备份,而RestoreDBFromBackup(backup_id, db_dir, wal_dir)可恢复指定 ID 的备份。此处有一个非常关键、容易踩坑的语义(原文档特别强调):假设你已有备份 1、2、3、4,若你从备份 2 恢复数据库、继续写入新数据并创建新备份,则旧备份 3 和 4 会被删除,新备份直接以备份 2 为基础在 ID 3 的位置上重建。原因在于备份采用增量去重共享文件策略,后续备份依赖前面的文件,破坏该链条会导致文件被误判为"无用"而回收,因此从中间版本恢复后继续备份会触发对更新备份的清理。
三、备份管理:查询、删除与增量机制
增量备份与校验
备份是增量的。每次调用CreateNewBackup()只会把"新数据"拷贝到备份目录。对于任何被备份的文件(包括 SST、WAL 日志等),引擎都会计算校验和(CRC32C)以确保文件在文件系统中的完整性;对于已存在于备份目录、本次无需复制的历史文件,同样会重新计算校验和并与历史记录比对,防止备份目录中的文件悄悄损坏。一旦发现校验和不匹配,当前备份会直接中止,并把系统回滚到调用CreateNewBackup()之前的状态(详见下文"Under the hood")。
需要说明的是,校验失败可能是备份目录中的文件损坏,也可能是当前数据库中对应文件损坏,两者需要结合日志进一步甄别。
查询备份列表
当备份数量增多后,可调用GetBackupInfo()获取全部备份的信息列表,包括:
- 备份 ID(
backup_id):始终递增,用于标识每个备份; - 创建时间戳(
timestamp); - 备份大小(
size)。
注意原文档的提醒:所有备份大小之和会大于备份目录的实际占用空间,因为多个备份共享了部分数据文件(去重),size统计的是每个备份逻辑上包含的文件大小。在现代 API 中,GetBackupInfo返回std::vector<BackupInfo>,还支持传入include_file_details=true获取每个备份的文件级明细(见 include/rocksdb/utilities/backup_engine.h)。
清理旧备份
通常你只需要保留少量备份,调用PurgeOldBackups(N)即可保留最新的 N 个备份并删除其余所有旧备份;也可以调用DeleteBackup(id)删除任意指定 ID 的备份。这两个操作在现代 API 中定义于BackupEngine接口(include/rocksdb/utilities/backup_engine.h)。如果删除操作因崩溃或断电而中断,下一次调用DeleteBackup、PurgeOldBackups或GarbageCollect时会自动清理残留状态(GarbageCollect的定义见 include/rocksdb/utilities/backup_engine.h)。
四、BackupEngineOptions 关键配置
无论是BackupableDBOptions还是现代BackupEngineOptions,核心字段基本一致(完整定义见 include/rocksdb/utilities/backup_engine.h):
| 配置项 | 默认值 | 说明 |
|---|---|---|
backup_dir | 必填 | 备份文件存放目录,必须与数据库目录不同,官方建议设为dbname + "/backups" |
backup_env | nullptr | 用于备份目录所有文件 I/O 的Env(备份时写入、恢复时读取)。设为 HDFS Env 即可把备份存到 HDFS,实现跨机器容灾 |
share_table_files | true | 是否让多个备份共享表(SST)与 blob 文件,以节省空间并支持增量备份;设为false时每个备份完全独立、互不共享数据 |
sync | true | 每次文件写入后是否fsync落盘。为true时保证机器重启/崩溃后备份仍一致;为false时速度快一些,但较新的备份可能不一致 |
destroy_old_data | false | 为true时,创建引擎即删除备份目录中所有旧备份 |
backup_log_files | true | 是否备份日志文件。对于表文件在内存、WAL 落盘的内存数据库,可设为false跳过日志备份 |
restore_rate_limit | 0 | 恢复期间每秒最大传输字节数,0表示不限速(仅限制写入;若还想限制读取,需通过restore_rate_limiter传入支持读限速的 RateLimiter) |
callback_trigger_interval_size | 4194304 | 备份进度回调触发间隔字节数(4 MiB),配合CreateBackupOptions::progress_callback上报进度 |
max_valid_backups_to_open | INT_MAX | 只读引擎Open时最多打开多少个最新且未损坏的备份 |
此外还有几个值得关注的高级字段:
share_files_with_checksum(默认true):仅在share_table_files=true时生效。该字段用于区分不同历史来源的同编号 SST 文件,防止"从非最新备份恢复后继续写入并创建新备份"这类场景下发生数据丢失;不建议设为false(已标记为 DEPRECATED 且有风险)。share_files_with_checksum_naming:共享文件的命名方案。默认kUseDbSessionId | kFlagIncludeFileSize,利用 DB session id 保证文件名强唯一且无需先读文件算校验和;kLegacyCrc32cAndFileSize为旧版命名,仅在兼容旧行为时使用。schema_version:备份元数据文件的 schema 版本,1兼容非常老的 RocksDB,2需要 RocksDB >= 6.19.0,且是保存/恢复文件温度元数据(实验特性)的最低版本要求。
高级用法补充
- 备份到 HDFS:
BackupableDBOptions::backup_env或BackupEngineOptions::backup_env指定 HDFS Env 后,所有备份写入与恢复读取都走该 Env,备份即可整体落在 HDFS 上。 - 日志输出:
info_log(Logger*类型)非空时用于打印备份相关的 LOG 信息,便于排查问题。 - 一致性保证:
sync=true可保证机器崩溃/重启后备份仍然一致;sync=false时只能保证"之前已完成同步的备份与恢复"不被本次操作破坏,较新备份存在不一致风险。 - 备份前刷盘:
CreateNewBackup(flush_before_backup)的flush_before_backup参数默认false。为true时,引擎先触发 memtable flush 再拷贝文件,这样 WAL 日志不会被拷入备份目录(flush 后日志被删除);为false时备份会包含对应活 memtable 的日志文件。无论该参数取值如何,备份都与数据库当前状态一致。现代 API 中该参数位于CreateBackupOptions::flush_before_backup(include/rocksdb/utilities/backup_engine.h),并补充了边界情况:2PC 开启时必然触发 flush;关闭 WAL 时需设flush_before_backup=true以免丢失 memtable 中未落盘的数据。 - 备份附加元数据:
CreateNewBackupWithMetadata()可在备份时携带应用自定义元数据(app_metadata),查询BackupInfo时一并返回。 - 备份进度与停止:
progress_callback按callback_trigger_interval_size字节间隔回调进度;StopBackup()可异步请求中止正在进行的备份(返回Status::Incomplete),中止后该引擎实例不可再创建备份,需重新Open。
五、Under the hood:备份流程内部原理
BackupableDB实现了DB接口并在其上增加了四个方法:CreateNewBackup()、GetBackupInfo()、PurgeOldBackups()、DeleteBackup(),所有其他DB接口调用都会被转发给底层DB对象。在现代实现中,这些逻辑统一收敛到BackupEngineImpl(实现文件 utilities/backup/backup_engine.cc,CreateNewBackupWithMetadata入口在 utilities/backup/backup_engine.cc)。
一次CreateNewBackup()调用大致经历以下步骤:
- 禁用文件删除(
db->DisableFileDeletions(),见 utilities/backup/backup_engine.cc):防止备份过程中后台 compaction 等操作删除正在被读取的文件。 - 获取存活文件列表(live files):包括表文件(SST)、
CURRENT与MANIFEST文件。 - 拷贝存活文件到备份目录:由于表文件不可变且文件名唯一,若备份目录中已存在同名文件(如
00050.sst)则跳过复制。但无论是否需要复制,都会对所有文件计算校验和:已存在文件的校验和会与历史记录比对,发现不匹配立即中止备份并回滚到调用前状态。需要注意的是,中止可能源于备份目录文件损坏,也可能源于当前数据库中对应文件损坏。由于MANIFEST与CURRENT文件不是不可变的,它们总是被复制。 - 若
flush_before_backup=false,还需拷贝日志文件:调用GetSortedWalFiles()取得所有存活 WAL 文件并复制到备份目录。 - 重新启用文件删除(
db->EnableFileDeletions(),见 utilities/backup/backup_engine.cc)。
崩溃恢复与 LATEST_BACKUP 文件
备份 ID 始终递增,备份目录中存在LATEST_BACKUP文件记录最新备份的 ID。如果在备份过程中崩溃,重启时会发现备份目录中存在比LATEST_BACKUP更新的文件,此时引擎会删除所有比LATEST_BACKUP更新的备份并清理相关文件——因为备份中断时部分表文件可能是损坏的,而基于"去重共享"策略,备份目录里残留的损坏表文件是危险的(后续增量备份可能误认为文件已存在而跳过复制)。通过元数据文件(backup_meta)记录每个备份的文件清单与校验和,引擎才能准确地判断哪些文件属于哪个备份、是否可以安全共享,相关读写逻辑见 utilities/backup/backup_engine.cc。
并发与只读引擎
现代BackupEngine自 6.20 起整体线程安全(内部使用读写锁),读操作之间可并发,追加/写操作之间互斥;同一backup_dir上建议只打开一个引擎实例,混用多个实例时某些组合行为未定义。需要说明的是,destroy_old_data=true的Open实质是一次写操作。若仅需读取/恢复备份(如从备份机读取),可使用只读变体BackupEngineReadOnly,其Open不会对备份目录做任何写操作。
六、测试验证与进一步阅读
仓库中针对备份引擎有详尽的测试用例,可用于验证本文描述的行为:
- utilities/backup/backup_engine_test.cc 包含大量
TEST_F(BackupEngineTest, ...)用例,如FileCollision(backup_engine_test.cc)验证不同历史来源的同名文件在去重策略下不会互相污染、BackupWithMetadata(backup_engine_test.cc)验证附加元数据的备份/恢复、DeleteTmpFiles(backup_engine_test.cc)验证临时文件清理。
关于接口细节,可查阅 include/rocksdb/utilities/backup_engine.h;关于实现细节,可深入阅读 utilities/backup/backup_engine.cc 与 utilities/backup/backup_engine_impl.h。另外 examples/rocksdb_backup_restore_example.cc 提供了一个可直接编译运行的备份/恢复完整示例,适合作为上手参考;Java 绑定示例可参考 java/rocksjni/backupenginejni.cc。
结语
RocksDB 的备份体系以"增量 + 去重 + 校验"为核心:增量复制只传新文件、跨备份共享表文件以节省空间、每次备份与恢复都做 CRC32C 校验保证数据完好,配合LATEST_BACKUP元数据实现崩溃后的自愈清理。实际使用中,建议开启sync=true保证崩溃一致性、保留少量备份并通过PurgeOldBackups定期清理,同时利用backup_env把备份放置到独立存储(如 HDFS)以实现真正的容灾目标。
【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考