基类leveldb::DB,提供面向用户的接口,用户主要使用其提供的接口来操作数据库,最重要的操作为增、删、查找。
class DB { public: static Status Open(const Options& options,const std::string& name,DB** dbptr);//打开一个名为name的数据库,并存放在dbptr中,会调用Recover() DB() { }//构造数据库 virtual ~DB(); virtual Status Put(const WriteOptions& options,const Slice& key,const Slice& value) = 0;//向数据库中写一条KV数据 virtual Status Delete(const WriteOptions& options, const Slice& key) = 0;//从数据库中删除一条记录 virtual Status Write(const WriteOptions& options, WriteBatch* updates) = 0;//批量写入数据 virtual Status Get(const ReadOptions& options,const Slice& key, std::string* value) = 0;//在数据库中查找key对应的value virtual Iterator* NewIterator(const ReadOptions& options) = 0; virtual const Snapshot* GetSnapshot() = 0; virtual void ReleaseSnapshot(const Snapshot* snapshot) = 0; virtual bool GetProperty(const Slice& property, std::string* value) = 0; virtual void GetApproximateSizes(const Range* range, int n,uint64_t* sizes) = 0; virtual void CompactRange(const Slice* begin, const Slice* end) = 0; private: // No copying allowed DB(const DB&); void operator=(const DB&); };
类leveldb::DBImpl派生自leveldb::DB,是leveldb最核心的部分。
leveldb::DBImpl中包含了大量的函数,包括派生自leveldb::DB的接口,一些用于测试的函数,还有一些用于后台处理的内部函数,用于完成后台合并等操作。下面是它的成员函数,暂时忽略测试函数和用于数据库恢复的函数以及错误处理的函数。
class DBImpl : public DB { public: DBImpl(const Options& options, const std::string& dbname); virtual ~DBImpl(); // 派生自leveldb::DB的接口 virtual Status Put(const WriteOptions&, const Slice& key, const Slice& value); virtual Status Delete(const WriteOptions&, const Slice& key); virtual Status Write(const WriteOptions& options, WriteBatch* updates); virtual Status Get(const ReadOptions& options,const Slice& key,std::string* value); virtual Iterator* NewIterator(const ReadOptions&); virtual const Snapshot* GetSnapshot(); virtual void ReleaseSnapshot(const Snapshot* snapshot); virtual bool GetProperty(const Slice& property, std::string* value); virtual void GetApproximateSizes(const Range* range, int n, uint64_t* sizes); virtual void CompactRange(const Slice* begin, const Slice* end); private: Status NewDB(); void DeleteObsoleteFiles(); void CompactMemTable() EXCLUSIVE_LOCKS_REQUIRED(mutex_); Status WriteLevel0Table(MemTable* mem, VersionEdit* edit, Version* base) EXCLUSIVE_LOCKS_REQUIRED(mutex_); Status MakeRoomForWrite(bool force /* compact even if there is room? */) EXCLUSIVE_LOCKS_REQUIRED(mutex_); WriteBatch* BuildBatchGroup(Writer** last_writer); void MaybeScheduleCompaction() EXCLUSIVE_LOCKS_REQUIRED(mutex_); static void BGWork(void* db); void BackgroundCall(); void BackgroundCompaction() EXCLUSIVE_LOCKS_REQUIRED(mutex_); void CleanupCompaction(CompactionState* compact) EXCLUSIVE_LOCKS_REQUIRED(mutex_); Status DoCompactionWork(CompactionState* compact) EXCLUSIVE_LOCKS_REQUIRED(mutex_); Status OpenCompactionOutputFile(CompactionState* compact); Status FinishCompactionOutputFile(CompactionState* compact, Iterator* input); Status InstallCompactionResults(CompactionState* compact) EXCLUSIVE_LOCKS_REQUIRED(mutex_); };由上可知,leveldb::DBImpl的成员函数主要是
1)实现派生自leveldb::DB的接口(对数据的增、删、查找)
2)后台合并相关的函数 (这一部分在leveldb之Compaction上、下中都见到过,就不再分析)
通过Put()写入一条记录,通过Write()批量写入
为了读取更加方便,leveldb每次写入记录时都会创建一个WriteBatch变量,然后再将WriteBatch变量写入到log文件中,并将WriteBatch中的每一条记录插入到Memtable中。即调用Put()时,首先也会将这一条记录转化为一个WriteBatch的形式,然后再调用Write()操作批量写入。
WriteBatch的格式是固定的(seq count type key.size() key value.size() value),按照这种方式将数据写入到log中后,读取时顺序的读取每一个WriteBatch即可获得每一条记录。
Put()的实现如下:
Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) { WriteBatch batch; batch.Put(key, value); return Write(opt, &batch); }
由对LevelDb之Compaction 的分析可知,leveldb在删除一条记录时并不会直接删除,而是写入一条标记为删除的记录,等待后面的合并操作再真正删除该记录。其实现如下
Status DB::Delete(const WriteOptions& opt, const Slice& key) { WriteBatch batch; batch.Delete(key); return Write(opt, &batch); }由上可知,删除记录时也是将记录转化成WriteBatch的形式,然后再将其写入到数据库中。
具体的删除操作在高level的合并时完成(见leveldb之Compaction操作下之具体实现 中一般的合并部分)
因此增加记录和删除记录最终调用的都是Write()函数,具体的实现见leveldb之Put、Get操作
Status DBImpl::Get(const ReadOptions& options,const Slice& key, std::string* value) { { mutex_.Unlock(); // First look in the memtable, then in the immutable memtable (if any). LookupKey lkey(key, snapshot); if (mem->Get(lkey, value, &s)) { //首先在Memtable中查找 // Done } else if (imm != NULL && imm->Get(lkey, value, &s)) {//若没找到且存在imm,则继续在imm中查找 // Done } else { //如果还没有找到,则继续在SSTable中查找 s = current->Get(options, lkey, value, &stats); have_stat_update = true; } mutex_.Lock(); } }
查找的具体实现见 leveldb之SSTable的创建与访问 中的第三部分
leveldb::DBImpl中有许多成员变量,其中最重要的是:
class DBImpl : public DB { private: TableCache* table_cache_;//SSTable文件缓存,保存了最近使用的.sst文件信息,以提高查找速度 MemTable* mem_; MemTable* imm_; // 等待合并的mem_,不能再向其中写入数据 port::AtomicPointer has_imm_; //判断是否有imm_ WritableFile* logfile_; //log文件,数据在插入mem_之前会写入到log文件中,以便于数据恢复 uint64_t logfile_number_; log::Writer* log_; //用于向log文件中写数据 std::deque<Writer*> writers_; //任务队列,等待向mem_中写入数据 std::set<uint64_t> pending_outputs_;//标记一个文件是否正在进行处理,以防止错误的删除正在执行Compaction的文件 bool bg_compaction_scheduled_;//标记是否已经完成或正在执行Compaction操作 VersionSet* versions_;//管理数据库运行过程中生成的所有版本 };
写数据时,首先写入log文件,再插入到mem_中,log文件可用于数据恢复,保证数据不丢失。
查找时,按照mem_,imm_,.sst文件的优先级进行查找,table_cache_缓存最近使用的.sst文件
versions_用于管理这个数据库的所有版本。
leveldb::DB类为用户提供了操作数据库的接口,主要包括对记录的增、删、查找。
leveldb::DBImpl类派生自leveldb::DB,实现了leveldb::DB中的接口,并且还增加了许多用于后台合并处理的内部函数。
增、删最终都是向数据库中写入一条记录,可能会导致Memtable写满,从而触发合并操作。查找记录时,按照mem_,imm_,.sst文件的优先级来进行查找。
在.sst文件中查找时是先找到覆盖目标Key值的所有文件,然后在缓存table_cache_中进行查找,并将最近使用的文件加入到缓存中。查找可能导致一些.sst文件的查找次数超过上限,此时也会触发合并操作。
读、写都可能触发leveldb的合并操作,合并操作的具体分析见LevelDb之Compaction