0.导读

LevelPut的流程:

Put操作首先将操作记录写入log文件,然后写入memtable,返回写成功。整体来看是这样,但是会引发下面的问题:

1. 写log的时候是实时刷到磁盘的吗?
2. 写入的时候memtable过大了咋办?
3. 同时多个线程并发写咋办?
......

下面的分析中就会面对这些问题,有些答案很清晰,有些涉及到超级多细节。

step by step

在开始之前,我们知道Write操作是要记录到log文件中的。那么一个记录它的格式是怎样的呢?看图:

这里特别解释一下,Delete操作也是通过Put实现的,只是图中的类型字段是0,而正常Write的操作类型是1,由此区分写操作和删除操作!

熟悉了LevelDB整个脉络之后, Put方法是相当简单的, 一章就可以解决. 数据删除和写入是一个概念, 删除就是写入特殊deletion marker; 批量(batch)和单条写入也是一个概念, 单条写入就是只有一条记录的batch. 整个流程很短, 基本上写个log就好了, 因此速度很快.

step 1

Put interface

很简单吧。这里讲解一下那三个参数:

  1. WriteOptions:提供一些写操作的配置项,例如要不要写log的时候马上flush磁盘
  2. key和value就是对应的keyValue,slice只是作者自己封装的char数组存储数据而已。<b>大牛喜欢把所有东西都封装一下,赋予数据结构意义!</b>这在大的工程里面是很有意义的,既方便操作,也方便思考(这样就不用思考底层的真实的char数组还是啥)。

Put对于多线程的处理非常精妙, 主体在DBImpl::Write函数中.

插入:

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

一条记录包含如下内容: 
Type、Key、Value 
当要插入记录时,Type为kTypeValue,当要删除记录时,Type为kTypeDeletion,同时中每一个batch都有一个对当前批处理记录信息的统计(sequence(8字节)和count(4字节),共12字节) 
由此可见,当我们要删除一个数据时,并不是直接从内存中删除,而是插入一条带有删除标志的记录

在本例中要插入数据:key=”lili”; value=”hihi”; 
由之前对WriterBatch的分析可知,得到的batch为: 
01 00 00 00 00 00 00 00 01 00 00 00 (前8字节表示是第一个batch,后4字节表示此batch中只有一条记录) 
01(kTypeValue) 04(Key.size) 6C 69 6C 69(lili) 04(value.size) 68 69 68 69(hihi) 
共12+1+1+4+1+4=23字节=0x17

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

 1 Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
 2   // A begin
 3   Writer w(&mutex_);
 4   w.batch = my_batch;
 5   w.sync = options.sync;
 6   w.done = false;
 7   // A end
 8
 9   // B begin/* mutex l上锁之后, 到了"w.cv.Wait()"的时候, 会先释放锁等待, 然后收到signal时再次上锁. 这段代码的作用就是多线程在提交任务的时候, 一个接一个push_back进队列. 但只有位于队首的线程有资格继续运行下去. 目的是把多个写请求合并成一个大batch提升效率.*/
10   MutexLock l(&mutex_);
11   writers_.push_back(&w);
12   while (!w.done && &w != writers_.front()) {
13     w.cv.Wait();
14   }
15   if (w.done) {
16     return w.status;
17   }
18   // B end
19
20   // May temporarily unlock and wait.
21   Status status = MakeRoomForWrite(my_batch == NULL);
22   uint64_t last_sequence = versions_->LastSequence();
23   Writer* last_writer = &w;
24   if (status.ok() && my_batch != NULL) {  // NULL batch is for compactions
25     WriteBatch* updates = BuildBatchGroup(&last_writer);
26     WriteBatchInternal::SetSequence(updates, last_sequence + 1);
27     last_sequence += WriteBatchInternal::Count(updates);
28
29     // Add to log and apply to memtable.  We can release the lock
30     // during this phase since &w is currently responsible for logging
31     // and protects against concurrent loggers and concurrent writes
32     // into mem_.
33     {
34       mutex_.Unlock();
35       status = log_->AddRecord(WriteBatchInternal::Contents(updates));
36       bool sync_error = false;
37       if (status.ok() && options.sync) {
38         status = logfile_->Sync();
39         if (!status.ok()) {
40           sync_error = true;
41         }
42       }
43       if (status.ok()) {
44         status = WriteBatchInternal::InsertInto(updates, mem_);
45       }
46       mutex_.Lock();
47       if (sync_error) {
48         // The state of the log file is indeterminate: the log record we
49         // just added may or may not show up when the DB is re-opened.
50         // So we force the DB into a mode where all future writes fail.
51         RecordBackgroundError(status);
52       }
53     }
54     if (updates == tmp_batch_) tmp_batch_->Clear();
55
56     versions_->SetLastSequence(last_sequence);
57   }
58
59   while (true) {
60     Writer* ready = writers_.front();
61     writers_.pop_front();
62     if (ready != &w) {
63       ready->status = status;
64       ready->done = true;
65       ready->cv.Signal();
66     }
67     if (ready == last_writer) break;
68   }
69
70   // Notify new head of write queue
71   if (!writers_.empty()) {
72     writers_.front()->cv.Signal();
73   }
74
75   return status;
76 }

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

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

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

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

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

  假设同时有w1, w2, w3, w4, w5, w6 并发请求写入。

  B部分代码让竞争到mutex资源的w1获取了锁。w1将它要写的数据添加到了writers_队列里去,此时队列只有一个w1, 从而其顺利的进行buildbatchgroup。当运行到34行时mutex_互斥锁释放,之所以这儿可以释放mutex_,是因为其它的写操作都不满足队首条件,进而不会进入log和memtable写入阶段。这时(w2, w3, w4, w5, w6)会竞争锁,由于B段代码中不满足队首条件,均等待并释放锁了。从而队列可能会如(w3, w5, w2, w4).

  继而w1进行log写入和memtable写入。 当w1完成log和memtable写入后,进入46行代码,则mutex_又锁住,这时B段代码中队列因为获取不到锁则队列不会修改。

  随后59行开始,w1被pop出来,由于ready==w, 并且ready==last_writer,所以直接到71行代码,唤醒了此时处于队首的w3.

w3唤醒时,发现自己是队首,可以顺利的进行进入buildbatchgroup,在该函数中,遍历了目前所有的队列元素,形成一个update的batch,即将w3, w5, w2, w4合并为一个batch. 并将last_writer置为此时处于队尾的最后一个元素w4,34行代码运行后,因为释放了锁资源,队列可能随着dbimpl::write的调用而更改,如队列状况可能为(w3, w5, w2, w4, w6, w9, w8).

  35-45行的代码将w3, w5, w2, w4整个的batch写入log和memtable. 到65行,分别对w5, w2, w4进行了一次cond signal.当判断到完w4 == lastwriter时,则退出循环。72行则对队首的w6唤醒,从而按上述步骤依次进行下去。

  这样就形成了多个并发write 合并为一个batch写入log和memtable的机制。

转载于:https://www.cnblogs.com/ym65536/p/7720105.html

[leveldb] 3.put/delete操作相关推荐

  1. 我艹,MySQL数据量大时,delete操作无法命中索引。

    来自:Java面试那些事儿 最近,在脉脉上看到一个楼主提出的问题:MySQL数据量大时,delete操作无法命中索引:并且还附上了相关案例截图. 最终,楼主通过开启MySQL分析优化器追踪,定位到是优 ...

  2. SAP CRM IBASE在ABAP update task中实现update和delete操作

    本文介绍SAP CRM IBASE在ABAP update task中实现update和delete操作的原理. 要获取更多Jerry的原创文章,请关注公众号"汪子熙":

  3. update和delete操作忘加where条件导致全表更新的处理方法

    在数据库日常维护中,开发人员是最让人头痛的,很多时候都会由于SQL语句写的有问题导致服务器出问题,导致资源耗尽.最危险的操作就是在做DML操作的时候忘加where条件,导致全表更新,这是作为运维或者D ...

  4. Mysql定义DELETE操作触发器,将删除数据存入历史表

    Mysql定义DELETE操作触发器,将删除数据存入历史表 SQL如下: // An highlighted blockDELIMITER $$ CREATE TRIGGER <触发器名称> ...

  5. 只做了delete操作,为啥 ORACLE-01466表定义已更改

    create table 新建了一个表之后,想测试删掉的数据能不能找回,就DELETE 掉了数据, 然后: --  按特定时间点恢复,例如:误操作 delete from retable_name ; ...

  6. openlayers中使用rBush(R树)来存放要素等信息,本文修改了一点其中的rbush源码中的demo,使用canvas画出了insert和delete操作(建立树和删除树中数据)

    openlayers中使用rBush(R树)来存放要素等信息,本文修改了一点其中的rbush源码中的demo,使用canvas画出了insert和delete操作(建立树和删除树中数据) 修改后的源代 ...

  7. 为什么mysql的delete操作不释放磁盘空间

    在 InnoDB 中,delete 操作并不会真的删除数据,mysql 实际上只是给要删除的数据打了标记,标记为删除.磁盘所占空间不会变小,即表空间并没有真正被释放. 一. MySQL 删除数据几种情 ...

  8. 为什么 MySQL 执行完 Delete 操作之后,空间没有释放?

    为什么 MySQL 执行完 Delete 操作之后,空间没有释放? 文章目录 为什么 MySQL 执行完 Delete 操作之后,空间没有释放? Mysql数据结构 表文件大小未更改和mysql设计有 ...

  9. MySQL中DELETE操作磁盘空间不会减少的原因

    MySQL中delete操作 在InnoDB中,delete操作并不会真的删除数据,mysql实际上只是给要删除的数据打了标记,标记为删除.磁盘所占空间不会变小,即表空间并没有真正被释放. 这样设计的 ...

最新文章

  1. Camera Lens Coating
  2. 关于target=标签
  3. Python-OpenCV 处理视频(一): 输入输出
  4. python pyqt eric_Python3.6 + Pyqt5 + Eric6 环境搭建
  5. intellij存放插件的路径(转载)
  6. 技术团队的工程师文化:效率与价值
  7. Multi-thread提高C++性能的编程技术笔记:单线程内存池+测试代码
  8. 如何在报表的Header和Footer中使用DataSet中的Field
  9. 注解之RetentionPolicy,ElementType
  10. julia的几种画图方法
  11. AMD HD7850 4G显卡刷Bios验真伪
  12. Webpack5学习笔记(基础篇七)—— Loader加载器
  13. onlyoffice+vue实现在线预览在线编辑
  14. freyja 将引入另外一项功能大幅提高服务器性能
  15. Python爬虫实践:优志愿 院校列表
  16. Vue3 第二十二篇:双向绑定样式style
  17. 学科实践活动感悟50字_学科实践活动写实记录50字范文
  18. 《灵飞经5·龙生九子》第二十三章 力压须眉(上)
  19. 《Springboot极简教程》继承WebMvcConfigurerAdapter: 一行代码写Controller
  20. Amber小分子-蛋白复合体分子动力学模拟

热门文章

  1. java的character用法_Java中Character类的使用方法
  2. linux下samba病毒,Samba 用户模拟漏洞(CVE-2016-2125)
  3. 炼油机出来的什么油_小型精炼油设备10来万一套供不应求,油作坊提高油品质量才有出路...
  4. python中multiple函数_关于多处理:在Python中将多个参数传递给pool.map()函数
  5. Python 面向对象编程基础
  6. 动态规划之硬币表示问题
  7. 从工作实践中积累Linux常用脚本(一)
  8. 为什么要将服务或者数据部署多份?
  9. Online Learning算法理论与实践
  10. [VSTO系列]三、简单的UI设计/QQ联系人导出(下)