“LOG文件在LevelDb中的主要作用是系统故障恢复时,能够保证不会丢失数据。因为在将记录写入内存的Memtable之前,会先写入Log文件,这样即使系统发生故障,Memtable中的数据没有来得及Dump到磁盘的SSTable文件,LevelDB也可以根据log文件恢复内存的Memtable数据结构内容,不会造成系统丢失数据,在这点上LevelDb和Bigtable是一致的。” (http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html)
准备工作:
Log文件只涉及顺序读、顺序写两种操作,而在LevelDB中的其他文件(如SSTable)还会设计随机读操作,作者针对每种操作类型做了封装:
class SequentialFile {
public:
virtual Status Read(size_t n, Slice* result, char* scratch) = ;
virtual Status Skip(uint64_t n) = ;
};
class RandomAccessFile {
public:
virtual Status Read(uint64_t offset, size_t n, Slice* result,
char* scratch) const = ;
};
class WritableFile {
public:
virtual Status Append(const Slice& data) = ;
virtual Status Close() = ;
virtual Status Flush() = ;
virtual Status Sync() = ;
};
操作系统相关操作在不同系统下,实现各不相同,作者也做了统一的接口封装(跨平台),称之为“环境类”Env:
class Env {
public:
Env() { }
virtual ~Env(); // Return a default environment suitable for the current operating
// system.
static Env* Default(); //基于不同的操作系统实现了各种文件类型的具体类,通过该工厂方法返回。
virtual Status NewSequentialFile(const std::string& fname, SequentialFile** result) = ;
virtual Status NewRandomAccessFile(const std::string& fname, RandomAccessFile** result) = ;
virtual Status NewWritableFile(const std::string& fname, WritableFile** result) = ;
......
};
写入
Leveldb命名空间下,有一个名为log的子命名空间,其下有Writer、Reader两个实现类。按前几节的命名规则,Writer其实是一个Builder,它对外提供了唯一的AddRecord方法用于追加操作记录。
Status Writer::AddRecord(const Slice& slice) {
const char* ptr = slice.data();
size_t left = slice.size(); // Fragment the record if necessary and emit it. Note that if slice
// is empty, we still want to iterate once to emit a single
// zero-length record
Status s;
bool begin = true;
do {
const int leftover = kBlockSize - block_offset_; //1. 当前块剩余大小
assert(leftover >= );
if (leftover < kHeaderSize) //2. 剩余大小不足,占位
{
// Switch to a new block
if (leftover > )
{
// Fill the trailer (literal below relies on kHeaderSize being 7)
assert(kHeaderSize == );
dest_->Append(Slice("\x00\x00\x00\x00\x00\x00", leftover));
}
block_offset_ = ;
} // Invariant: we never leave < kHeaderSize bytes in a block.
assert(kBlockSize - block_offset_ - kHeaderSize >= ); const size_t avail = kBlockSize - block_offset_ - kHeaderSize;
const size_t fragment_length = (left < avail) ? left : avail; //3. 当前块存储的空间大小 RecordType type; //4. Record Type
const bool end = (left == fragment_length);
if (begin && end) {
type = kFullType;
}
else if (begin) {
type = kFirstType;
}
else if (end) {
type = kLastType;
}
else {
type = kMiddleType;
} s = EmitPhysicalRecord(type, ptr, fragment_length); //5. 写入文件
ptr += fragment_length;
left -= fragment_length;
begin = false;
} while (s.ok() && left > );
return s;
}
备忘如下:
- Log文件被逻辑上分为多个Block,每个Block大小为32K。
- 每条记录由Record Header + Record Content组成,其中Header大小为kHeaderSize(7字节)。
- 当前Block剩余大小不足以填充Record Header时,以"\x00\x00\x00\x00\x00\x00"占位。
- 当Block无法完整记录一条Record时,通过type信息标识该record在当前block中的区块信息,以便读取时可根据type拼接出完整的record。
- EmitPhysicalRecord向Block中插入Record数据
- 每个Record结构如下:
Header |
Record Content |
||
CRC |
Record Size |
Type |
Record Content |
读取
Log读取逻辑并无特别之处,略。
注:LOG文件和活动的MemTable对应,当活动的MemTable将被切换为非活动(immutable)MemTable时,将创建新的LOG文件。
原本Current、Manifest与Log打算一起备注,但要搞清楚Manifest,LevelDB的版本机制必定要搞清楚,而这本身又是很丰富的内容。