CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • leveldb源码分析1
  • leveldb源码分析2
  • leveldb源码分析3
  • leveldb源码分析4
  • leveldb源码分析5
  • leveldb源码分析6
  • leveldb源码分析7
  • leveldb源码分析8
  • leveldb源码分析9
  • leveldb源码分析10
  • leveldb源码分析11
  • leveldb源码分析12
  • leveldb源码分析13
  • leveldb源码分析14
  • leveldb源码分析15
  • leveldb源码分析16
  • leveldb源码分析17
  • leveldb源码分析18
  • leveldb源码分析19
    • 11.VersionSet分析之2
      • 11.4 LogAndApply()
      • 11.4.1 函数流程
      • 11.4.2 WriteSnapshot()
      • 11.4.3 ManifestContains()
      • 11.5 ApproximateOffsetOf()
  • leveldb源码分析20
  • leveldb源码分析21
  • leveldb源码分析22
  • leveldb源码分析
zhangxf
2023-04-02
目录

leveldb源码分析19

# leveldb源码分析19

本系列《leveldb源码分析》共有22篇文章,这是第十九篇

# 11.VersionSet分析之2

# 11.4 LogAndApply()

函数声明:

Status LogAndApply(VersionEdit*edit, port::Mutex* mu)
1

前面接口小节中讲过其功能:在currentversion上应用指定的VersionEdit,生成新的MANIFEST信息,保存到磁盘上,并用作current version,故为Log And Apply。 参数edit也会被函数修改。

# 11.4.1 函数流程

下面就来具体分析函数代码。 S1 为edit设置log number等4个计数器。

if (edit->has_log_number_) {
    assert(edit->log_number_ >= log_number_);
    assert(edit->log_number_ < next_file_number_);
}
else edit->SetLogNumber(log_number_);
if (!edit->has_prev_log_number_) edit->SetPrevLogNumber(prev_log_number_);
edit->SetNextFile(next_file_number_);
edit->SetLastSequence(last_sequence_);
1
2
3
4
5
6
7
8

要保证edit自己的log number是比较大的那个,否则就是致命错误。保证edit的log number小于next file number,否则就是致命错误-见9.1小节。

S2 创建一个新的Version v,并把新的edit变动保存到v中。

Version* v = new Version(this);
{
    Builder builder(this, current_);
    builder.Apply(edit);
    builder.SaveTo(v);
}
Finalize(v); //如前分析,只是为v计算执行compaction的最佳level  
1
2
3
4
5
6
7

S3 如果MANIFEST文件指针不存在,就创建并初始化一个新的MANIFEST文件。这只会发生在第一次打开数据库时。这个MANIFEST文件保存了current version的快照。

std::string new_manifest_file;
Status s;
if (descriptor_log_ == NULL) {
    // 这里不需要unlock *mu因为我们只会在第一次调用LogAndApply时  
    // 才走到这里(打开数据库时).  
    assert(descriptor_file_ == NULL); // 文件指针和log::Writer都应该是NULL  
    new_manifest_file = DescriptorFileName(dbname_, manifest_file_number_);
    edit->SetNextFile(next_file_number_);
    s = env_->NewWritableFile(new_manifest_file, &descriptor_file_);
    if (s.ok()) {
        descriptor_log_ = new log::Writer(descriptor_file_);
        s = WriteSnapshot(descriptor_log_); // 写入快照  
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

S4 向MANIFEST写入一条新的log,记录current version的信息。在文件写操作时unlock锁,写入完成后,再重新lock,以防止浪费在长时间的IO操作上。

[cpp] view plain copy
mu->Unlock();
if (s.ok()) {
    std::string record;
    edit->EncodeTo(&record);// 序列化current version信息  
    s = descriptor_log_->AddRecord(record); // append到MANIFEST log中  
    if (s.ok()) s = descriptor_file_->Sync();
    if (!s.ok()) {
        Log(options_->info_log, "MANIFEST write: %s\n", s.ToString().c_str());
        if (ManifestContains(record)) { // 返回出错,其实确实写成功了  
            Log(options_->info_log, "MANIFEST contains log record despiteerror ");
            s = Status::OK();
        }
    }
}
//如果刚才创建了一个MANIFEST文件,通过写一个指向它的CURRENT文件  
//安装它;不需要再次检查MANIFEST是否出错,因为如果出错后面会删除它  
if (s.ok() && !new_manifest_file.empty()) {
    s = SetCurrentFile(env_, dbname_, manifest_file_number_);
}
mu->Lock();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

S5 安装这个新的version

if (s.ok()) { // 安装这个version  
    AppendVersion(v);
    log_number_ = edit->log_number_;
    prev_log_number_ = edit->prev_log_number_;
}
else { // 失败了,删除  
    delete v;
    if (!new_manifest_file.empty()) {
        delete descriptor_log_;
        delete descriptor_file_;
        descriptor_log_ = descriptor_file_ = NULL;
        env_->DeleteFile(new_manifest_file);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

流程的S4中,函数会检查MANIFEST文件是否已经有了这条record,那么什么时候会有呢?

主函数使用到了几个新的辅助函数WriteSnapshot,ManifestContains和SetCurrentFile,下面就来分析。

# 11.4.2 WriteSnapshot()

函数声明:

Status WriteSnapshot(log::Writer*log)
1

把currentversion保存到*log中,信息包括comparator名字、compaction点和各级sstable文件,函数逻辑很直观。

  • S1 首先声明一个新的VersionEdit edit;
  • S2 设置comparator:edit.SetComparatorName(icmp_.user_comparator()->Name());
  • S3 遍历所有level,根据compact_pointer_[level],设置compaction点: edit.SetCompactPointer(level, key);
  • S4 遍历所有level,根据current_->files_,设置sstable文件集合:edit.AddFile(level, xxx)
  • S5 根据序列化并append到log(MANIFEST文件)中;
std::string record;
edit.EncodeTo(&record);
returnlog->AddRecord(record);
1
2
3

以上就是WriteSnapshot的代码逻辑。

# 11.4.3 ManifestContains()

函数声明:

bool ManifestContains(conststd::string& record)
1

如果当前MANIFEST包含指定的record就返回true,来看看函数逻辑。

  • S1 根据当前的manifest_file_number_文件编号打开文件,创建SequentialFile对象

  • S2 根据创建的SequentialFile对象创建log::Reader,以读取文件

  • S3 调用log::Reader的ReadRecord依次读取record,如果和指定的record相同,就返回true,没有相同的record就返回false

SetCurrentFile很简单,就是根据指定manifest文件编号,构造出MANIFEST文件名,并写入到CURRENT即可。

# 11.5 ApproximateOffsetOf()

函数声明:

uint64_tApproximateOffsetOf(Version* v, const InternalKey& ikey)
1

在指定的version中查找指定key的大概位置。 假设version中有n个sstable文件,并且落在了地i个sstable的key空间内,那么返回的位置**= sstable1文件大小+sstable2文件大小 + … + sstable (i-1)文件大小 + key在sstable i中的大概偏移**。 可分为两段逻辑。

  • 首先直接和sstable的max key作比较,如果key > max key,直接跳过该文件,还记得sstable文件是有序排列的。 对于level >0的文件集合而言,如果如果key < sstable文件的min key,则直接跳出循环,因为后续的sstable的min key肯定大于key。

  • key在sstable i中的大概偏移使用的是Table:: ApproximateOffsetOf(target)接口,前面分析过,它返回的是Table中>= target的key的位置。

VersionSet的相关函数暂时分析到这里,compaction部分后需单独分析。

编辑 (opens new window)
上次更新: 2023/12/11, 22:32:09
leveldb源码分析18
leveldb源码分析20

← leveldb源码分析18 leveldb源码分析20→

最近更新
01
第二章 关键字static及其不同用法
03-27
02
第一章 auto与类型推导
03-27
03
C++语言面试问题集锦 目录与说明
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式