由于网络上对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. 使用SharedPreference保存用户数据的步骤

    1. 声明 SharedPreferences sp; 2. 初始化 sp = this.getSharedPreferences("文件名", 0);//0代表的是私有 3. 获 ...

  2. 自然语言处理--Word2vec(二)

    前一篇,word2vec(一)主要讲了word2vec一些表层概念,以及主要介绍CBOW方法来求解词向量模型,这里主要讲论文 Distributed Representations of Words ...

  3. 机器学习--降维算法:PCA主成分分析

    引言 当面对的数据被抽象为一组向量,那么有必要研究一些向量的数学性质.而这些数学性质将成为PCA的理论基础. 理论描述 向量运算即:内积.首先,定义两个维数相同的向量的内积为: (a1,a2,⋯,an ...

  4. Nodejs学习笔记(八)—Node.js + Express 实现上传文件功能(felixge/node-formidable)

    前言 前面讲了一个构建网站的示例,这次在此基础上再说说web的常规功能----文件上传,示例以一个上传图片的功能为例子 上传功能命名用formidable实现,示例很简单! PS:最近比较忙,距上一次 ...

  5. EXCEL导出工具类及调用

    一.Excel导出工具类代码 package com.qiyuan.util; import java.io.OutputStream; import java.io.UnsupportedEncod ...

  6. [Codeforces Round#488]Div.2

    总结 这是我无聊透顶肝到三点半的一场 cf ,结果还真够无聊的 这套题涵盖了英语题,语文题,模拟题.注重考查了选手的英语素养能力,语文阅读能力和精湛的模拟和枚举能力.是不可多得的一套好题. 没什么单独 ...

  7. if 和case

    select case 'O' when 'O' then (1*5-6) when 'C' then (1*5-6) when 'EC' then (1*5-6) --else null --end ...

  8. springboot之约定大约配置

    前言 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样 ...

  9. 哪个类可用于处理 Unicode?

    A. InputStreanReader的构造函数: InputStreamReader(InputStream in)          创建一个使用默认字符集的 InputStreamReader ...

  10. javaweb开发之get与post请求的区别

    GET和POST是HTTP请求的两种基本方法,要说它们的区别,接触过WEB开发的人都能说出一二. 最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数. 你可能自己 ...