RocksDB是Facebook基于LevelDB开发的高性能嵌入式键值存储引擎,采用LSM树(Log-Structured Merge-Tree)数据结构,在写入密集型场景下性能远超B-Tree数据库。RocksDB不提供网络层和SQL接口,作为库嵌入应用程序中,被MySQL RocksDB、TiDB TiKV、Kafka Streams状态存储等系统作为底层存储。后端开发中,理解LSM树读写放大原理并合理配置RocksDB参数,是构建高性能持久化层的核心能力。
LSM树数据结构与读写放大原理
LSM树将随机写入转换为顺序写入,所有写操作先写入WAL(Write-Ahead Log)保证持久性,再写入内存中的MemTable。MemTable写满后变为Immutable MemTable,被后台线程flush到磁盘成为SSTable。SSTable按层级组织,L0层SSTable范围重叠,L1及以下层SSTable范围不重叠。
// LSM树层级结构
//
// Write Path:
// Client -> WAL (append) -> MemTable (skip list in memory)
// -> MemTable full -> Immutable MemTable -> Flush -> L0 SSTable
//
// Read Path:
// Client -> MemTable -> Immutable MemTable -> L0 (multiple files, binary search each)
// -> L1 (one file per range) -> L2 -> ... -> Ln
//
// Compaction:
// L0 -> L1: 当L0文件数 >= 4 时触发
// Ln -> Ln+1: 当Ln总大小超过 10^n MB 时触发
//
// Write Amplification = (WAL write) + (MemTable flush) + (Compaction L0->L1 + L1->L2 + ...)
// 典型值: 10-30x (vs B-Tree的2-4x)
// Read Amplification = L0文件数 + 各层查找次数
// 典型值: 6-15x (Bloom Filter可大幅降低)
// Space Amplification: 同一key的新旧版本占用的额外空间
// 典型值: 1.1-2.0x
RocksDB C++ API核心操作与Column Family管理
#include
#include
#include
#include
#include
#include
// 基本数据库操作
void basic_operations() {
rocksdb::DB* db;
rocksdb::Options options;
// 核心配置
options.create_if_missing = true;
options.compression = rocksdb::kLZ4Compression;
options.level_compaction_dynamic_level_bytes = true;
// 写缓冲区(MemTable大小)
options.write_buffer_size = 64 * 1024 * 1024; // 64MB
options.max_write_buffer_number = 4;
options.min_write_buffer_number_to_merge = 2;
// L0层控制
options.level0_file_num_compaction_trigger = 4;
options.level0_slowdown_writes_trigger = 20;
options.level0_stop_writes_trigger = 36;
// 层级大小
options.max_bytes_for_level_base = 256 * 1024 * 1024;
options.max_bytes_for_level_multiplier = 10;
options.num_levels = 7;
// 后台线程
options.max_background_jobs = 12;
// Block Cache(读缓存)
rocksdb::BlockBasedTableOptions table_options;
table_options.block_cache = rocksdb::NewLRUCache(512 * 1024 * 1024);
table_options.block_size = 4 * 1024;
table_options.cache_index_and_filter_blocks = true;
table_options.pin_l0_filter_and_index_blocks_in_cache = true;
options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options));
rocksdb::Status s = rocksdb::DB::Open(options, "/data/rocksdb", &db);
if (!s.ok()) {
std::cerr << "Open failed: " << s.ToString() << std::endl;
return;
}
// 写入
rocksdb::WriteOptions write_opts;
write_opts.sync = false;
write_opts.disableWAL = false;
s = db->Put(write_opts, "key1", "value1");
s = db->Put(write_opts, "key2", "value2");
// 批量写入(原子性)
rocksdb::WriteBatch batch;
batch.Put("key3", "value3");
batch.Put("key4", "value4");
batch.Delete("key1");
s = db->Write(write_opts, &batch);
// 读取
rocksdb::ReadOptions read_opts;
std::string value;
s = db->Get(read_opts, "key2", &value);
if (s.ok()) {
std::cout << "key2 = " << value << std::endl;
}
// 前缀扫描
rocksdb::Iterator* it = db->NewIterator(read_opts);
for (it->Seek("key"); it->Valid(); it->Next()) {
std::cout << it->key().ToString() << ": " << it->value().ToString() << std::endl;
if (!it->key().starts_with("key")) break;
}
delete it;
delete db;
}
Column Family多列族配置与差异化调优
Column Family允许同一个RocksDB实例内不同数据使用不同的compaction策略、压缩算法和缓存配置。适合在一个实例中同时存储热数据和冷数据。
void column_family_operations() {
rocksdb::DB* db;
rocksdb::Options options;
options.create_if_missing = true;
std::vector cf_descs;
// default列族 - 热数据
rocksdb::ColumnFamilyOptions default_opts;
default_opts.write_buffer_size = 128 * 1024 * 1024;
default_opts.num_levels = 7;
default_opts.compression = rocksdb::kLZ4Compression;
default_opts.max_bytes_for_level_base = 512 * 1024 * 1024;
default_opts.target_file_size_base = 64 * 1024 * 1024;
rocksdb::BlockBasedTableOptions hot_table_opts;
hot_table_opts.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10, false));
hot_table_opts.block_cache = rocksdb::NewLRUCache(256 * 1024 * 1024);
default_opts.table_factory.reset(rocksdb::NewBlockBasedTableFactory(hot_table_opts));
cf_descs.push_back({"default", default_opts});
// 冷数据列族 - 归档数据,强压缩
rocksdb::ColumnFamilyOptions cold_opts;
cold_opts.write_buffer_size = 32 * 1024 * 1024;
cold_opts.compression = rocksdb::kZSTDCompression;
cold_opts.num_levels = 7;
cold_opts.max_bytes_for_level_base = 1024 * 1024 * 1024;
cold_opts.compaction_pri = rocksdb::kMinOverlappingRatio;
rocksdb::BlockBasedTableOptions cold_table_opts;
cold_table_opts.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10, false));
cold_table_opts.block_cache = rocksdb::NewLRUCache(64 * 1024 * 1024);
cold_opts.table_factory.reset(rocksdb::NewBlockBasedTableFactory(cold_table_opts));
cf_descs.push_back({"cold_data", cold_opts});
// 索引列族 - 小数据,高读性能
rocksdb::ColumnFamilyOptions index_opts;
index_opts.write_buffer_size = 16 * 1024 * 1024;
index_opts.compression = rocksdb::kNoCompression;
index_opts.max_bytes_for_level_base = 64 * 1024 * 1024;
rocksdb::BlockBasedTableOptions index_table_opts;
index_table_opts.filter_policy.reset(rocksdb::NewBloomFilterPolicy(20, false));
index_table_opts.block_cache = rocksdb::NewLRUCache(512 * 1024 * 1024);
index_opts.table_factory.reset(rocksdb::NewBlockBasedTableFactory(index_table_opts));
cf_descs.push_back({"index", index_opts});
std::vector handles;
rocksdb::Status s = rocksdb::DB::Open(options, "/data/rocksdb_cf", cf_descs, &handles, &db);
db->Put(rocksdb::WriteOptions(), handles[0], "hot_key", "hot_value");
db->Put(rocksdb::WriteOptions(), handles[1], "cold_key", "cold_value");
db->Put(rocksdb::WriteOptions(), handles[2], "idx:001", "metadata");
std::string val;
db->Get(rocksdb::ReadOptions(), handles[1], "cold_key", &val);
for (auto h : handles) {
db->DestroyColumnFamilyHandle(h);
}
delete db;
}
Write Batch事务与悲观锁并发控制
#include
void transaction_operations() {
rocksdb::TransactionDB* txn_db;
rocksdb::Options options;
options.create_if_missing = true;
rocksdb::TransactionDBOptions txn_db_opts;
txn_db_opts.max_num_locks = 1000000;
txn_db_opts.num_stripes = 16;
txn_db_opts.transaction_lock_timeout_ms = 5000;
rocksdb::Status s = rocksdb::TransactionDB::Open(
options, txn_db_opts, "/data/rocksdb_txn", &txn_db);
rocksdb::TransactionOptions txn_opts;
txn_opts.set_snapshot = true;
rocksdb::Transaction* txn = txn_db->BeginTransaction(
rocksdb::WriteOptions(), txn_opts);
rocksdb::ReadOptions read_opts;
read_opts.snapshot = txn->GetSnapshot();
txn->Put("account:A", "900");
txn->Put("account:B", "1100");
std::string balance_a;
txn->GetForUpdate(read_opts, "account:A", &balance_a);
s = txn->Commit();
if (!s.ok()) {
std::cerr << "Commit failed: " << s.ToString() << std::endl;
txn->Rollback();
}
delete txn;
delete txn_db;
}
Compaction策略调优与Write Amplification测量
RocksDB的compaction策略直接影响写放大(WA)、读放大(RA)和空间放大(SA)的权衡。通用策略有三种:Level(默认)、Universal、FIFO。
// Level Compaction(默认,适合读多写少)
rocksdb::Options level_opts;
level_opts.compaction_style = rocksdb::kCompactionStyleLevel;
level_opts.level_compaction_dynamic_level_bytes = true;
level_opts.max_bytes_for_level_base = 256 * 1024 * 1024;
level_opts.max_bytes_for_level_multiplier = 10;
level_opts.target_file_size_base = 64 * 1024 * 1024;
level_opts.target_file_size_multiplier = 1;
// Universal Compaction(适合写多读少,SSD环境)
rocksdb::Options universal_opts;
universal_opts.compaction_style = rocksdb::kCompactionStyleUniversal;
universal_opts.min_write_buffer_number_to_merge = 2;
universal_opts.max_merge_width = 10;
universal_opts.min_merge_width = 2;
universal_opts.compaction_options_universal.size_ratio = 1;
universal_opts.compaction_options_universal.max_size_amplification_percent = 200;
// 统计信息收集
void print_statistics(rocksdb::DB* db) {
std::string stats;
db->GetProperty("rocksdb.write-amplification", &stats);
std::cout << "Write Amp: " << stats << std::endl;
db->GetProperty("rocksdb.read-amplification", &stats);
std::cout << "Read Amp: " << stats << std::endl;
db->GetProperty("rocksdb.estimate-live-data-size", &stats);
std::cout << "Live Data: " << stats << " bytes" << std::endl;
db->GetProperty("rocksdb.estimate-num-keys", &stats);
std::cout << "Num Keys: " << stats << std::endl;
db->GetProperty("rocksdb.compaction-pending", &stats);
std::cout << "Compaction Pending: " << stats << std::endl;
db->GetProperty("rocksdb.num-files-at-level0", &stats);
std::cout << "L0 Files: " << stats << std::endl;
uint64_t written, read;
db->GetIntProperty("rocksdb.bytes-written", &written);
db->GetIntProperty("rocksdb.bytes-read", &read);
std::cout << "Bytes Written: " << written << std::endl;
std::cout << "Bytes Read: " << read << std::endl;
}
在中等写入负载(每秒10万次Put)下,Level compaction的写放大约15-25倍,Universal compaction约5-8倍。读延迟方面Level更好,P99通常在0.5ms以下,Universal在1-2ms。实际选择取决于读写比(RW ratio):读写比在1:10以上选Level,1:1以下选Universal。对于TTL场景(如缓存淘汰),FIFO策略的写放大接近1倍,且compaction开销几乎为零,适合用RocksDB替代Redis实现持久化缓存层。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/rocksdblsm-shu-cun-chu-yin-qing-yuan-li-yu-qian-ru-shi-jian/