rocksdb:manifest 文件写流程
目录
警告
本文最后更新于 2024-09-24,文中内容可能已过时。
本文由原作者归档自知乎,原文发布于 2024-09-24。原文链接:rocksdb:manifest 文件写流程。
零、说明
manifest文件是rocksdb用来管理元信息变动的文件。文件中以VersionEdit为单位。记录了所有SST文件的创建、删除、层级调整等变动行为。
我们都知道,写文件之前需要先将文件open,然后才能写入。所以下文会先介绍manifest文件时怎么open的,然后说明一次变动是如何写入manifest文件中的。
一、open 流程
1.1、一句话概述
一个fd被三次封装,最后以log::Writer提供使用。
descriptor_fname -> WritableFile(在这里open得到fd) -> WritableFileWriter -> log::Writer1.2、引用关系
std::string descriptor_fname // 文件名
-> std::unique_ptr<WritableFile> descriptor_file = new PosixWritableFile(descriptor_fname)
// WritableFile 顺序写入的文件抽象。如果实现这个类:需要支持”缓冲,小片段追加到文件,上层控制flush、sync操作”。
// PosixWritableFile 是其中一种实现。还有 PosixMmapFile 等实现
-> std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(std::move(descriptor_file));
// WritableFile 的包装,添加了 io stat 、 写事件回调 、 限速 、 buffer write 或者 direct write 等功能
-> log::Writer descriptor_log_(new log::Writer(std::move(file_writer), 0, false));
// 具体的业务,log字节流如何组织
1.3、代码逻辑
- 先准备 EnvOptions
- 然后基于descriptor_fname得到WritableFile
- 再基于WritableFile得到WritableFileWriter
- 最后基于WritableFileWriter得到log::Writer
// 优化 env 参数:写 manifest 时需要 use_mmap_writes=false; use_direct_writes = false; fallocate_with_keep_size = true;
EnvOptions opt_env_opts = env_->OptimizeForManifestWrite(env_options_);
// 生成文件名 descriptor_fname
std::string descriptor_fname = DescriptorFileName(dbname_, pending_manifest_file_number_);
// 实例化 WritableFile
std::unique_ptr<WritableFile> descriptor_file;
s = NewWritableFile(env_, descriptor_fname, &descriptor_file, opt_env_opts);
descriptor_file->SetPreallocationBlockSize(db_options_->manifest_preallocation_size);
// 将 WritableFile 转为 WritableFileWriter
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(std::move(descriptor_file), descriptor_fname, opt_env_opts, env_,nullptr, db_options_->listeners));
// 将 WritableFileWriter 转为 log::Writer
descriptor_log_.reset(new log::Writer(std::move(file_writer), 0, false));二、write 流程
2.1 一句话概述
先写内存、再进行flush、sync。
2.2 代码逻辑
- manifest记录变动的最小单位是 version_edit,所以需要将 edit->EncodeTo(&record)
- 然后将 record 写入 descriptor_log_,即调用 log::Writer的AddRecord
- 最后每次变动都要执行 SyncManifest 确保数据落盘
// 将 version edit 写入 manifest
Status VersionSet::ProcessManifestWrites(...) {
// ...
for (auto& e : batch_edits) {
e->EncodeTo(&record);
s = descriptor_log_->AddRecord(record); // 逐次 append
}
s = SyncManifest(env_, db_options_, descriptor_log_->file()); // 都写完后一把sync
// ...
}
// 先写
Status Writer::AddRecord(const Slice& slice) {
// ...
EmitPhysicalRecord(type, ptr, fragment_length);
dest_->Flush();
// ...
}
// 再 sync
Status SyncManifest(...) {
return file->Sync(db_options->use_fsync);
}
// 写 record 时,分为两次:先写 header、再写 payload。 secondary 读到的数据是空,是读取到了 header 字段判断发现 type 和 length 是 0。
Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
// ...
Status s = dest_->Append(Slice(buf, header_size));
if (s.ok()) {
s = dest_->Append(Slice(ptr, n));
}
// ...
}
// 整个写流程都是buffer io,这个 buffer 是 rocksdb 自己维护的一个。当 buffer 不够后将 buffer 数据写到 file,然后 flush file。
Status WritableFileWriter::Append(const Slice& data) {
// ...
writable_file_->PrepareWrite(static_cast<size_t>(GetFileSize()), left); // 写 manifest 时此特性已经关闭
// ...
buf_.Append(src, left);
// ...
Flush();
// ...
}
Status WritableFileWriter::Flush() {
// ...
s = WriteBuffered(buf_.BufferStart(), buf_.CurrentSize());
// ...
s = writable_file_->Flush(); // Status PosixWritableFile::Flush() { return Status::OK(); }
}
Status WritableFileWriter::WriteBuffered(const char* data, size_t size) {
//...
s = writable_file_->Append(Slice(src, allowed));
// ...
}
Status PosixWritableFile::Append(const Slice& data) {
// ...
const char* src = data.data();
size_t nbytes = data.size();
if (!PosixWrite(fd_, src, nbytes)) {
return IOError("While appending to file", filename_, errno);
}
filesize_ += nbytes;
// ...
}
bool PosixWrite(int fd, const char* buf, size_t nbyte) {
const size_t kLimit1Gb = 1UL << 30;
const char* src = buf;
size_t left = nbyte;
while (left != 0) {
size_t bytes_to_write = std::min(left, kLimit1Gb);
ssize_t done = write(fd, src, bytes_to_write); // 写数据到fs
if (done < 0) {
if (errno == EINTR) {
continue;
}
return false;
}
left -= done;
src += done;
}
return true;
}
Status WritableFileWriter::Sync(bool use_fsync) {
// ...
Flush();
// ...
SyncInternal(use_fsync);
// ...
}
Status WritableFileWriter::SyncInternal(bool use_fsync) {
// ...
writable_file_->Sync();
// ...
}
Status PosixWritableFile::Sync() {
// ...
fdatasync(fd_); // sync 数据
// ...
}总结
- rocksdb在文件io操作这块做了层层封装和抽象,简化了上层代码的使用方式
- 这样的抽象也使得rocksdb可以很容易适配不同操作系统(win、linux、macos)
- 还可以支持更多的存储类型:如硬盘、hdfs、s3等
- 还可以实现更多的特性:如文件镜像(同时写多份)、模拟文件故障(flush sync做随机故障)