“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)还会设计随机读操作,作者针对每种操作类型做了封装:
1 class SequentialFile { 2 public: 3 virtual Status Read(size_t n, Slice* result, char* scratch) = 0; 4 virtual Status Skip(uint64_t n) = 0; 5 }; 6 class RandomAccessFile { 7 public: 8 virtual Status Read(uint64_t offset, size_t n, Slice* result, 9 char* scratch) const = 0;10 };11 class WritableFile {12 public:13 virtual Status Append(const Slice& data) = 0;14 virtual Status Close() = 0;15 virtual Status Flush() = 0;16 virtual Status Sync() = 0;17 };
操作系统相关操作在不同系统下,实现各不相同,作者也做了统一的接口封装(跨平台),称之为“环境类”Env:
1 class Env { 2 public: 3 Env() { } 4 virtual ~Env(); 5 6 // Return a default environment suitable for the current operating 7 // system. 8 static Env* Default(); 9 10 //基于不同的操作系统实现了各种文件类型的具体类,通过该工厂方法返回。11 virtual Status NewSequentialFile(const std::string& fname, SequentialFile** result) = 0;12 virtual Status NewRandomAccessFile(const std::string& fname, RandomAccessFile** result) = 0;13 virtual Status NewWritableFile(const std::string& fname, WritableFile** result) = 0;14 ......15 };
写入
Leveldb命名空间下,有一个名为log的子命名空间,其下有Writer、Reader两个实现类。按前几节的命名规则,Writer其实是一个Builder,它对外提供了唯一的AddRecord方法用于追加操作记录。
1 Status Writer::AddRecord(const Slice& slice) { 2 const char* ptr = slice.data(); 3 size_t left = slice.size(); 4 5 // Fragment the record if necessary and emit it. Note that if slice 6 // is empty, we still want to iterate once to emit a single 7 // zero-length record 8 Status s; 9 bool begin = true;10 do {11 const int leftover = kBlockSize - block_offset_; //1. 当前块剩余大小12 assert(leftover >= 0);13 if (leftover < kHeaderSize) //2. 剩余大小不足,占位14 {15 // Switch to a new block16 if (leftover > 0) 17 {18 // Fill the trailer (literal below relies on kHeaderSize being 7)19 assert(kHeaderSize == 7);20 dest_->Append(Slice("\x00\x00\x00\x00\x00\x00", leftover));21 }22 block_offset_ = 0;23 }24 25 // Invariant: we never leave < kHeaderSize bytes in a block.26 assert(kBlockSize - block_offset_ - kHeaderSize >= 0);27 28 const size_t avail = kBlockSize - block_offset_ - kHeaderSize;29 const size_t fragment_length = (left < avail) ? left : avail; //3. 当前块存储的空间大小30 31 RecordType type; //4. Record Type32 const bool end = (left == fragment_length); 33 if (begin && end) {34 type = kFullType;35 }36 else if (begin) {37 type = kFirstType;38 }39 else if (end) {40 type = kLastType;41 }42 else {43 type = kMiddleType;44 }45 46 s = EmitPhysicalRecord(type, ptr, fragment_length); //5. 写入文件47 ptr += fragment_length;48 left -= fragment_length;49 begin = false;50 } while (s.ok() && left > 0);51 return s;52 }
备忘如下:
- 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的版本机制必定要搞清楚,而这本身又是很丰富的内容。