RocksDB快照 #
一、快照基础 #
1.1 什么是快照 #
快照(Snapshot)是RocksDB在某一时刻数据库状态的一致性视图。通过快照可以实现:
text
快照特性:
├── 一致性读 - 读取特定时间点的数据
├── 隔离性 - 不受并发写入影响
├── 轻量级 - 创建快照成本很低
└── MVCC支持 - 基于多版本并发控制
1.2 快照原理 #
text
数据版本链:
写入序列:
Write(key1, v1) → seq=100
Write(key2, v2) → seq=101
Write(key1, v3) → seq=102
Write(key3, v1) → seq=103
快照创建:
Snapshot A → seq=101
Snapshot B → seq=103
读取:
- 普通读取:看到seq<=103的所有数据
- Snapshot A读取:只看到seq<=101的数据
- Snapshot B读取:只看到seq<=103的数据
1.3 创建和使用快照 #
cpp
#include <rocksdb/db.h>
#include <rocksdb/options.h>
#include <iostream>
int main() {
rocksdb::DB* db;
rocksdb::Options options;
options.create_if_missing = true;
rocksdb::Status status = rocksdb::DB::Open(options, "/tmp/testdb", &db);
if (!status.ok()) {
std::cerr << "Open failed: " << status.ToString() << std::endl;
return 1;
}
// 写入初始数据
db->Put(rocksdb::WriteOptions(), "key1", "value1_v1");
db->Put(rocksdb::WriteOptions(), "key2", "value2_v1");
// 创建快照
const rocksdb::Snapshot* snapshot = db->GetSnapshot();
std::cout << "Snapshot created at sequence: " << snapshot->GetSequenceNumber() << std::endl;
// 修改数据
db->Put(rocksdb::WriteOptions(), "key1", "value1_v2");
db->Put(rocksdb::WriteOptions(), "key2", "value2_v2");
db->Put(rocksdb::WriteOptions(), "key3", "value3_v1");
// 读取当前数据
std::string value;
std::cout << "\nCurrent data:" << std::endl;
db->Get(rocksdb::ReadOptions(), "key1", &value);
std::cout << "key1 = " << value << std::endl;
db->Get(rocksdb::ReadOptions(), "key2", &value);
std::cout << "key2 = " << value << std::endl;
db->Get(rocksdb::ReadOptions(), "key3", &value);
std::cout << "key3 = " << value << std::endl;
// 通过快照读取历史数据
rocksdb::ReadOptions snapshot_options;
snapshot_options.snapshot = snapshot;
std::cout << "\nSnapshot data:" << std::endl;
db->Get(snapshot_options, "key1", &value);
std::cout << "key1 = " << value << std::endl;
db->Get(snapshot_options, "key2", &value);
std::cout << "key2 = " << value << std::endl;
status = db->Get(snapshot_options, "key3", &value);
if (status.IsNotFound()) {
std::cout << "key3 not found (not in snapshot)" << std::endl;
}
// 释放快照
db->ReleaseSnapshot(snapshot);
delete db;
return 0;
}
二、快照应用场景 #
2.1 一致性读取 #
cpp
#include <rocksdb/db.h>
#include <iostream>
#include <vector>
// 一致性读取多个键
std::vector<std::pair<std::string, std::string>>
ConsistentMultiGet(rocksdb::DB* db, const std::vector<std::string>& keys) {
// 创建快照
const rocksdb::Snapshot* snapshot = db->GetSnapshot();
// 使用快照读取
rocksdb::ReadOptions read_options;
read_options.snapshot = snapshot;
std::vector<std::pair<std::string, std::string>> result;
for (const auto& key : keys) {
std::string value;
rocksdb::Status status = db->Get(read_options, key, &value);
if (status.ok()) {
result.push_back({key, value});
}
}
// 释放快照
db->ReleaseSnapshot(snapshot);
return result;
}
int main() {
rocksdb::DB* db;
rocksdb::Options options;
options.create_if_missing = true;
rocksdb::DB::Open(options, "/tmp/testdb", &db);
// 写入相关数据
db->Put(rocksdb::WriteOptions(), "user:1:name", "Alice");
db->Put(rocksdb::WriteOptions(), "user:1:age", "25");
db->Put(rocksdb::WriteOptions(), "user:1:email", "alice@example.com");
// 一致性读取
std::vector<std::string> keys = {
"user:1:name",
"user:1:age",
"user:1:email"
};
auto data = ConsistentMultiGet(db, keys);
std::cout << "Consistent read result:" << std::endl;
for (const auto& [key, value] : data) {
std::cout << " " << key << " = " << value << std::endl;
}
delete db;
return 0;
}
2.2 实现事务隔离 #
cpp
#include <rocksdb/db.h>
#include <rocksdb/options.h>
#include <iostream>
#include <thread>
#include <chrono>
// 模拟事务隔离
class ReadOnlyTransaction {
public:
ReadOnlyTransaction(rocksdb::DB* db) : db_(db) {
snapshot_ = db_->GetSnapshot();
}
~ReadOnlyTransaction() {
if (snapshot_) {
db_->ReleaseSnapshot(snapshot_);
}
}
std::string Get(const std::string& key) {
rocksdb::ReadOptions options;
options.snapshot = snapshot_;
std::string value;
db_->Get(options, key, &value);
return value;
}
rocksdb::Iterator* NewIterator() {
rocksdb::ReadOptions options;
options.snapshot = snapshot_;
return db_->NewIterator(options);
}
private:
rocksdb::DB* db_;
const rocksdb::Snapshot* snapshot_;
};
int main() {
rocksdb::DB* db;
rocksdb::Options options;
options.create_if_missing = true;
rocksdb::DB::Open(options, "/tmp/testdb", &db);
// 写入初始数据
db->Put(rocksdb::WriteOptions(), "counter", "0");
// 创建只读事务
ReadOnlyTransaction txn(db);
// 另一个线程修改数据
std::thread writer([db]() {
for (int i = 1; i <= 10; i++) {
db->Put(rocksdb::WriteOptions(), "counter", std::to_string(i));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
// 事务中多次读取,值应该保持不变
for (int i = 0; i < 5; i++) {
std::string value = txn.Get("counter");
std::cout << "Transaction read: counter = " << value << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
writer.join();
// 事务结束后读取最新值
std::string latest;
db->Get(rocksdb::ReadOptions(), "counter", &latest);
std::cout << "Latest value: counter = " << latest << std::endl;
delete db;
return 0;
}
2.3 数据备份 #
cpp
#include <rocksdb/db.h>
#include <rocksdb/options.h>
#include <rocksdb/utilities/checkpoint.h>
#include <iostream>
void CreateBackup(rocksdb::DB* db, const std::string& backup_dir) {
// 创建快照确保一致性
const rocksdb::Snapshot* snapshot = db->GetSnapshot();
// 创建检查点(基于快照)
rocksdb::Checkpoint* checkpoint;
rocksdb::Status status = rocksdb::Checkpoint::Create(db, &checkpoint);
if (status.ok()) {
status = checkpoint->CreateCheckpoint(backup_dir);
if (status.ok()) {
std::cout << "Backup created successfully at " << backup_dir << std::endl;
}
}
delete checkpoint;
db->ReleaseSnapshot(snapshot);
}
三、快照迭代 #
3.1 快照迭代器 #
cpp
#include <rocksdb/db.h>
#include <iostream>
void IterateWithSnapshot(rocksdb::DB* db) {
// 写入初始数据
for (int i = 0; i < 5; i++) {
db->Put(rocksdb::WriteOptions(),
"key" + std::to_string(i),
"v1_" + std::to_string(i));
}
// 创建快照
const rocksdb::Snapshot* snapshot = db->GetSnapshot();
// 修改数据
for (int i = 0; i < 5; i++) {
db->Put(rocksdb::WriteOptions(),
"key" + std::to_string(i),
"v2_" + std::to_string(i));
}
db->Put(rocksdb::WriteOptions(), "key5", "v2_5");
// 使用快照迭代
rocksdb::ReadOptions snapshot_options;
snapshot_options.snapshot = snapshot;
std::cout << "Snapshot iteration:" << std::endl;
rocksdb::Iterator* it = db->NewIterator(snapshot_options);
for (it->SeekToFirst(); it->Valid(); it->Next()) {
std::cout << " " << it->key().ToString()
<< " => " << it->value().ToString() << std::endl;
}
delete it;
// 当前数据迭代
std::cout << "\nCurrent iteration:" << std::endl;
it = db->NewIterator(rocksdb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
std::cout << " " << it->key().ToString()
<< " => " << it->value().ToString() << std::endl;
}
delete it;
db->ReleaseSnapshot(snapshot);
}
3.2 比较不同快照 #
cpp
#include <rocksdb/db.h>
#include <iostream>
#include <vector>
#include <string>
void CompareSnapshots(rocksdb::DB* db) {
// 初始数据
db->Put(rocksdb::WriteOptions(), "key1", "v1");
db->Put(rocksdb::WriteOptions(), "key2", "v1");
db->Put(rocksdb::WriteOptions(), "key3", "v1");
// 快照1
const rocksdb::Snapshot* snap1 = db->GetSnapshot();
// 修改
db->Put(rocksdb::WriteOptions(), "key1", "v2");
db->Delete(rocksdb::WriteOptions(), "key2");
db->Put(rocksdb::WriteOptions(), "key4", "v1");
// 快照2
const rocksdb::Snapshot* snap2 = db->GetSnapshot();
// 再次修改
db->Put(rocksdb::WriteOptions(), "key1", "v3");
db->Put(rocksdb::WriteOptions(), "key3", "v2");
// 比较三个时间点的数据
std::vector<std::string> keys = {"key1", "key2", "key3", "key4"};
std::cout << "Key\t\tSnap1\tSnap2\tCurrent" << std::endl;
std::cout << "----\t\t-----\t-----\t-------" << std::endl;
for (const auto& key : keys) {
std::string v1, v2, v3;
rocksdb::ReadOptions opt1;
opt1.snapshot = snap1;
db->Get(opt1, key, &v1);
rocksdb::ReadOptions opt2;
opt2.snapshot = snap2;
db->Get(opt2, key, &v2);
db->Get(rocksdb::ReadOptions(), key, &v3);
std::cout << key << "\t\t"
<< (v1.empty() ? "-" : v1) << "\t"
<< (v2.empty() ? "-" : v2) << "\t"
<< (v3.empty() ? "-" : v3) << std::endl;
}
db->ReleaseSnapshot(snap1);
db->ReleaseSnapshot(snap2);
}
四、快照管理 #
4.1 快照生命周期 #
cpp
#include <rocksdb/db.h>
#include <iostream>
class ScopedSnapshot {
public:
explicit ScopedSnapshot(rocksdb::DB* db)
: db_(db), snapshot_(db->GetSnapshot()) {}
~ScopedSnapshot() {
if (snapshot_) {
db_->ReleaseSnapshot(snapshot_);
}
}
const rocksdb::Snapshot* Get() const { return snapshot_; }
rocksdb::ReadOptions GetReadOptions() const {
rocksdb::ReadOptions options;
options.snapshot = snapshot_;
return options;
}
private:
rocksdb::DB* db_;
const rocksdb::Snapshot* snapshot_;
ScopedSnapshot(const ScopedSnapshot&) = delete;
ScopedSnapshot& operator=(const ScopedSnapshot&) = delete;
};
// 使用示例
void UseScopedSnapshot(rocksdb::DB* db) {
ScopedSnapshot snap(db);
std::string value;
db->Get(snap.GetReadOptions(), "key", &value);
// 函数结束时自动释放快照
}
4.2 快照列表管理 #
cpp
#include <rocksdb/db.h>
#include <vector>
#include <memory>
class SnapshotManager {
public:
explicit SnapshotManager(rocksdb::DB* db) : db_(db) {}
const rocksdb::Snapshot* CreateSnapshot() {
const rocksdb::Snapshot* snapshot = db_->GetSnapshot();
snapshots_.push_back(snapshot);
return snapshot;
}
void ReleaseSnapshot(const rocksdb::Snapshot* snapshot) {
auto it = std::find(snapshots_.begin(), snapshots_.end(), snapshot);
if (it != snapshots_.end()) {
db_->ReleaseSnapshot(snapshot);
snapshots_.erase(it);
}
}
void ReleaseAll() {
for (const rocksdb::Snapshot* snapshot : snapshots_) {
db_->ReleaseSnapshot(snapshot);
}
snapshots_.clear();
}
size_t Count() const { return snapshots_.size(); }
private:
rocksdb::DB* db_;
std::vector<const rocksdb::Snapshot*> snapshots_;
};
五、快照与Compaction #
5.1 快照对Compaction的影响 #
text
Compaction与快照的关系:
1. 快照会阻止数据被清理
- 有快照指向的旧版本数据不会被删除
- 可能导致空间放大
2. 快照释放后
- 旧版本数据可以被清理
- Compaction可以正常进行
3. 建议
- 及时释放不再使用的快照
- 避免长时间持有快照
5.2 监控快照影响 #
cpp
#include <rocksdb/db.h>
#include <iostream>
void MonitorSnapshotImpact(rocksdb::DB* db) {
// 获取快照数量
uint64_t num_snapshots = 0;
db->GetIntProperty("rocksdb.num-snapshots", &num_snapshots);
std::cout << "Number of snapshots: " << num_snapshots << std::endl;
// 获取最旧快照的序列号
uint64_t oldest_snapshot_seq = 0;
db->GetIntProperty("rocksdb.oldest-snapshot-sequence", &oldest_snapshot_seq);
std::cout << "Oldest snapshot sequence: " << oldest_snapshot_seq << std::endl;
// 获取当前序列号
uint64_t current_seq = db->GetLatestSequenceNumber();
std::cout << "Current sequence: " << current_seq << std::endl;
// 计算快照滞后
uint64_t lag = current_seq - oldest_snapshot_seq;
std::cout << "Snapshot lag: " << lag << " versions" << std::endl;
}
六、高级应用 #
6.1 实现时间旅行查询 #
cpp
#include <rocksdb/db.h>
#include <map>
#include <string>
#include <chrono>
class TimeSeriesDB {
public:
TimeSeriesDB(rocksdb::DB* db) : db_(db) {}
// 记录带时间戳的快照
void RecordSnapshot(const std::string& name) {
const rocksdb::Snapshot* snapshot = db_->GetSnapshot();
uint64_t timestamp = std::chrono::system_clock::now().time_since_epoch().count();
snapshots_[name] = {snapshot, timestamp};
}
// 查询特定时间点的数据
std::string GetAtTime(const std::string& key, const std::string& snapshot_name) {
auto it = snapshots_.find(snapshot_name);
if (it == snapshots_.end()) {
return "";
}
rocksdb::ReadOptions options;
options.snapshot = it->second.snapshot;
std::string value;
db_->Get(options, key, &value);
return value;
}
// 清理旧快照
void CleanupOldSnapshots(uint64_t max_age_ns) {
uint64_t now = std::chrono::system_clock::now().time_since_epoch().count();
for (auto it = snapshots_.begin(); it != snapshots_.end(); ) {
if (now - it->second.timestamp > max_age_ns) {
db_->ReleaseSnapshot(it->second.snapshot);
it = snapshots_.erase(it);
} else {
++it;
}
}
}
private:
rocksdb::DB* db_;
struct SnapshotInfo {
const rocksdb::Snapshot* snapshot;
uint64_t timestamp;
};
std::map<std::string, SnapshotInfo> snapshots_;
};
6.2 实现乐观锁 #
cpp
#include <rocksdb/db.h>
#include <rocksdb/options.h>
#include <iostream>
class OptimisticLock {
public:
OptimisticLock(rocksdb::DB* db, const std::string& key)
: db_(db), key_(key), committed_(false) {
// 创建快照作为版本基准
snapshot_ = db_->GetSnapshot();
// 读取当前值
rocksdb::ReadOptions options;
options.snapshot = snapshot_;
db_->Get(options, key_, &original_value_);
}
~OptimisticLock() {
if (!committed_) {
Rollback();
}
db_->ReleaseSnapshot(snapshot_);
}
std::string GetValue() const { return original_value_; }
bool Commit(const std::string& new_value) {
// 检查是否被修改
std::string current_value;
rocksdb::Status status = db_->Get(rocksdb::ReadOptions(), key_, ¤t_value);
bool modified = status.ok() ? (current_value != original_value_) : !original_value_.empty();
if (modified) {
return false; // 冲突,提交失败
}
// 无冲突,提交
status = db_->Put(rocksdb::WriteOptions(), key_, new_value);
if (status.ok()) {
committed_ = true;
return true;
}
return false;
}
void Rollback() {
// 乐观锁不需要显式回滚
committed_ = false;
}
private:
rocksdb::DB* db_;
std::string key_;
const rocksdb::Snapshot* snapshot_;
std::string original_value_;
bool committed_;
};
// 使用示例
void DemoOptimisticLock(rocksdb::DB* db) {
db->Put(rocksdb::WriteOptions(), "counter", "100");
OptimisticLock lock(db, "counter");
int value = std::stoi(lock.GetValue());
value++;
if (lock.Commit(std::to_string(value))) {
std::cout << "Commit successful: counter = " << value << std::endl;
} else {
std::cout << "Commit failed due to conflict" << std::endl;
}
}
七、最佳实践 #
7.1 快照使用建议 #
| 建议 | 说明 |
|---|---|
| 及时释放 | 不再使用时立即释放快照 |
| 使用RAII | 用类封装快照生命周期 |
| 监控数量 | 关注快照数量和滞后 |
| 避免长时间持有 | 长时间快照会影响Compaction |
| 批量操作 | 在快照内完成批量读取 |
7.2 性能影响 #
text
快照的性能影响:
1. 内存影响
- 快照本身内存占用很小
- 但会阻止旧版本数据清理
2. Compaction影响
- 旧版本数据不能被删除
- 可能导致空间放大
3. 读取性能
- 快照读取需要检查版本
- 性能略低于普通读取
4. 建议
- 快照用完立即释放
- 避免创建过多快照
- 定期检查快照状态
八、总结 #
8.1 快照API速查 #
| 操作 | 方法 | 说明 |
|---|---|---|
| 创建快照 | db->GetSnapshot() |
创建当前状态快照 |
| 释放快照 | db->ReleaseSnapshot(snapshot) |
释放快照 |
| 使用快照读取 | ReadOptions.snapshot = snapshot |
设置读取选项 |
| 获取序列号 | snapshot->GetSequenceNumber() |
获取快照序列号 |
8.2 关键要点 #
- 一致性保证:快照提供一致性读取视图
- 轻量级:创建快照成本很低
- 及时释放:避免影响Compaction
- RAII封装:使用类管理快照生命周期
- 监控状态:关注快照数量和滞后
下一步,让我们学习高级特性!
最后更新:2026-03-27