2019-09-09

目录:

  • 1. 原子性 Atomicity
  • 2. Consistency
  • 2.1 有没有事务, log没有保存旧值, 如何撤销?
  • 2.2 (TODO)基于leveldb, filestore 是否实现了 事务/commit等?
  • 3. log
  • 3.1 写log在哪里
  • 4. RecoverLogFile
  • 5. VersionSet::Recover

  • 1. 原子性 Atomicity

    ReadPhysicalRecord():

    1. header eof, 但读到的<kHeaderSize, 则知道header不完整.
    2. header完整, 但对应的payload数据不够.
    3. 检查 checksum


    CURRENT文件的原子: 通过mv来实现, 保证CURRENT的内容是有效的. see SetCurrentFile()


    leveldb::WriteBatch batch;
    batch.Delete("key");
    batch.Put("key2", value);
    db->Write(leveldb::WriteOptions(), &batch);
    

    WriteBatch中 rep_ 中虽然有存储batch需要完成的个数.
    WriteBatch::Put -> WriteBatchInternal::SetCount 修改 ->rep_[8]
    Writer::AddRecord -> Writer::EmitPhysicalRecord
    但这个并不作为完整性校验的依据.
    原子的实现 和 非batch 的方法一样.

    2. Consistency

    2.1 有没有事务, log没有保存旧值, 如何撤销?

    问题: log record内容是 事务id, 元素, 旧值, 新值, 对吗?
    答:不对. 因为不支持事务, 也不存储旧值.

    问题: 检查到 corrupt 的记录后, 如何处理, 有没有将这个corrupt的记录抹掉,代码在?
    答:

    VersionSet::Recover
    {
      while (reader.ReadRecord(&record, &scratch) && s.ok()) {
        s = edit.DecodeFrom(record);
      }
    }
    

    没有抹掉 corrupt 的数据.

    问题: open, 不写入key, 然后关闭, 后续, 是否会重复同一个文件的 Recover ?

    DB::Open
    {
      VersionEdit edit;
      bool save_manifest = false;
      impl->Recover(&edit, &save_manifest);
      if (save_manifest) {
        edit.SetPrevLogNumber(0);// No older logs needed after recovery.
        edit.SetLogNumber(impl->logfile_number_);
      }
    }
    

    问题: 判断是否有log 需要恢复?
    答: 参与恢复的log, 其编号 需要大于LogNumber().

    DBImpl::Recover
    |--VersionSet::Recover(bool *save_manifest)
    | |--log::Reader reader(file,.., 0/*initial_offset*/);
    | |--reader.ReadRecord(&record, &scratch)
    | |--log_number_, has_prev_log_number_
    |--Recover from all newer log files than the ones named in the descriptor
    |--const uint64_t min_log = versions_->LogNumber();
    |--env_->GetChildren(dbname_, &filenames) //读取目录
    |--for each of filenames
    | |--if (type == kLogFile && ((number >= min_log) {logs.push_back(number)}
    |--for each of logs
    | |--RecoverLogFile
    

    2.2 (TODO)基于leveldb, filestore 是否实现了 事务/commit等?

    3. log

    uint64_t new_log_number = versions_->NewFileNumber();
    

    所以, log编号的作用域是VersionSet.

    3.1 写log在哪里

    当应用写入一条Key:Value记录的时候,LevelDb会先往log文件里写入,成功后将记录插进Memtable中,这样基本就算完成了写入操作. 代码?

    答:

    MakeRoomForWrite
    {
      // Attempt to switch to a new memtable and trigger compaction of old
      // 1.memtable满了 或者 2.没满但force
      //因为background_work_finished_signal_.Wait完成, 压缩完成, 也就是没有log正在被压缩, 故PrevLogNumber()为0.
      assert(versions_->PrevLogNumber() == 0);
      WritableFile* lfile = nullptr;
      env_->NewWritableFile(LogFileName(dbname_, new_log_number), &lfile);
      logfile_ = lfile;
      log_ = new log::Writer(lfile);
    }
    
    DBImpl::Write
    |--log_->AddRecord
    | |--dest_->Append()
    |--if (options.sync) logfile_->Sync()
    |--以下是写内存
    |--WriteBatchInternal::InsertInto(updates, mem_)
    | |--MemTableInserter inserter;
    | |--inserter.mem_ = memtable;
    | |--updates->Iterate(&inserter)
    | | |--Slice input(rep_);
    | | |--case kTypeValue:
    | | | |--handler->Put(key, value) => MemTableInserter::Put
    | | | |--mem_->Add(sequence_, kTypeValue, key, value)
    | | | | |--SkipList
    

    4. RecoverLogFile

    RecoverLogFile
    |--while
    | |--reader.ReadRecord(&record, &scratch)
    | |--WriteBatchInternal::SetContents(&batch, record)
    | | |--把record 放入 b->rep_
    | |--if (mem == nullptr) mem = new MemTable;
    | |--WriteBatchInternal::InsertInto(&batch, mem)
    | | |--   input(也就是record) 格式是:  (WriteBatch::rep_的格式)
    | | |--     seq(8B)  cnt(4B)tag key(+value) tag key(+value)
    | | |--    |---------|------|--|------------|--|-------------|
    | | |--while
    | | | |--MemTableInserter::Put 或Delete, 放入 MemTable
    | | | |--sequence_++
    | |--if (mem > write_buffer_size) WriteLevel0Table
    

    5. VersionSet::Recover

    VersionSet::Recover
    |--Builder builder(this, current_);
    |--while
    | |--reader.ReadRecord(&record, &scratch)
    | |--VersionEdit edit;
    | |--edit.DecodeFrom(record);
    | | |--case kCompactPointer:
    | | |  |--GetLevel
    | | |  |--GetInternalKey
    | | |  |--compact_pointers_.push_back(std::make_pair(level, key))
    | | |--case kDeletedFile:
    | | | |--deleted_files_.insert(std::make_pair(level, number))
    | | |--case kNewFile:
    | | | |--FileMetaData f
    | | | |--new_files_.push_back(std::make_pair(level, f))
    | |--builder.Apply(&edit); 将edit 应用到 Builder, 之后, edit就可以丢弃
    |--Version* v = new Version(this); // 基于VersionSet创建Version
    |--builder.SaveTo(v)  // Version* v
    | |--将base_ 和 levels_中added_files, 按递增顺序  放入 v
    | | |--MaybeAddFile -> push to v->files_[level]
    |--Finalize(v)  计算下次从哪个level进行压缩.
    |--AppendVersion(v);  current_ 指向@v
    

    要指明一个文件, 需要给出level 和 number. 比如 Level 4: File4_0, File4_1, File4_2, File4_3

    compact_pointers_ 见 https://awakening-fong.github.io/posts/database/leveldb_04_compact

    VersionSet是版本的集合, 从 AppendVersion() 就有体现. VersionEdit 不是 VersionSet. VersionEdit 的意思是版本经过 edit后, 变成另一个版本, 所以, VersionEdit可以认为是差异.

    本文地址: https://awakening-fong.github.io/posts/database/leveldb_01_data_safe

    转载请注明出处: https://awakening-fong.github.io


    若无法评论, 请打开JavaScript, 并通过proxy.


    blog comments powered by Disqus