leveldb之log写操作

来源:互联网 发布:网易云课堂mac版 编辑:程序博客网 时间:2024/05/23 17:55

当应用要插入一条记录时,leveldb首先是将其写入到log中,若成功,则继续将其插入到memtable中。因此,当系统故障而memtable又没有来得及将数据存放到内存中,那么就可以通过log文件来恢复数据,保证数据不会丢失。
由于log的读比较复杂,因此将主要介绍log的写操作。

在class DBImpl中主要有两个与log相关的成员变量:log::Writer* log_; 和 WritableFile* logfile_;
其中log_用于向logfile_中增加一条记录 ,logfile_主要用于对log文件进行同步、刷新等操作

1、Writer类

class Writer { public:  explicit Writer(WritableFile* dest);  ~Writer();  Status AddRecord(const Slice& slice); private:  WritableFile* dest_;  //以一个WritableFile对象作为Writer的成员,Writer则是将要插入的记录插入到dest_中  int block_offset_;       // 当前位置在Block中的偏移  uint32_t type_crc_[kMaxRecordType + 1];//CRC  Status EmitPhysicalRecord(RecordType type, const char* ptr, size_t length);//调用Append写入数据};

Writer类对外只提供了一个方法AddRecord()用于加入一条记录,同时其中还有一个WritableFile成员变量,记录最终是插入到WritableFile创建的文件中的。

根据leveldb源码可知,log文件每次都是以32K的物理Block为单位进行操作的,因此log文件可看作是由很多个连续的32K的Block组成的。插入一条数据时,首先确定数据在Block中的起始位置,然后不断写入到log文件中。

Status Writer::AddRecord(const Slice& slice) {  const char* ptr = slice.data();  size_t left = slice.size();  Status s;  bool begin = true;  do {    const int leftover = kBlockSize - block_offset_;//当前Block中的剩余空间    assert(leftover >= 0);    if (leftover < kHeaderSize) {//若剩余空间比固定头部要小,则要在一个新Block的开始写入数据      if (leftover > 0) {        // Fill the trailer (literal below relies on kHeaderSize being 7)        assert(kHeaderSize == 7);        dest_->Append(Slice("\x00\x00\x00\x00\x00\x00", leftover));//尾部填充      }      block_offset_ = 0;//块内偏移置0    }    const size_t avail = kBlockSize - block_offset_ - kHeaderSize;//当前Block中,可用于填充数据的长度    const size_t fragment_length = (left < avail) ? left : avail;//第一个要写入Block的分段的长度    RecordType type;    const bool end = (left == fragment_length);//若end=1,则表明所有数据都可存放在当前Block中    if (begin && end) {//根据begin和end确定当前记录的类型type      type = kFullType;    } else if (begin) {      type = kFirstType;    } else if (end) {      type = kLastType;    } else {      type = kMiddleType;    }    s = EmitPhysicalRecord(type, ptr, fragment_length);//将固定头部和fragment_length长的分段写入到log文件dest_    ptr += fragment_length;//指向数据的指针向前移动已写入长度    left -= fragment_length;//剩余待写入长度减小    begin = false;  } while (s.ok() && left > 0);  return s;}

要写入的记录分为固定头部和待写入数据两部分,其中固定头部包括:CRC(4字节)、记录长度(2字节)、type(1字节)共7字节。而待写入数据一般是经过WriteBatch组织的一条记录(主要包括type(kTypeValue或kTypeDeletion)、key、value)。

2、WritableFile类

class WritableFile { public:  WritableFile() { }  virtual ~WritableFile();  virtual Status Append(const Slice& data) = 0;//写入记录  virtual Status Close() = 0;//关闭文件  virtual Status Flush() = 0;//刷新文件  virtual Status Sync() = 0;//同步文件};

WritableFile类只是作为一个抽象基类,定义了一些纯虚函数作为接口,最终作为父类被继承。
leveldb中定义的一个子类为class PosixWritableFile:

class PosixWritableFile : public WritableFile { private:  std::string filename_;//要操作的文件名  FILE* file_;//最终要操作的文件 public:  virtual Status Append(const Slice& data) {    size_t r = fwrite_unlocked(data.data(), 1, data.size(), file_);//调用fwrite将数据写入到file_中    return Status::OK();  }  virtual Status Close() {    Status result;    if (fclose(file_) != 0) {//关闭文件      result = IOError(filename_, errno);    }    file_ = NULL;    return result;  }  virtual Status Flush() {    if (fflush_unlocked(file_) != 0) {//刷新文件      return IOError(filename_, errno);    }    return Status::OK();  }  virtual Status Sync() {//同步文件    // Ensure new files referred to by the manifest are in the filesystem.    Status s = SyncDirIfManifest();    if (fflush_unlocked(file_) != 0 ||        fdatasync(fileno(file_)) != 0) {      s = Status::IOError(filename_, strerror(errno));    }    return s;  }};

WritableFile类在写入数据时不会对数据进行任何封装、修改操作,而是直接将数据写入到log文件中。
因此我们一般插入一个Key-Value对时,首先会调用batch.Put(key, value);将其组织成一条记录,然后调用Write::AddRecord(),在log文件中找到合适的位置,同时为每一条记录增加一个头部,再将其写入到log文件中。然后调用WritableFile的Flush()、Sync()等方法来对log文件进行操作。

0 0
原创粉丝点击