由于网络上对leveldb的分析文章都比较丰富,一些基础概念和模型都介绍得比较多,所以本人就不再对这些概念以专门的篇幅进行介绍,本文主要以代码流程注释的方式。

首先我们从db的插入和删除开始以对整个体系有一个感性的认识,首先看插入:

Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) {
WriteBatch batch; //leveldb中不管单个插入还是多个插入都是以WriteBatch的方式进行的
batch.Put(key, value);
return Write(opt, &batch);
}

Delete也类似,只是调用了WriteBatch 的 Delete(key), 这样再内部会以不同的形式编码传递至下一步进行处理。具体的WriteBatch的实现和编码方式在稍后的文章中进行介绍。Delete和Put都调用了Write,,这里的Write是在DBImpl::Write中通过虚函数的形式实现对其调用的,我们接着看Write的流程

Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
Writer w(&mutex_);
w.batch = my_batch;
w.sync = options.sync;
w.done = false;
/*

产生一个Writer对象,然后保存必要的锁、batch、和同步写的相关信息

*/
MutexLock l(&mutex_);
writers_.push_back(&w); // 上锁,然后放入待写的队列中
while (!w.done && &w != writers_.front()) {
/* 这里设计比较特别,需要跟后面的 BuildBatchGroup结合起来看,这里大致的意思是
   一直等待到这次写完成或者这次写被放在队列的最前面,BuildBatchGroup会将队列  
   里所有sync设置相同的写请求组成一个WriteBatch进行写入,这里的写请求有可能在
   别的线程完成写操作了,而是否在队列首的判断是有可能此刻没有其他线程在写循环中,
   或者本次写请求和前面的写请求的同步设置不一致,那么这种情况就需要自己进入该线
   程完成写的操作。
*/
w.cv.Wait();
}
if (w.done) {
return w.status;
} // 这个函数的主要作用是清理内存表和外存(磁盘)的表使内存表腾出空间插入新的数据
// 这里的设计比较复杂设计到leveldb 的很多核心设计,我们这里先大致了解其功能
Status status = MakeRoomForWrite(my_batch == NULL);
uint64_t last_sequence = versions_->LastSequence();
Writer* last_writer = &w;
if (status.ok() && my_batch != NULL) { // NULL batch is for compactions
WriteBatch* updates = BuildBatchGroup(&last_writer);
WriteBatchInternal::SetSequence(updates, last_sequence + );
last_sequence += WriteBatchInternal::Count(updates); {
mutex_.Unlock();BuildBatchGroup
   // 这里讲组装好的batch内容写入log,并根据同步设置判断是否同步到磁盘
status = log_->AddRecord(WriteBatchInternal::Contents(updates));
bool sync_error = false;
if (status.ok() && options.sync) {
status = logfile_->Sync();
if (!status.ok()) {
sync_error = true;
}
}
if (status.ok()) {
    // 写入内存表,这里采用了一个遍历WriteBatch完成插入的方式,稍后分析
status = WriteBatchInternal::InsertInto(updates, mem_);
}
mutex_.Lock();
if (sync_error) {
// 如果同步错误则记录相应错误信息.
RecordBackgroundError(status);
}
}
// 删除在BuildBatch里面设置的零时Batch的内容 
if (updates == tmp_batch_) tmp_batch_->Clear();

    versions_->SetLastSequence(last_sequence);
} 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;
} // Notify new head of write queue,因为可能请求时不在队首而进入了等待状态,
// 这样唤醒他使其成为新的队首写线程,进行MakeRoomForWrite等一系列操作
if (!writers_.empty()) {
writers_.front()->cv.Signal();
} return status;
}

所以从流程可以清晰的看到插入删除的流程主要为:

1. 将这条KV记录以顺序写的方式追加到log文件末尾;

2. 将这条KV记录插入内存中的Memtable中,在插入过程中如果刚好后台进程在compaction会短暂停顿以为后台进程compaction腾出时间及cpu

这里涉及到一次磁盘读写操作和内存SkipList的插入操作,但是这里的磁盘写时文件的顺序追加写入效率是很高的,所以并不会导致写入速度的降低;

而且从流程分析我们知道,在插入(删除)过程中如果多线程同时进行,那么这些操作将会将操作的同步设置相同的相邻的操作合并为一个批插入,这样可以使整个系统的总吞吐量更大。所以一次插入记录操作只会等待一次磁盘文件追加写和内存SkipList插入操作,这是为何leveldb写入速度如此高效的根本原因。

我们这里讲插入和删除以等同的方式进行了介绍,可能有的朋友会觉得奇怪,删除不是需要查找到插入的原始记录的么?而leveldb进行了一个巧妙的将随机读写,转换为顺序读写的方式,那就是其并不存在立即删除的操作,而是与插入操作相同将插入操作插入的是Key:Value值改为删除操作插入的是“Key:删除标记”,并不真正立即去删除记录,而是后台Compaction的时候才去做对应的真正的删除操作,这又极大的提高了leveldb的效率。

leveldb源码分析--插入删除流程的更多相关文章

  1. leveldb源码分析--WriteBatch

    从[leveldb源码分析--插入删除流程]和WriteBatch其名我们就很轻易的知道,这个是leveldb内部的一个批量写的结构,在leveldb为了提高插入和删除的效率,在其插入过程中都采用了批 ...

  2. leveldb源码分析--Key结构

    [注]本文参考了sparkliang的专栏的Leveldb源码分析--3并进行了一定的重组和排版 经过上一篇文章的分析我们队leveldb的插入流程有了一定的认识,而该文设计最多的又是Batch的概念 ...

  3. leveldb源码分析--SSTable之block

    在SSTable中主要存储数据的地方是data block,block_builder就是这个专门进行block的组织的地方,我们来详细看看其中的内容,其主要有Add,Finish和CurrentSi ...

  4. Leveldb源码分析--1

    coming from http://blog.csdn.net/sparkliang/article/details/8567602 [前言:看了一点oceanbase,没有意志力继续坚持下去了,暂 ...

  5. nodejs的Express框架源码分析、工作流程分析

    nodejs的Express框架源码分析.工作流程分析 1.Express的编写流程 2.Express关键api的使用及其作用分析 app.use(middleware); connect pack ...

  6. openVswitch(OVS)源码分析之工作流程(哈希桶结构体的解释)

    这篇blog是专门解决前篇openVswitch(OVS)源码分析之工作流程(哈希桶结构体的疑惑)中提到的哈希桶结构flex_array结构体成员变量含义的问题. 引用下前篇blog中分析讨论得到的f ...

  7. Okhttp源码分析--基本使用流程分析

    Okhttp源码分析--基本使用流程分析 一. 使用 同步请求 OkHttpClient okHttpClient=new OkHttpClient(); Request request=new Re ...

  8. SpringMVC源码分析-400异常处理流程及解决方法

    本文涉及SpringMVC异常处理体系源码分析,SpringMVC异常处理相关类的设计模式,实际工作中异常处理的实践. 问题场景 假设我们的SpringMVC应用中有如下控制器: 代码示例-1 @Re ...

  9. Django drf:序列化增删改查、局部与全局钩子源码流程、认证源码分析、执行流程

    一.序列化类的增.删.改.查 用drf的序列化组件   -定义一个类继承class BookSerializer(serializers.Serializer):   -写字段,如果不指定source ...

随机推荐

  1. python基础笔记之注释三种方法

    ---恢复内容开始--- 1,,单行注释  用# 2,多行注释 用 “”” dddd""" 3,较长行虽然分行写但是只是注释,最终显示为一行:用 \ ---恢复内容结束- ...

  2. 极光推送android sdk集成步骤

    推送,用极光,大家都说好,哈哈. 进入正题: 1.确认android studio的 Project 根目录的主 gradle 中配置了jcenter支持.(基本上现在都已经支持了,循例说一下)  , ...

  3. WordPress 主题教程

    创建 WordPress 主题其实不难,只要你从现在开始认真学习这个教程,从零一步一步开始,你就会成为一个 WordPress 主题制作高手,至少你会修改现有主题. 下面是一个从零开始制作 WordP ...

  4. Redis笔记(二):Redis数据类型

    Redis 数据类型 Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合). String(字符串) st ...

  5. MySQL Optimization 优化原理

    MySQL Optimization 优化原理 MySQL逻辑架构 如果能在头脑中构建一幅MySQL各组件之间如何协同工作的架构图,有助于深入理解MySQL服务器.下图展示了MySQL的逻辑架构图. ...

  6. 简单canvas刮刮乐

    好久没玩canvas了,随便写点效果玩玩.要开始重新拾起这门牛x的技术了,工作中不一定能用得上,以后说不定就能发挥用处了. <!DOCTYPE html> <html lang=&q ...

  7. i.mx6 Android5.1.1 build解析

    参考资料:理解 Android Build 系统 把总结放前面: 1. 常用编译命令 make clean 执行清理,等同于:rm -rf out/. make sdk 编译出 Android 的 S ...

  8. bzoj 4912: [Sdoi2017]天才黑客

    Description Solution 这个题和点没什么关系 , 之和边与边之间关系有关 , 我们就把边看作点 , 边权就是 \(lcp\) , 点权看作这条边本来的权值. 现在考虑两两连边 , \ ...

  9. mongodb int型id 自增

    mongo的c#客户端提供了接口IIdGenerator,有guid和objectid等几种实现,但没有int型id的实现 接口主要2个方法,一个IsEmpty返回bool,判断当前id值是否是空(估 ...

  10. SpringBoot数据库访问(一)--------关系型数据库访问(RDBMS)

    关系型数据库访问(RDBMS) 采用JdbcTemplate.MyBatis.JPA.Hibernate等技术. 一.JdbcTemplate工具 在pom.xml添加boot-starter-jdb ...