leveldb设计分析之log

在leveldb中log的意义是什么?

所有的写操作都必须先成功的append到操作日志中,然后再更新内存memtable。这样做有两个有点:
1可以将随机的写IO变成append,极大的提高写磁盘速度;
2防止在节点down机导致内存数据丢失,造成数据丢失,这对系统来说是个灾难。

log的结构

+-------------+-------------+-------------+-------------+
| block       | block       | block       | block       |
+-------------+-------------+-------------+-------------+

log由连续的block组成,每个大小为固定的32kb,而每个block由连续的record组成。

+--------+
| record |
+--------+
| record |
+--------+
| record |
+--------+

record的内部结构如下:

  4 bytes       2 bytes       1 byte
+-------------+-------------+--------------+-----------+
| crc32       | length      | log type     | data      |
+-------------+-------------+--------------+-----------+

log有四种类型: FULL,FIRST, MIDDLE, LAST。为什么会有这四种类型?分开说。如果一个用户的数据小于32kb。那么它可以在一个block里存放。这时,数据所在record(只有一个)的logtype就是FULL,如果用户数据很大,超过32kb,并且占据了好多个block。那么第一个block中存放的数据record,就是FIRST(它其实是上次数据占用block部分空间后,剩余部分)。中间block的所有的record都是MIDDLE类型,而最后一个block,也有一部分数据,那么这个record就是LAST,当然它可能占不满block,那么剩下的空间就是下一个user data的FIRST。下面有个图比较直观:

leveldb设计分析之log_第1张图片

我们可以看出,由于record有data数据,所以它不是定长的。而且用户角度的record跟底层log中结构中的record是不一样的。用户的record数据可能很大,可能跨越很多block。由于record是block内部的结构,导致最终落实到内部结构时无法跨越block,所以导致了分拆。

write

写入的接口是AddRecord。

Status Writer::AddRecord(const Slice& slice)

在实现方面,通过leftover来跟踪当前block内剩余的空间,其中block_offset_在block内的偏移。当剩余空间少于7个字符(record内部结构图中的4+2+1)用0填充(一般是6个字符的空间)。剩下的逻辑就要其实是按block为单位大小来写入的,一直写直到处理完用户提交的数据。实际的写入动作其实委托给了EmitPhysicalRecord函数。在EmitPhysicalRecord函数内部首先会生成一个record头部(包括前面提到的4+2+1结构),然后把用户的payload追加到后面就可以了。

read

ReadRecord函数执行具体的读取log的动作。在一开始需要先设置一下处理点,如果last_record_offset_小于initial_offset_,那么需要将处理点移动到initial_offset_开始(函数SkipToInitialBlock)。在reader构造函数中,initial_offset_跟last_record_offset_都是0。那么在什么情况下,last_record_offset_会比initial_offset_小呢?

实际的读取动作在ReadPhysicalRecord函数里。在函数里面首先处理一个情况就是buffer_.size() < kHeaderSize。该条件成立,一种情况是出现在第一次read,因为buffer_在reader的构造函数里是初始化空的,第二种情况就是上次read是一个FULL类型的record,那么当前读到record后面剩余的内容为6个0的trailer。这里需要识别到这种情况,并反馈给上层告知EOF。

除了上面的情况,那么意味着当前读到了一个有效的record。核心的处理就是解析record header并告知上层,同时使用Slice *result参数携带解析得到的payload数据。

ReadRecord接下来处理各种record类型,并依次将user data追加到scratch参数提供的内存中, 并在发现FIRST类型时标记处本次处理的record的位置,即last_record_offset_。

其中使用in_fragmented_record来检测record的完整性。当发现FIRST类型的record时,该变量被置为true,那么在任何时候碰到MIDDLE,LAST,但是in_fragmented_record却为false的,都算是异常情况。

你可能感兴趣的:(高性能server,架构与设计)