文章目录

  • Open
  • Get
  • Put、Delete、Write

Open

数据库 Open 操作主要用于创建新的 LevelDB 数据库或打开一个已存在的数据库。Open 操作的主要函数共需传递 3 个参数:两个输入参数 options 与 dbname,一个输出参数 dbptr。

首先我们来看看它的代码:

// https://github.com/google/leveldb/blob/master/db/db_impl.ccStatus DB::Open(const Options& options, const std::string& dbname, DB** dbptr) {*dbptr = nullptr;//初始化dbimplDBImpl* impl = new DBImpl(options, dbname);impl->mutex_.Lock();VersionEdit edit;//尝试恢复之前已经存在的数据库文件中的数据bool save_manifest = false;Status s = impl->Recover(&edit, &save_manifest);//判断Memtable是否为空if (s.ok() && impl->mem_ == nullptr) {//创建新的Log和MemTableuint64_t new_log_number = impl->versions_->NewFileNumber();WritableFile* lfile;s = options.env->NewWritableFile(LogFileName(dbname, new_log_number),&lfile);if (s.ok()) {edit.SetLogNumber(new_log_number);impl->logfile_ = lfile;impl->logfile_number_ = new_log_number;impl->log_ = new log::Writer(lfile);impl->mem_ = new MemTable(impl->internal_comparator_);impl->mem_->Ref();}}//判断是否需要保存Manifest文件if (s.ok() && save_manifest) {edit.SetPrevLogNumber(0);  edit.SetLogNumber(impl->logfile_number_);//生成新的版本s = impl->versions_->LogAndApply(&edit, &impl->mutex_);}if (s.ok()) {//请理无用的文件impl->RemoveObsoleteFiles();//尝试进行Compactionimpl->MaybeScheduleCompaction();}impl->mutex_.Unlock();if (s.ok()) {assert(impl->mem_ != nullptr);*dbptr = impl;} else {delete impl;}return s;
}

具体的实现流程如下图所示:

Open执行流程

  1. 初始化一个 DBImpl 的对象 impl,将相关的参数选项 options 与数据库名称 dbname 作为构造函数的参数。
  2. 调用 DBImpl 对象的 Recover 函数,尝试恢复之前存在的数据库文件数据。
  3. 进行 Recover 操作后,判断 impl 对象中的 MemTable 对象指针 mem_ 是否为空,如果为空,则进入第 4 步,不为空则进入第 5 步。
  4. 创建新的 Log 文件以及对应的 MemTable 对象。这一步主要分别实例化 log::Writer 和 MemTable 两个对象,并赋值给 impl 中对应的成员变量,后续通过 impl 中的成员变量操作 Log 文件和 MemTable。
  5. 判断是否需要保存 Manifest 相关信息,如果需要,则保存相关信息。
  6. 判断前面步骤是否都成功了,如果成功,则调用 DeleteObsoleteFiles 函数对一些过时文件进行删除,且调用 MaybeScheduleCompaction 函数尝试进行数据文件的 Compaction 操作。

Get

Get 主要用于从 LevelDB 中获取对应的键-值对数据,它是单个数据读取的主要接口。Get 的主要参数为数据读参数选项 options、键 key,以及一个用于返回数据值的 string 类型指针 value。其代码如下:

// https://github.com/google/leveldb/blob/master/db/db_impl.ccStatus DBImpl::Get(const ReadOptions& options, const Slice& key,std::string* value) {Status s;MutexLock l(&mutex_);SequenceNumber snapshot;//获取序列号并赋值给snapshotif (options.snapshot != nullptr) {snapshot =static_cast<const SnapshotImpl*>(options.snapshot)->sequence_number();} else {snapshot = versions_->LastSequence();}MemTable* mem = mem_;MemTable* imm = imm_;Version* current = versions_->current();mem->Ref();if (imm != nullptr) imm->Ref();current->Ref();bool have_stat_update = false;Version::GetStats stats;{mutex_.Unlock();//首先查找memtableLookupKey lkey(key, snapshot);if (mem->Get(lkey, value, &s)) {//如果查找不到,接着查找immutable} else if (imm != nullptr && imm->Get(lkey, value, &s)) {//如果还是没找到,则继续查找SSTable} else {s = current->Get(options, lkey, value, &stats);have_stat_update = true;}mutex_.Lock();}if (have_stat_update && current->UpdateStats(stats)) {MaybeScheduleCompaction();}mem->Unref();if (imm != nullptr) imm->Unref();current->Unref();return s;
}

具体的实现流程如下图所示:

Get执行流程

Get 在查询读取数据时,依次从 MemTable、Immutable MemTable 以及当前保存的 SSTable 文件中进行查找。如果在 MemTabel 中找到,立即返回对应的数值,如果没有找到,再从 Immutable MemTable 中查找。而如果Immutable MemTable 中还是没有找到,则会从持久化的文件 SSTable 中查找,直到找出该键对应的数值为止。

SequenceNumber 有什么用呢?

其主要作用是对 DB 的整个存储空间进行时间刻度上的序列备份,即要从 DB 中获取某一个数据,不仅需要其对应的键 key,而且需要其对应的时间序列号。对数据库进行写操作会改变序列号,每进行一次写操作,则序列号加 1。

Put、Delete、Write

Put 主要有3个参数:写操作参数 opt、操作数据的 key 与操作数据新值 value。其代码如下:

// https://github.com/google/leveldb/blob/master/db/db_impl.ccStatus DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) {return DB::Put(o, key, val);
}Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) {WriteBatch batch;batch.Put(key, value);return Write(opt, &batch);
}

从上面的代码可以看出, Put 其实也是将单条数据的操作变更为一个批量操作,然后调用 Write 进行实现。

Delete 不会直接删除数据,而是在对应位置插入一个 key 的删除标志,然后在后续的 Compaction 过程中才最终去除这条 key-value 记录。其代码如下:

// https://github.com/google/leveldb/blob/master/db/db_impl.ccStatus DBImpl::Delete(const WriteOptions& options, const Slice& key) {return DB::Delete(options, key);
}Status DB::Delete(const WriteOptions& opt, const Slice& key) {WriteBatch batch;batch.Delete(key);return Write(opt, &batch);
}

从上面的代码可以看出 Delete 的本质其实也是一个 Write 操作。

在介绍 Write 之前,首先介绍其封装的消息结构 Writer 与任务队列 writes_。

Writer 用于保存基本信息,如批量操作 batch、状态信息 status、是否同步 sync、是否完成 done 以及用于多线程操作的条件变量cv 。

// https://github.com/google/leveldb/blob/master/db/db_impl.ccstruct DBImpl::Writer {explicit Writer(port::Mutex* mu): batch(nullptr), sync(false), done(false), cv(mu) {}Status status; //状态WriteBatch* batch; //批量写入对象bool sync;//表示是否已经同步了bool done;//表示是否已经处理完成port::CondVar cv;//这个是条件变量
};

接着看看任务队列 writers_,该队列对象中的元素节点为 Writer 对象指针。可见 writes_ 与写操作的缓存空间有关,批量操作请求均存储在这个队列中,按顺序执行,已完成的出队,而未执行的则在这个队列中处于等待状态。

std::deque<Writer*> writers_ GUARDED_BY(mutex_);

writers_队列示意图

Write 主要有两个参数:WriteOptions 对象与 WriteBatch 对象。WriteOptions 主要包含一些关于写操作的参数选项,而WriteBatch对象,相当于一个缓冲区,用于定义、保存一系列的批量操作。其代码实现如下:

// https://github.com/google/leveldb/blob/master/db/db_impl.ccStatus DBImpl::Write(const WriteOptions& options, WriteBatch* updates) {//实例化一个Writer对象b并插入writers_队列中等待执行Writer w(&mutex_);w.batch = updates;w.sync = options.sync;w.done = false;MutexLock l(&mutex_);writers_.push_back(&w);while (!w.done && &w != writers_.front()) {w.cv.Wait();}if (w.done) {return w.status;}Status status = MakeRoomForWrite(updates == nullptr);uint64_t last_sequence = versions_->LastSequence();Writer* last_writer = &w;if (status.ok() && updates != nullptr) {  //合并写入操作WriteBatch* write_batch = BuildBatchGroup(&last_writer);WriteBatchInternal::SetSequence(write_batch, last_sequence + 1);last_sequence += WriteBatchInternal::Count(write_batch);{mutex_.Unlock();//将更新写入日志文件中,并且将日志文件写入磁盘中status = log_->AddRecord(WriteBatchInternal::Contents(write_batch));bool sync_error = false;if (status.ok() && options.sync) {status = logfile_->Sync();if (!status.ok()) {sync_error = true;}}//将更新写入Memtable中if (status.ok()) {status = WriteBatchInternal::InsertInto(write_batch, mem_);}mutex_.Lock();if (sync_error) {RecordBackgroundError(status);}}if (write_batch == tmp_batch_) tmp_batch_->Clear();versions_->SetLastSequence(last_sequence);}//由于和并写入操作一次可能会处理多个writer_队列中的元素,因此将所有已经处理的元素状态进行变更,并且发送signal信号while (true) {Writer* ready = writers_.front();writers_.pop_front();if (ready != &w) {ready->status = status;ready->done = true;ready->cv.Signal();}if (ready == last_writer) break;}//通知writers_队列中的第一个元素,发送signal信号if (!writers_.empty()) {writers_.front()->cv.Signal();}return status;
}

具体的实现流程如下图所示:

Write执行流程

  • 实例化一个 Writer 对象,并将其插入所示的 writers_ 队列中。

  • 通过 Writer 中的条件变量 cv 调用 wait 方法将该线程挂起,等待其他线程发送 signal 信号,并且等待队列前面的 Writer 操作全部执行完毕:

    • 如果线程收到了 signal 信号:则解除阻塞。
    • 如果线程没有收到了 signal 信号:说明队列前面仍有其他的 Writer 操作,那么该线程会再次调用 wait 方法实现阻塞,从而保证了 Writer 操作按照队列生成次序执行。
  • 当轮到本线程操作时,首先通过 MakeRoomForWrite 函数进行内存空间分配。

  • 当获取到需要的内存后,根据一系列的批量操作,对 Log 文件以及 MemTable 分别进行更新。

  • 依据批量操作的数目更新 SequenceNumber。

  • 通过 Writer 中的条件变量 cv 发送 signal 信号,以通知处于等待状态的其他线程开始执行。

LevelDB 源码剖析(九)DBImpl模块:Open、Get、Put、Delete、Write相关推荐

  1. LevelDB 源码剖析(一)准备工作:环境搭建、接口使用、常用优化

    文章目录 环境搭建 实战使用 创建.关闭数据库 数据读.写.删除 批量处理 迭代器遍历 常用优化方案 压缩 缓存 过滤器 命名 环境搭建 # 下载源码 git clone https://github ...

  2. LevelDB 源码剖析(三)公共基础:内存管理、数值编码、Env家族、文件操作

    文章目录 内存管理 Arena 结构 内存分配 内存使用率统计 TCMalloc Env家族 PosixEnv EnvWrapper InMemoryEnv 文件操作 SequentialFile W ...

  3. LevelDB 源码剖析(六)WAL模块:LOG 结构、读写流程、崩溃恢复

    文章目录 日志结构 读写流程 写入 读取 崩溃恢复 当向 LevelDB 写入数据时,只需要将数据写入内存中的 MemTable,而由于内存是易失性存储,因此 LevelDB 需要一个额外的持久化文件 ...

  4. LevelDB 源码剖析(八)Compaction模块:Minor Compaction、Major Compaction、文件选取、执行流程、垃圾回收

    文章目录 结构 Minor Compaction 定义 触发时机 核心要点 Major Compaction 定义 触发时机 Size Compaction Seek Compaction Manua ...

  5. [Leveldb源码剖析疑问]-block_builder.cc之Add函数

    Add函数是给一个Data block中添加对应的key和value,函数源码如下,其中有一处不理解: L30~L34是更新last_key_的,不理解这里干嘛不直接last_key_ = key.T ...

  6. Python envoy 模块源码剖析

    Kenneth Reitz 是公认的这个世界上 Python 代码写得最好的人之一.抱着学习的心态,我阅读了 Reitz 写的 envoy 模块的源码,将笔记记录如下. 介绍 和 requests 模 ...

  7. Dubbo协议模块源码剖析

    Dubbo协议模块源码剖析 目录 概 述 RPC协议报文编码与实现详解 RPC 传输实现: 拆包与粘包解决办法: Dubbo 报文格式 分析: 小结: 参考资料和推荐阅读 LD is tigger f ...

  8. leveldb 源码阅读 一周目完成

    leveldb 源码阅读项目 - github rsy 一些感想 前前后后总共读了2周,国庆一周,最近这一周 第一周读了 util 和 table 里面的东西.util 还算比较好读:table 里面 ...

  9. Python3.5源码分析-内建模块builtins初始化

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3模块初始化与加载 Python的模块分为内建的模 ...

最新文章

  1. 移动端rem屏幕设置
  2. Linux之OpenSSL
  3. 穷不可怕,可怕的是“穷人思维”
  4. python实现排序算法_数据结构之(3)python实现排序算法
  5. LINUX下的APACHE的配置
  6. mysql导入报编码错误问题解决
  7. python os write_Python 3:写入方法与os.write返回的字节数
  8. C Primer Plus学习笔记(二)
  9. 个人网站如何使用微信扫一扫登录---SpringBoot项目
  10. opencv源码下载编译
  11. 用什么软件测试电脑硬件的问题,新电脑检测软件-我买了新电脑,用什么软件测试比较好?最好还能看见自己详细配置的软 爱问知识人...
  12. MySQL安装步骤【亲测可用】
  13. 2020-10-23 集合+序列化+递归+多线程+泛型+枚举+单例+反射小记
  14. 布尔盲注运用burp的操作
  15. JS特效模板精彩案例!
  16. FeedBurner: 使用RSS路由器的风险
  17. 树莓派4B安装Ubuntu Server20.04(18.04)连接wifi(对于ubuntu server 99%适用)
  18. do while(0)的作用
  19. Keil编译警告汇总(持续更新。。。)
  20. 【日语】日语单词 ---- 身体部位

热门文章

  1. mysql5.58_mysql5.58编译安装手记
  2. 2021宁夏英语高考成绩查询,2021宁夏高考成绩官方查询时间及入口
  3. php redis命令大全,redis中key相关命令详解
  4. docker redis mysql_docker创建redis mysql 等服务
  5. 如何查看 JSP 和 Servlet 的版本
  6. 互联网协议套件(TCP/IP)及七层OSI模型
  7. maven自动化部署插件sshexec-maven-plugin
  8. 《Spark大数据分析:核心概念、技术及实践》一3.5 API
  9. iOS-UICollectionView
  10. 过来人经验!聊聊前端工程师的职业规划