LevelDB 源码剖析(九)DBImpl模块:Open、Get、Put、Delete、Write
文章目录
- 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执行流程
- 初始化一个 DBImpl 的对象 impl,将相关的参数选项 options 与数据库名称 dbname 作为构造函数的参数。
- 调用 DBImpl 对象的
Recover
函数,尝试恢复之前存在的数据库文件数据。 - 进行
Recover
操作后,判断 impl 对象中的 MemTable 对象指针 mem_ 是否为空,如果为空,则进入第 4 步,不为空则进入第 5 步。 - 创建新的 Log 文件以及对应的 MemTable 对象。这一步主要分别实例化 log::Writer 和 MemTable 两个对象,并赋值给 impl 中对应的成员变量,后续通过 impl 中的成员变量操作 Log 文件和 MemTable。
- 判断是否需要保存 Manifest 相关信息,如果需要,则保存相关信息。
- 判断前面步骤是否都成功了,如果成功,则调用
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相关推荐
- LevelDB 源码剖析(一)准备工作:环境搭建、接口使用、常用优化
文章目录 环境搭建 实战使用 创建.关闭数据库 数据读.写.删除 批量处理 迭代器遍历 常用优化方案 压缩 缓存 过滤器 命名 环境搭建 # 下载源码 git clone https://github ...
- LevelDB 源码剖析(三)公共基础:内存管理、数值编码、Env家族、文件操作
文章目录 内存管理 Arena 结构 内存分配 内存使用率统计 TCMalloc Env家族 PosixEnv EnvWrapper InMemoryEnv 文件操作 SequentialFile W ...
- LevelDB 源码剖析(六)WAL模块:LOG 结构、读写流程、崩溃恢复
文章目录 日志结构 读写流程 写入 读取 崩溃恢复 当向 LevelDB 写入数据时,只需要将数据写入内存中的 MemTable,而由于内存是易失性存储,因此 LevelDB 需要一个额外的持久化文件 ...
- LevelDB 源码剖析(八)Compaction模块:Minor Compaction、Major Compaction、文件选取、执行流程、垃圾回收
文章目录 结构 Minor Compaction 定义 触发时机 核心要点 Major Compaction 定义 触发时机 Size Compaction Seek Compaction Manua ...
- [Leveldb源码剖析疑问]-block_builder.cc之Add函数
Add函数是给一个Data block中添加对应的key和value,函数源码如下,其中有一处不理解: L30~L34是更新last_key_的,不理解这里干嘛不直接last_key_ = key.T ...
- Python envoy 模块源码剖析
Kenneth Reitz 是公认的这个世界上 Python 代码写得最好的人之一.抱着学习的心态,我阅读了 Reitz 写的 envoy 模块的源码,将笔记记录如下. 介绍 和 requests 模 ...
- Dubbo协议模块源码剖析
Dubbo协议模块源码剖析 目录 概 述 RPC协议报文编码与实现详解 RPC 传输实现: 拆包与粘包解决办法: Dubbo 报文格式 分析: 小结: 参考资料和推荐阅读 LD is tigger f ...
- leveldb 源码阅读 一周目完成
leveldb 源码阅读项目 - github rsy 一些感想 前前后后总共读了2周,国庆一周,最近这一周 第一周读了 util 和 table 里面的东西.util 还算比较好读:table 里面 ...
- Python3.5源码分析-内建模块builtins初始化
Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3模块初始化与加载 Python的模块分为内建的模 ...
最新文章
- 移动端rem屏幕设置
- Linux之OpenSSL
- 穷不可怕,可怕的是“穷人思维”
- python实现排序算法_数据结构之(3)python实现排序算法
- LINUX下的APACHE的配置
- mysql导入报编码错误问题解决
- python os write_Python 3:写入方法与os.write返回的字节数
- C Primer Plus学习笔记(二)
- 个人网站如何使用微信扫一扫登录---SpringBoot项目
- opencv源码下载编译
- 用什么软件测试电脑硬件的问题,新电脑检测软件-我买了新电脑,用什么软件测试比较好?最好还能看见自己详细配置的软 爱问知识人...
- MySQL安装步骤【亲测可用】
- 2020-10-23 集合+序列化+递归+多线程+泛型+枚举+单例+反射小记
- 布尔盲注运用burp的操作
- JS特效模板精彩案例!
- FeedBurner: 使用RSS路由器的风险
- 树莓派4B安装Ubuntu Server20.04(18.04)连接wifi(对于ubuntu server 99%适用)
- do while(0)的作用
- Keil编译警告汇总(持续更新。。。)
- 【日语】日语单词 ---- 身体部位
热门文章
- mysql5.58_mysql5.58编译安装手记
- 2021宁夏英语高考成绩查询,2021宁夏高考成绩官方查询时间及入口
- php redis命令大全,redis中key相关命令详解
- docker redis mysql_docker创建redis mysql 等服务
- 如何查看 JSP 和 Servlet 的版本
- 互联网协议套件(TCP/IP)及七层OSI模型
- maven自动化部署插件sshexec-maven-plugin
- 《Spark大数据分析:核心概念、技术及实践》一3.5 API
- iOS-UICollectionView
- 过来人经验!聊聊前端工程师的职业规划