leveldb源码分析(七)db操作流程详解

1、Get

Get操作较简单,先查内存的两个memtable,查不到的话继续查磁盘文件,伪代码如下:

Get(options, key, value) {

    mutex.lock()

    {

        sequence_num = options.has_snapshot() ? options.snapshot() : versions.last_sequence()   // 获取seq num

        mem.Ref(), imm.Ref(), current_version.Ref() // 加引用计数保证本次查找过程中不会被销毁

        db_key = key + sequence_num // 拼接db的key

    }

    mutex.unlock()  // 查找前解锁,对两个memtable和sst文件读不需要同步保护,引用计数保证不会被销毁即可

 

    if (mem.Get(db_key, value)) {   // first seek in mutable memtable

    else if (imm.Get(db_key, value) { // if not found, then seek in immutable memtable

    else {

        current_version.Get(db_key, value)  // if not found, then seek in sstable files from disk

    }

 

    mutex.lock()

    {

        MaybeScheduleCompaction()   // 若访问了sstable文件,可能导致seek次数超限触发compaction

        mem.Unref(), imm.Unref(), current_version.Unref()   // 减引用计数

    }

    mutex.unlock()

}

2、Put && Delete

leveldb是惰性追加删除,Delete操作只是在key中嵌入了type标记本条记录是删除,本质和Put操作流程相同。leveldb的Put操作需要写磁盘,因此进行了合并写优化,具体流程伪代码如下

Write(options, updates) {

    mutex.lock()

    {

        writers.push_back(current_writer)   // 当前writer加入writers队列

        while (!current_writer.done && current_writer != writers.front())

            current_writer.condition_variable.wait()    // (A)队列头的writer负责写入,其余writer等待

 

        if (current_writer.done)    // (B)

            return

 

        // 只有队列头的writer能进入这里

 

        MakeRoomForWrite() // 检查当前memtable大小和level0文件大小是否超限,若超限可能延迟或阻塞写入操作直到compaction完成

        last_sequence = versions.LastSequence()

        write_batch = BuildBatchGroup(&last_writer) // 尽可能将多次写合并,last_writer返回本次合并的最后一个writer

        write_batch.SetSequence(last_sequence)  // 设置本次批量写入的起始序列号

        last_sequence += write_batch.Count() // 每条记录占一个单独的序列号,last_sequence设置为最后一个记录的序列号

    }

    mutex.unlock()  // 这里可以解锁,因为只有队列头的writer有权利写,待耗时长的写磁盘结束后再把锁拿回来,期间其他writer可以加入队列,并在(A)位置等待

 

    log.AddRecord(write_batch) && log_file.Sync()   // 1、记录log文件并刷入磁盘

    mem.InsertInto(write_batch)                     // 2、写入内存的mutable memtable

 

    mutex.lock()

    {

        versions.SetLastSequence(last_sequence) // 更新序列号

        while (true) {

            front = writers.front()

            writers.pop_front()

            if (front != current_writer) {

                // 对本次被合并的其他writer,设置done并唤醒,该writer所在线程被唤醒后在(B)位置看到done并返回

                front.done = true

                front.condition_variable.signal()

            }

            if (front == last_writer)   // 遍历到了本次合并的最后一个writer,结束

                break

        }

        // 若writers队列中还有writer,那么唤醒队列头的writer,由该writer负责后续的写入工作

        if (!writers.empty())

            writers.front().condition_variable.signal()

    }

    mutex.unlock()

}

3、Compaction

// Compaction是一次compaction操作的抽象,每次compaction包含基层level+0的文件和level+1的文件,最终合并成若干level+1的文件

Compaction* PickCompaction() {

    level = current_version.compaction_level

    // 根据size触发compaction还是seek数量触发compaction,选出本次compaction的基层level

    if (size_compaction) {

        for (file : current_version.files[level]) {

            if (compact_pointer[level].empty() || file.largest_key > compaction_pointer[level]) {

                compaction.inputs[0].push_back(file)

                break

            }

        }

    else if (seek_compaction) {

        compaction.inputs[0].push_back(current.files_to_compact)

    }

    // 和其他level不同,level0的文件彼此key range可能重合,需要全部找出来

    if (level == 0)

        current_version.GetOverlappingInputs()

    // 基层level文件就绪,继续添加level+1文件

    SetupOtherInputs(compaction)

    return compaction

}

 

SetupOtherInputs() {

    // 若同一个user key的若干记录刚好本两个文件分开了,那么需要把另一个文件也加进来,否则会查到旧数据,例如

    // file1结尾是 user_key="alibaba", seq=101,file2开头是 user_key="alibaba", seq=100

    // 若本次只合并file1到下一层,那么下次查找这个key的时候会先查到file2的旧数据

    AddBoundaryInputs(inputs[0])

 

    // 获取inputs[0]层所有文件的最小key和最大key,在level+1层中寻找有冲突的文件并加入inputs[1]

    GetRange(inputs[0], &smallest, &largest)

    GetOverlappingInputs(level + 1, smallest, largest, &inputs[1])

 

    // 如下操作试图扩展inputs[0],即如果level+0层的其他文件加入不会导致level+1层新增其他冲突文件,那么扩展到inputs[0]

    GetRange2(inputs[0], inputs[1], &all_smallest, &all_largest)

    GetOverlappingInputs(level, all_smallest, all_largest, &expanded0)

    GetRange(expanded0, &new_smallest, &new_largest)    // 获取扩展后expanded0的最小key和最大key

    GetOverlappingInputs(level + 1, new_smallest, new_largest, &expanded1)

    if (expanded0.size() > inputs[0].size() && expanded1.size() < inputs[1].size())

        inputs[0] = expanded0

}

 

BackgroundCall() {

    // 执行compaction

    BackgroundCompaction()

    // 一次compaction后可能导致一个level产生了过多文件,尝试再次进行compaction

    MaybeScheduleCompaction()

}

 

BackgroundCompaction() {

    // 若有immutable memtable,那么刷入磁盘文件

    if (imm != nullptr) {

        CompactMemTable()

        return

    }

    // 手动compaction

    if (manual) {

        compaction = versions.CompactRange()

    else {

        // size或者seek触发的compaction

        compaction = versions.PickCompaction()

    }

    // 简单compaction只要增加level到level+1即可

    if (!manual && compaction.IsTrivialMove()) {

        compaction.edit().RemoveFile(level, file_number)

        compaction.edit().AddFile(level, file_number)

        versions.LogAndApply(compaction->edit())

    else {

        // 实际compaction操作

        DoCompactionWork()

    }

}

 

DoCompactionWork() {

    // 获取多路归并排序中每个文件的迭代器

    merge_iter = versions.MakeInputIterator(compaction)

    whlie (merge_iter.Valid()) {

        if (imm != nullptr) {

            CompactMemTable()

        }

        key = merge_iter.key()

        if (compaction.ShouldStopBefore(key))

            // 条件1、若当前key range导致下层冲突过多,那么不能再追加了,产出一个sst文件

            FinishCompactionOutputFile()

        if (key.user_key() == last_key.user_key()) {

            // 和上一个key拥有相同的user_key,当前key更旧不再需要了直接剔除

            continue;

        else {

            // 启动sst文件builder

            if (compaction.builder == nullptr)

                OpenCompactionOutputFile()

            compaction.builder.Add(key, merge_iter.value())

            // 条件2、若当前builder中存储的数据大小达到一定限制,产出一个sst文件

            if (compaction.builder.FileSize() > MaxOutputFileSize())

                FinishCompactionOutputFile()

        }

    }

    // 将最新文件信息保存到current version同时删除本次compaction的输入的旧文件

    InstallCompactionResults()

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值