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

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

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

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

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

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

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

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

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. 【树】Count Complete Tree Nodes

    题目: 求完全二叉树节点数. 思路: 满二叉树的节点数是2^k-1,k是树的深度. 所以我们可以先判断该树是否为满二叉树,然后是的话直接返回结果,如果不是递归地求解子树. 这样不用遍历所有的节点.复杂 ...

  2. 【链表】Reorder List

    题目: Given a singly linked list L: L0→L1→…→Ln-1→Ln,reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→… You must do ...

  3. golang prometheus包的使用

    prometheus包提供了用于实现监控代码的metric原型和用于注册metric的registry.子包(promhttp)允许通过HTTP来暴露注册的metric或将注册的metric推送到Pu ...

  4. CART树

    算法概述 CART(Classification And Regression Tree)算法是一种决策树分类方法. 它采用一种二分递归分割的技术,分割方法采用基于最小距离的基尼指数估计函数,将当前的 ...

  5. elasticsearch插件安装之--中文分词器 ik 安装

    /** * 系统环境: vm12 下的centos 7.2 * 当前安装版本: elasticsearch-2.4.0.tar.gz */ ElasticSearch中内置了许多分词器, standa ...

  6. logback 日志打印输出

    slf4j 其实是一个日志的抽象层, 其本质仍然需要真正的实现 他可以解决代码中独立于任意特定的日志类库, 可以减少很多维护日志的麻烦, 除此之外, 还有占位符的特性, {}, 类似于String#f ...

  7. XML CData 处理

    调研了 JAXB.XMLMapper(jackson) 具体方式 实现 优势 JAXB 1. 需要增加 CDATA 的Adaptor 2. 需要增加对非CDATA 的 CharacterEscapeH ...

  8. sparkthriftserver启动及调优

    Sparkthriftserver启用及优化 1.  概述 sparkthriftserver用于提供远程odbc调用,在远端执行hive sql查询.默认监听10000端口,Hiveserver2默 ...

  9. 表单提交.serialize()方法

    html中<form id="myForm" action="..." method='POST'> <div><input ty ...

  10. Linq之Distinct详解

    前天在做批量数据导入新增时,要对数据进行有效性判断,其中还要去除重复,如果没出现linq的话可能会新声明一个临时对象集合,然后遍历原始数据判断把符合条件的数据添加到临时集合中,这在有了linq之后显得 ...