LevelDB 从源码层次上看Compaction过程

Compaction过程

在LevelDB中,Compaction主要由两种:

  • minor compaction:这个就是immutable持久化到Level 0层的过程。这里最重要的要求就是高性能,因为其一旦阻塞就会导致memtable无法写入但是有没有办法转换成immutable
  • major compaction:负责将磁盘上的SSTable和并,每合并一次,sstable中的数据就落到更底一层,数据慢慢被合并到底层的Level

而通过Compaction可以达到以下的几个效果:

  1. 内存中的数据持久化到磁盘
  2. 清理荣誉数据
  3. 通过compaction使Level 0层以下的文件层中的数据保持有序,这样便可以通过二分进行数据查找,同时也可以减少待查找的文件数量,提升读效率

minor compaction

刚刚也稍微介绍了以下minor compaction,为了提升数据持久化的速度,在对immutable进行持久化时不会考虑不同文件之间的重复和顺序问题,这也使得这个过程变得稍微简单了一些。

1)触发minor compaction的时间

当内存中的memtable size小于配置的阈值时,数据都会直接更新到memtable。超过大小后,memtable会转换成 Immutable,这时会由一个后台线程负责将immutable持久化到磁盘成文 Level 0的SSTable文件。

这个过程在之前介绍的DBImpl::MakeRoomForWrite得到实现:

Status DBImpl::MakeRoomForWrite(bool force) {
 	  ...
 	  //memtable 转换成immutable
      imm_ = mem_;
      has_imm_.store(true, std::memory_order_release);

      //申请新的memtable
      mem_ = new MemTable(internal_comparator_);
      mem_->Ref();
      force = false;  // Do not force another compaction if have room
      //触发合并操作
      MaybeScheduleCompaction();
    }
  }
  return s;
}

这里我们可以看到调用了函数 DBImpl::MaybeScheduleCompaction,这个函数会把DBImpl::BGWork加入后台线程的执行日程之中。而这个DBImpl::BGWork则会调用DBImpl::BackgroundCall,而这个DBImpl::BackgroundCall则会调用函数DBImpl::BackgroundCompaction,在这个函数里,才会真正的调用合并函数DBImpl::CompactMemTable

2)将immutable memtable落盘成SSTable

DBImpl::CompactMemTable会调用函数WriteLevel0Table,在这函数里会落盘immutable

void DBImpl::CompactMemTable() {
  mutex_.AssertHeld();
  assert(imm_ != nullptr);

  // Save the contents of the memtable as a new Table
  VersionEdit edit;
  Version* base = versions_->current();
  base->Ref();
  Status s = WriteLevel0Table(imm_, &edit, base);
  base->Unref();

  if (s.ok() && shutting_down_.load(std::memory_order_acquire)) {
    s = Status::IOError("Deleting DB during memtable compaction");
  }

  // Replace immutable memtable with the generated Table
  if (s.ok()) {
    edit.SetPrevLogNumber(0);
    edit.SetLogNumber(logfile_number_);  // Earlier logs no longer needed
    //记录edit信息
    s = versions_->LogAndApply(&edit, &mutex_);
  }

  if (s.ok()) {
    // Commit to the new state
    //释放imm_空间
    imm_->Unref();
    imm_ = nullptr;
    has_imm_.store(false, std::memory_order_release);
    //清理无效文件
    RemoveObsoleteFiles();
  } else {
    RecordBackgroundError(s);
  }
}

WriteLevel0Table在落盘immutable的同时会将文件信息记录到edit之中,edit里面保存了文件的元信息。这里要提一点的是,假如说新生成的SSTable文件实际上并不总是放到Level 0 层,如果这个新生成的SSTable的key与当前Level 1层的所有文件都没有重叠,则会直接将文件放到Level 1层

Status DBImpl::WriteLevel0Table(MemTable* mem, VersionEdit* edit,
                                Version* base) {
  mutex_.AssertHeld();
  const uint64_t start_micros = env_->NowMicros();

  //生成sstable编号,用于构建文件名
  FileMetaData meta;
  meta.number = versions_->NewFileNumber();
  pending_outputs_.insert(meta.number);
  Iterator* iter = mem->NewIterator();
  Log(options_.info_log, "Level-0 table #%llu: started",
      (unsigned long long)meta.number);

  Status s;
  {
    mutex_.Unlock();
    //更新memtable中的所有数据到xxx.ldb文件追踪
    //meta 记录key的范围,file_size等元信息
    s = BuildTable(dbname_, env_, options_, table_cache_, iter, &meta);
    mutex_.Lock();
  }

  Log(options_.info_log, "Level-0 table #%llu: %lld bytes %s",
      (unsigned long long)meta.number, (unsigned long long)meta.file_size,
      s.ToString().c_str());
  delete iter;
  pending_outputs_.erase(meta.number);

  // Note that if file_size is zero, the file has been deleted and
  // should not be added to the manifest.
  int level = 0;
  if (s.ok() && meta.file_size > 0) {
    const Slice min_user_key = meta.smallest.user_key();
    const Slice max_user_key = meta.largest.user_key();
    if (base != nullptr) {
      //为新生成的sstable选择合适的level,其实这里就是比较最大key和最小key的过程
      level = base->PickLevelForMemTableOutput(min_user_key, max_user_key);
    }
    //level及file meta记录到edit,实际上就是记录到一个vector<[int]level,[FileMetaData]file>
    edit->AddFile(level, meta.number, meta.file_size, meta.smallest,
                  meta.largest);
  }

  CompactionStats stats;
  stats.micros = env_->NowMicros() - start_micros;
  stats.bytes_written = meta.file_size;
  stats_[level].Add(stats);
  return s;
}

3)将edit信息记录到version

WriteLevel0Table执行完成之后,会将新生成的edit信息记录到version(version是整个LevelDB的元信息),当前的version作为数据库的一个最新状态,后续的操作都会基于该状态。

void DBImpl::CompactMemTable() {
  ...
  // Replace immutable memtable with the generated Table
  if (s.ok()) {
    edit.SetPrevLogNumber(0);
    edit.SetLogNumber(logfile_number_);  // Earlier logs no longer needed
    //记录edit信息
    s = versions_->LogAndApply(&edit, &mutex_);
  }

  if (s.ok()) {
    // Commit to the new state
    //释放imm_空间
    imm_->Unref();
    imm_ = nullptr;
    has_imm_.store(false, std::memory_order_release);
    //清理无效文件
    RemoveObsoleteFiles();
  } else {
    RecordBackgroundError(s);
  }
}

major compaction

而Major Compaction主要有三种分别为:

  1. Manual Compaction:是人工触发的Compaction,由外部接口调用产生,例如在ceph调用的Compaction都是Manual Compaction,实际其内部触发调用的接口DBImpl::CompactRange

  2. Size Compaction:是根据每个level的总文件大小来触发,注意Size Compation的优先级高于Seek Compaction。

  3. Seek Compaction:每个文件的 seek miss 次数都有一个阈值,如果超过了这个阈值,那么认为这个文件需要Compact。

其中这些 Compaction 的优先级不一样(详细可以参见 BackgroundCompaction 函数),具体优先级的大小为:

Minor > Manual > Size > Seek

LevelDB 是在 MayBeScheduleCompaction 的 Compation 调度函数中完成各种 Compaction 的调度的,第一个判断的就是 immu_ (也就是 immutable memtable)是不是为 NULL,如果不为 NULL,那么说明有 immutable memtable 存在,那就需要优先将其转化为 level 0 的 sst 文件,否则再看是不是 Manual,否则再是PickCompaction() 函数——它的内部会优先判断是不是有 Size Compaction,如果有就优先处理。

major compation的设计带来一个明显的好处就是可以清理冗余数据,节省磁盘空间,因为之前被标记删除的数据可以在major compaction的过程中被清理掉。

Level 0中的数据文件之间是无序的,但是被归并到Level 1之后,数据变得有序,这使读操作需要查询的文件数就会变少。因此,major compaction带来的另外一个好处就是可以提升读效率。

1)触发major compaction的时机

  • Level 0:SSTable文件个数超过指定个数
  • Level i:第i层的sstable size总大小超过10iMB。level越大,说明数据越冷,读取的几率越小。
  • 对于sstable文件还有seek限制,如果文件多次seek但是一直没有查找到数据,那么就应该被合并了。

上面的这个逻辑在函数VersionSet::Finalize得到体现,

void VersionSet::Finalize(Version* v) {
  // Precomputed best level for next compaction
  int best_level = -1;
  double best_score = -1;

  for (int level = 0; level < config::kNumLevels - 1; level++) {
    double score;
    if (level == 0) {
      //如果是第0层,就直接判断第0层文件是否大于4个,kL0_CompactionTrigger=4
      score = v->files_[level].size() /
              static_cast<double>(config::kL0_CompactionTrigger);
    } else {
      // TotalFileSize得到这一层的总文件大小
      const uint64_t level_bytes = TotalFileSize(v->files_[level]);
      // 其他层就计算这一层的文件占用空间是否大于限制
      score =
          static_cast<double>(level_bytes) / MaxBytesForLevel(options_, level);
    }

    if (score > best_score) {
      best_level = level;
      best_score = score;
    }
  }
  //这里记录当前层数和score,score大于1就表示要合并
  v->compaction_level_ = best_level;
  v->compaction_score_ = best_score;
}

2) compaction流程

  • 选择合适的level及sstable用于合并,这个过程发生在 VersionSet::PickCompaction

筛选文件会根据size_compaction规则,或者seek_compaction规则计算应该合并的文件。

首先就是根据刚刚计算得到的score,如果这个score大于1就表示要合并了。我们把size_compaction置为true。之后,就是把文件元信息指针FileMetaData放入inputs[0]之中了。

  const bool size_compaction = (current_->compaction_score_ >= 1);
  const bool seek_compaction = (current_->file_to_compact_ != nullptr);
  if (size_compaction) {
    level = current_->compaction_level_;
    assert(level >= 0);
    assert(level + 1 < config::kNumLevels);
    c = new Compaction(options_, level);

    // Pick the first file that comes after compact_pointer_[level]
    for (size_t i = 0; i < current_->files_[level].size(); i++) {
      FileMetaData* f = current_->files_[level][i];
      if (compact_pointer_[level].empty() ||
          icmp_.Compare(f->largest.Encode(), compact_pointer_[level]) > 0) {
        c->inputs_[0].push_back(f);
        break;
      }
    }
    if (c->inputs_[0].empty()) {
      // Wrap-around to the beginning of the key space
      c->inputs_[0].push_back(current_->files_[level][0]);
    }
  } else if (seek_compaction) {
    level = current_->file_to_compact_level_;
    c = new Compaction(options_, level);
    c->inputs_[0].push_back(current_->file_to_compact_);
  } else {
    return nullptr;
  }

对于seek_compaction,则是会为每个新的SSTable文件维护一个allowed_seek的初始阈值,表示最多容忍多少次seek miss,当allowed_seeks递减到小于0的时候,就会将对应文件标记为需要compact

bool Version::UpdateStats(const GetStats& stats) {
  FileMetaData* f = stats.seek_file;
  if (f != nullptr) {
    f->allowed_seeks--;
    if (f->allowed_seeks <= 0 && file_to_compact_ == nullptr) {
      file_to_compact_ = f;
      file_to_compact_level_ = stats.seek_file_level;
      return true;
    }
  }
  return false;
}
  • 根据key重叠的情况扩大输入文件集合

根据 key 重叠情况扩大输入文件集合的基本思想是:所有有重叠的 level+1 层文件都要参与 compact,得到这些文件后,反过来看下,在不增加 level+1 层文件的前提下,能否继续增加 level 层的文件。具体步骤如下:
在这里插入图片描述
这个过程发生在函数Version::GetOverlappingInputs以及函数VersionSet::SetupOtherInputs之中。其中Version::GetOverlappingInputs负责扩大Level 0层的范围,VersionSet::SetupOtherInputs扩大其他层的范围。函数均在VersionSet::PickCompaction被调用:

void Version::GetOverlappingInputs(int level, const InternalKey* begin,
                                   const InternalKey* end,
                                   std::vector<FileMetaData*>* inputs) {
  assert(level >= 0);
  assert(level < config::kNumLevels);
  inputs->clear();
  Slice user_begin, user_end;
  if (begin != nullptr) {
    user_begin = begin->user_key();
  }
  if (end != nullptr) {
    user_end = end->user_key();
  }
  const Comparator* user_cmp = vset_->icmp_.user_comparator();
  for (size_t i = 0; i < files_[level].size();) {
    FileMetaData* f = files_[level][i++];
    const Slice file_start = f->smallest.user_key();
    const Slice file_limit = f->largest.user_key();
    if (begin != nullptr && user_cmp->Compare(file_limit, user_begin) < 0) {
      // "f" is completely before specified range; skip it
    } else if (end != nullptr && user_cmp->Compare(file_start, user_end) > 0) {
      // "f" is completely after specified range; skip it
    } else {
      inputs->push_back(f);
      if (level == 0) {
        // Level-0 files may overlap each other.  So check if the newly
        // added file has expanded the range.  If so, restart search.
        if (begin != nullptr && user_cmp->Compare(file_start, user_begin) < 0) {
          user_begin = file_start;
          inputs->clear();
          i = 0;
        } else if (end != nullptr &&
                   user_cmp->Compare(file_limit, user_end) > 0) {
          user_end = file_limit;
          inputs->clear();
          i = 0;
        }
      }
    }
  }
}

level与level+1层归并压缩之后,最后的文件是要放到level+1层的,在方法SetupOtherInputs中获取到压缩之后的key范围[all_start,all_limit],并查询出level+2层与level+1层overlap的SSTable,存放与grandparents_中。

  • 多路合并

多路合并会将上一步骤选出来的待合并 sstable 中的数据按序整理。在按序整理之后,就会进入函数DBImpl::DoCompactionWork(调用点在DBImpl::BackgroundCompaction之中)。这个函数会调用 VersionSet::MakeInputIterator 函数返回了一个迭代器对象,通过遍历该迭代器对象,则可以得到全部有序的 key 集合。

Iterator* VersionSet::MakeInputIterator(Compaction* c) {
  ReadOptions options;
  options.verify_checksums = options_->paranoid_checks;
  options.fill_cache = false;

  // Level-0 files have to be merged together.  For other levels,
  // we will make a concatenating iterator per level.
  // TODO(opt): use concatenating iterator for level-0 if there is no overlap
  const int space = (c->level() == 0 ? c->inputs_[0].size() + 1 : 2);
  //list 存储所有Iterator
  Iterator** list = new Iterator*[space];
  int num = 0;
  for (int which = 0; which < 2; which++) {
    if (!c->inputs_[which].empty()) {
      //第0层
      if (c->level() + which == 0) {
        const std::vector<FileMetaData*>& files = c->inputs_[which];
        for (size_t i = 0; i < files.size(); i++) {
          list[num++] = table_cache_->NewIterator(options, files[i]->number,
                                                  files[i]->file_size);
        }
      } else {
        // Create concatenating iterator for the files from this level
        list[num++] = NewTwoLevelIterator(
            new Version::LevelFileNumIterator(icmp_, &c->inputs_[which]),
            &GetFileIterator, table_cache_, options);
      }
    }
  }
  assert(num <= space);
  Iterator* result = NewMergingIterator(&icmp_, list, num);
  delete[] list;
  return result;
}

下面的流程就是遍历所有需要待Compact的SSTable文件中的key,有效的key就写入到新的SSTable文件中,无效的key就丢弃。写完之后删除掉无用的SSTable。详细的说明已在代码中注释。

//Compact工作。
//1.将待Compact的input[0]、input[1]文件创建为迭代器访问
//2.Seek到迭代器的最小key,并开始循环访问这些key并进行Compact。
//3.在循环的过程中,如果有immutable,那就优先处理进行下Compact。
//4.判断下当前准备Compact的文件对应的key是否需要提前停止Compact,并将当前的文件落地为SSTable。
Status DBImpl::DoCompactionWork(CompactionState* compact) {
  const uint64_t start_micros = env_->NowMicros();
  int64_t imm_micros = 0;  // Micros spent doing imm_ compactions

  Log(options_.info_log, "Compacting %d@%d + %d@%d files",
      compact->compaction->num_input_files(0), compact->compaction->level(),
      compact->compaction->num_input_files(1),
      compact->compaction->level() + 1);

  assert(versions_->NumLevelFiles(compact->compaction->level()) > 0);
  assert(compact->builder == nullptr);
  assert(compact->outfile == nullptr);
  if (snapshots_.empty()) {
    compact->smallest_snapshot = versions_->LastSequence();
  } else {

	//如果某个快照被外部使用(GetSnapShot),这个快照对应的SnapShotImpl对象会被放
	//在SnapshotList中(也就是sequence_number被保存下来了),Compaction的时候
    //遇到可以清理的数据,还需要判断要清理数据的seq_number不能大于这些快照中的
	//sequence_number,否则会影响夸张数据。
    compact->smallest_snapshot = snapshots_.oldest()->sequence_number();
  }

  Iterator* input = versions_->MakeInputIterator(compact->compaction);

  // Release mutex while we're actually doing the compaction work
  mutex_.Unlock();

  //这里input迭代器的循环遍历是每次取迭代器中最小的key,
  //key是指InternalKey,key比较器是InternalKeyComparator,
  //InternalKey比较方式是对InternalKey中的User_Key按BytewiseComparator来比较。
  //在User_Key相同的情况下,按SequenceNumber来比较,SequenceNumber值大的是小于SequenceNumber值小的,
  //所以针对abc_456_1、abc_123_2,abc_456_1是小于abc_123_2的。针对我们对同一个user_key的操作,
  //最新对此key的操作是小于之前对此key的操作的。
  input->SeekToFirst();
  Status status;
  ParsedInternalKey ikey;
  std::string current_user_key;
  bool has_current_user_key = false;
  SequenceNumber last_sequence_for_key = kMaxSequenceNumber;
  while (input->Valid() && !shutting_down_.load(std::memory_order_acquire)) {

    // Prioritize immutable compaction work
    //检查并优先compact存在的immutable memtable。
    if (has_imm_.load(std::memory_order_relaxed)) {
      const uint64_t imm_start = env_->NowMicros();
      mutex_.Lock();
      if (imm_ != nullptr) {
        CompactMemTable();
        // Wake up MakeRoomForWrite() if necessary.
        background_work_finished_signal_.SignalAll();
      }
      mutex_.Unlock();
      imm_micros += (env_->NowMicros() - imm_start);
    }

    Slice key = input->key();

	//如果当前InternalKey与grandparent层产生overlap的size超过阈值,
	//那就停止当前SSTable检查,直接落地并停止遍历。
    if (compact->compaction->ShouldStopBefore(key) &&
        compact->builder != nullptr) {
      status = FinishCompactionOutputFile(compact, input);
      if (!status.ok()) {
        break;
      }
    }

    // Handle key/value, add to state, etc.
	//确定当前key是否要丢弃
    bool drop = false;
    if (!ParseInternalKey(key, &ikey)) {
	  //解析key失败。
      //针对解析失败的key,这里不丢弃,直接存储。
	  //目的就是不隐藏这种错误,存储到SSTable中,
	  //便于后续逻辑去处理。
      // Do not hide error keys
      current_user_key.clear();
      has_current_user_key = false;
      last_sequence_for_key = kMaxSequenceNumber;
    } else {
	 //解析InternalKey成功。
	 
      if (!has_current_user_key ||
          user_comparator()->Compare(ikey.user_key, Slice(current_user_key)) !=0) {
		  //前后检测的两个InternalKey不一样,
		  //那就记录这个首次出现的key,
		  //并将last_sequence_for_key设置为最大。
        // First occurrence of this user key
        current_user_key.assign(ikey.user_key.data(), ikey.user_key.size());
        has_current_user_key = true;
        last_sequence_for_key = kMaxSequenceNumber;
      }

	  //如果前后两个key不一样,last_sequence_for_key会被赋值为kMaxSequenceNumber,
      if (last_sequence_for_key <= compact->smallest_snapshot) {

		//进入此逻辑,说明last_sequence_for_key不是最大值kMaxSequenceNumber,
		//也就是当前这个key的user_key(是一个比较旧的userkey)和上一个key的user_key是相同的。
		//所以这里就直接丢弃。
        // Hidden by an newer entry for same user key
        drop = true;  // (A)
      } else if (ikey.type == kTypeDeletion &&
                 ikey.sequence <= compact->smallest_snapshot &&
                 compact->compaction->IsBaseLevelForKey(ikey.user_key)) {
		//如果这个InternalKey满足一下三个条件,则可以直接丢弃。
		//1.是个Deletionkey。
		//2.sequence <= small_snaphshot。
		//3.当前compact的level是level-n和level-n+1,
		//  如果在level-n+1以上的层已经没有此InternalKey对应的user_key了。
		//基于以上三种情况可删除。
		//为什么要此条件(IsBaseLevelForKey)判断呢?
		//举个例子:
		//如果在更高层,还有此InternalKey对应的User_key,
		//此时你把当前这个InternalKey删除了,那就会出现两个问题:
		//问题1:再次读取删除的key时,就会读取到老的过期的key(这个key的type是非deletion),这是有问题的。
		//问题2:再次合并时,但这个key(这个key的type是非deletion)首次被读取时last_sequence_for_key会设置为kMaxSequenceNumber,
		//      这样就也不会丢弃。
		//以上两个问题好像在更高层的也就是旧的此key的所有userkey的type都是是delete的时候好像是没问题的,
		//但这毕竟是少数,原则上为了系统正常运行,我们每次丢弃一个标记为kTypeDeletion的key时,
	    //必须保证数据库中不存在它的过期key,否则就得将它保留,直到后面它和这个过期的key合并为止,合并之后再丢弃

        // For this user key:
        // (1) there is no data in higher levels
        // (2) data in lower levels will have larger sequence numbers
        // (3) data in layers that are being compacted here and have
        //     smaller sequence numbers will be dropped in the next
        //     few iterations of this loop (by rule (A) above).
        // Therefore this deletion marker is obsolete and can be dropped.
        drop = true;
      }

      last_sequence_for_key = ikey.sequence;
    }
#if 0
    Log(options_.info_log,
        "  Compact: %s, seq %d, type: %d %d, drop: %d, is_base: %d, "
        "%d smallest_snapshot: %d",
        ikey.user_key.ToString().c_str(),
        (int)ikey.sequence, ikey.type, kTypeValue, drop,
        compact->compaction->IsBaseLevelForKey(ikey.user_key),
        (int)last_sequence_for_key, (int)compact->smallest_snapshot);
#endif

	//写入不丢弃的key
    if (!drop) {

	  //1.打开SSTable文件
      // Open output file if necessary
      if (compact->builder == nullptr) {
        status = OpenCompactionOutputFile(compact);
        if (!status.ok()) {
          break;
        }
      }

	  //对于要写入的key,从input_取出时,
	  //应该是当前整个input_中最小的key(此处应该就体现了迭代器封装的好处了)。
	  //也就是说写入到SSTable是升序的。
      if (compact->builder->NumEntries() == 0) {
        compact->current_output()->smallest.DecodeFrom(key);
      }
      compact->current_output()->largest.DecodeFrom(key);
      compact->builder->Add(key, input->value());

	  //待生成的SSTable已超过特定阈值,那么就将此SSTable文件落地。
      // Close output file if it is big enough
      if (compact->builder->FileSize() >=
          compact->compaction->MaxOutputFileSize()) {
        status = FinishCompactionOutputFile(compact, input);
        if (!status.ok()) {
          break;
        }
      }
    }

    input->Next();
  }

  if (status.ok() && shutting_down_.load(std::memory_order_acquire)) {
    status = Status::IOError("Deleting DB during compaction");
  }
  if (status.ok() && compact->builder != nullptr) {
    status = FinishCompactionOutputFile(compact, input);
  }
  if (status.ok()) {
    status = input->status();
  }
  delete input;
  input = nullptr;

  CompactionStats stats;
  stats.micros = env_->NowMicros() - start_micros - imm_micros;
  for (int which = 0; which < 2; which++) {
    for (int i = 0; i < compact->compaction->num_input_files(which); i++) {
      stats.bytes_read += compact->compaction->input(which, i)->file_size;
    }
  }
  for (size_t i = 0; i < compact->outputs.size(); i++) {
    stats.bytes_written += compact->outputs[i].file_size;
  }

  mutex_.Lock();
  stats_[compact->compaction->level() + 1].Add(stats);

  if (status.ok()) {
    status = InstallCompactionResults(compact);
  }
  if (!status.ok()) {
    RecordBackgroundError(status);
  }
  VersionSet::LevelSummaryStorage tmp;
  Log(options_.info_log, "compacted to: %s", versions_->LevelSummary(&tmp));
  return status;
}


参考文献

[1] LevelDB 原理解析:数据的读写与合并是怎样发生的?(在原文基础上增添内容)
[2] 【leveldb】Compact(二 十 三):Major Compaction

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要阅读Leveldb源码,你可以按照以下步骤进行: 1. 确保你对C++语言有基本的了解。Leveldb是用C++编写的,因此你需要熟悉C++的语法和面向对象编程的概念。 2. 阅读Leveldb的README文件。这个文件提供了关于Leveldb的基本信息,如其用途、功能和性能特征。同时,它还列出了Leveldb的依赖关系,这对于理解源码以及构建和运行Leveldb非常重要。 3. 了解Leveldb的核心概念和数据结构。Leveldb是一个高效的键值存储库,它使用了一些关键的数据结构,如有序字符串表(Skip List)和持久化存储。 4. 查看Leveldb的目录结构。Leveldb源码包含了一些核心文件和目录,如“db”目录下的文件是Leveldb的核心实现。理解源码的组织结构可以帮助你快速找到感兴趣的部分。 5. 阅读核心文件的源码。从“db/db_impl.cc”文件开始,这个文件是Leveldb的主要实现。阅读这个文件可以帮助你了解Leveldb如何管理内存、实施并发控制和实现持久化存储。 6. 跟踪函数调用和数据流。了解Leveldb的主要功能是如何通过函数调用进行实现的很重要。你可以使用调试器或添加日志输出来跟踪函数调用和数据流,这有助于你了解代码的执行流程和逻辑。 7. 阅读Leveldb的测试用例。Leveldb源码中包含了大量的测试用例,这些用例对于理解Leveldb的不同功能和特性非常有帮助。通过阅读和运行这些测试用例,你可以对Leveldb的行为有更深入的了解。 8. 参考文档和论文。如果你想更深入地了解Leveldb的实现原理和技术细节,可以查阅Leveldb的官方文档或相关的论文。这些文档可以为你提供更详细的信息和背景知识。 最后,要理解Leveldb源码并不是一件简单的任务,需要投入大量的时间和精力。所以,建议你在阅读源码之前,对C++和数据库原理有一定的了解和经验,同时也要具备耐心和持续学习的精神。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值