【leveldb源码阅读】之LOG

理清leveldb基本架构图之后,我又标出了一些比较重要(主要因为我是小白)的信息点,如下:

在这里插入图片描述

[comment<>(在代码 [db/filename.h]可以看到leveldb的几种文件类型)

// Owned filenames have the form:
//    dbname/CURRENT
//    dbname/LOCK
//    dbname/LOG
//    dbname/LOG.old
//    dbname/MANIFEST-[0-9]+
//    dbname/[0-9]+.(log|sst|ldb)

我们先按照写数据的顺序对着代码依次介绍。

LOG

写入数据的时候,最开始会写入到log文件中,由于是顺序写入文件,所以写入速度很快,可以马上返回。

log日志的格式说明[doc/log_format.md]

  • 一个Log文件由多个Block组成,每个Block大小为32KB。

  • 一个Block内部又有多个Record组成,Record分为四种类型(还有一个留为预分配文件[db/log_format.h]):

    • Full:一个Record占满了整个Block存储空间。
    • First:一个Block的第一个Record。
    • Last:一个Block的最后一个Record。
    • Middle:其余的都是Middle类型的Record。
  • 一个Record由几部分组成:

    • Header部分
    • 32位长度的CRC
    • 16位长度的Length:存储数据部分长度。
    • 8位长度的Type:存储Record类型,就是上面说的四种类型。

1.1 写log

写过程举个例子,我们现在想把这些数据写入:

    A: 长度 1000
    B: 长度 97270
    C: 长度 8000

我们先装第一个block。可以看到A数据很小,所以用FULL 就可以装下,到这时第一个record还剩31761B的空间。

扫描二维码关注公众号,回复: 10479349 查看本文章

下面继续装B,但是它好大,要把它切分再装。B的第一部分要接着第一个Record去装,所以在这里它的RecordTypeFirst,装了31761B的数据。这时候第一个block已经满了,再来一个block,这个block刨除record的hearder、crc等部分,还可以装32761B的数据,当然这还是不够B装的,没关系,我们接着开一个block装。而这时,对于B的第二个部分的RecordType就是Second了。这时B还剩余32655B的数据,一个block是装得下的,还剩了6B的空间,我们留出来做trailer

致郁C,和A一样,都是FULL record,落在第四个block中。

上述过程可以用一张图直观表示:

总结一下,log有多个固定大小的block组成 ,block又由record组成,record是连续的,数据可能会被拆到不同的record上。

写操作类Writer中的接口函数是AddRecord

Status AddRecord(const Slice& slice);

简单看一下这个函数:

status:状态
block_offset_ : 当前block用(偏移)到哪里了
leftover : 当前block还剩多少
left:待写入数据
kBlockSize:32(32768,Bytes)
kHeaderSize:74+2+1,Bytes)
type:即RecordType


while(status_is_ok &&  left>0) {
    if (leftover < kHeaderSize) {
        // 用0填充
    }
    // 根据left、block_offset_,更新RecordType
    // 真正写入过程由EmitPhysicalRecord完成,包括生成一个record头部,追加数据
    // 更新status
    // 更新left
}

1.2 读log

读操作类Writer中的接口函数是ReadRecord

bool ReadRecord(Slice* record, std::string* scratch);
  // 真正读入过程由ReadPhysicalRecord实现:从文件中每次读取一个Block,Read内部会做偏移,保证按顺序读取,并判断各种badrecord的情况
  // 根据recordtype,向switch指向的内存中追加数据
  switch(recordtype) {
      case Full:
      case First:
      case Middle:
      case Last:
  }

Slice是一个结构体,其中只有两个成员,一个指向外存的指针,一个是大小。

代码实现用了switch...case真是棒呆。具体的代码逻辑我加了一些注释,可以具体了解一下。

参考资料

LevelDB设计与实现 - 基础篇

发布了120 篇原创文章 · 获赞 35 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/u012328476/article/details/104218904