LevelDB的put操作

leveldb源码分析2

在了解了Open操作后,接下来分析Put操作。

DB::Put(db/db_impl.cc1487行)

// Default implementations of convenience methods that subclasses of DB
// can call if they wish
Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) {
  WriteBatch batch;
  batch.Put(key, value);
  return Write(opt, &batch);
}
  • WriteBatch(db/write_batch.cc):对若干数目的key的write操作(put/delete)封装成WriteBatch。它会将userkey连同SequenceNumber(db/dbformat.h,leveldb中的每次更新put/delete操作都拥有一个版本,由SequenceNumber标识,真个db有一个全局值保存着当前用到的SequenceNumber)和ValueType先做encode,然后做decode,将数据insert到指定的Handler(memtable)上面。

WriteBatch::Put

接着上面的batch.Put(key, value);,跳转到write_batch.cc102行:

void WriteBatch::Put(const Slice& key, const Slice& value) {
  WriteBatchInternal::SetCount(this, WriteBatchInternal::Count(this) + 1);
  rep_.push_back(static_cast<char>(kTypeValue));
  PutLengthPrefixedSlice(&rep_, key);
  PutLengthPrefixedSlice(&rep_, value);
}

这个函数主要就是将key-value封装WriteBatch

DBImpl::Write(db/db_impl.cc1204行)

由于代码太长就不贴上来了,简单描述一下流程: 1. 循环检查当前 db 状态,确定策略(DBImpl:: MakeRoomForWrite()): + 如果当前level-0中的文件数目达到kL0_SlowdownWritesTrigger阈值,则sleep进行 delay。该delay只会发生一次。 + 如果当前memtablesize未达到阈值write_buffer_size,则允许这次写。 + 如果memtable已经达到阈值,但immutable memtable仍存在,则等待compact将其dump完成。 + 如果 level-0 中的文件数目达到 kL0_StopWritesTrigger 阈值,则等待compact memtable完成 + 上述条件都不满足,则是 memtable 已经写满,并且 immutable memtable 不存在,则将当前 memtable 置为 immutable memtable,生成新的 memtable 和 log file,主动触发 compact,允许该次写。 2. 设置WriteBatchSequnceNumber。 3. 先将WriteBatch中的数据记 log(Log::AddRecord())。 4. 将WriteBatch应用在memtable上。(WriteBatchInternal::InsertInto()) ,即遍历decode出 WriteBatch中的key/value/ValueType,根据ValueTypememetable进行put/delete操作。 5. 更新SequnceNumber(last_sequnce + WriteBatch::count()`)。

总结

完成Put操作包括两个步骤:首先是将这条KV记录以顺序写的方式追加到log文件末尾,尽管是一个磁盘读写操作, 但是文件的顺序追加写入效是很高的,因此不会导致写入速度降低;第二个步骤是如果写入log文件成功,那么将这条KV记录 插入内存的memtable中。