当前位置:Gxlcms > 数据库问题 > LevelDB Cache实现机制分析

LevelDB Cache实现机制分析

时间:2021-07-01 10:21:17 帮助过:17人阅读

  1. 136 class LRUCache {
  2. 137 public:
  3. 138 LRUCache();
  4. 139 ~LRUCache();
  5. 140
  6. 141 // Separate from constructor so caller can easily make an array of LRUCache
  7. 142 void SetCapacity(size_t capacity) { capacity_ = capacity; }
  8. 143
  9. 144 // Like Cache methods, but with an extra "hash" parameter.
  10. 145 Cache::Handle* Insert(const Slice& key, uint32_t hash,
  11. 146 void* value, size_t charge,
  12. 147 void (*deleter)(const Slice& key, void* value));
  13. 148 Cache::Handle* Lookup(const Slice& key, uint32_t hash);
  14. 149 void Release(Cache::Handle* handle);
  15. 150 void Erase(const Slice& key, uint32_t hash);
  16. 151
  17. 152 private:
  18. 153 void LRU_Remove(LRUHandle* e);
  19. 154 void LRU_Append(LRUHandle* e);
  20. 155 void Unref(LRUHandle* e);
  21. 156
  22. 157 // Initialized before use.
  23. 158 size_t capacity_;
  24. 159
  25. 160 // mutex_ protects the following state.
  26. 161 port::Mutex mutex_;
  27. 162 size_t usage_;
  28. 163 uint64_t last_id_;
  29. 164
  30. 165 // Dummy head of LRU list.
  31. 166 // lru.prev is newest entry, lru.next is oldest entry.
  32. 167 LRUHandle lru_;
  33. 168
  34. 169 HandleTable table_;
  35. 170 };
技术分享

     1) capacity_是Cache大小,其单位可以自行指定(如table cache,一个sstable文件的索引信息是一个单位,而block cache,一个byte是一个单位);

     2)lru_是一个双向链表,如注释说明,lru_.prev是最新被访问的条目,lru_.next是最老被访问的条目。在访问cache中的一个数据时,会顺次执行LRU_Remove和LRU_Append函数,将条目移到lru_.prev的位置;

     3)table_是LevelDB自己实现的一个hash_map,其实现也在cache.cc文件中,据作者说,在特定的编译环境下性能更优,如与g++ 4.4.3内置的hashtable相比,随机读性能可以提升5%;

     4)insert操作会根据capacity_的大小,顺着lru_.next讲老的条目Release掉;

     2. block 的读取逻辑,代码片段如下:

     leveldb-1.7.0/table/table.cc

技术分享
  1. 154 Iterator* Table::BlockReader(void* arg,
  2. 155 const ReadOptions& options,
  3. 156 const Slice& index_value) {
  4. 157 Table* table = reinterpret_cast<Table*>(arg);
  5. 158 Cache* block_cache = table->rep_->options.block_cache;
  6. 159 Block* block = NULL;
  7. 160 Cache::Handle* cache_handle = NULL;
  8. 161
  9. 162 BlockHandle handle;
  10. ......
  11. 168 if (s.ok()) {
  12. 169 BlockContents contents;
  13. 170 if (block_cache != NULL) {
  14. ......
  15. 175 cache_handle = block_cache->Lookup(key);
  16. 176 if (cache_handle != NULL) {
  17. 177 block = reinterpret_cast<Block*>(block_cache->Value(cache_handle));
  18. 178 } else {
  19. 179 s = ReadBlock(table->rep_->file, options, handle, &contents);
  20. 180 if (s.ok()) {
  21. 181 block = new Block(contents);
  22. 182 if (contents.cachable && options.fill_cache) {
  23. 183 cache_handle = block_cache->Insert(
  24. 184 key, block, block->size(), &DeleteCachedBlock);
  25. 185 }
  26. 186 }
  27. 187 }
  28. 188 } else {
  29. 189 s = ReadBlock(table->rep_->file, options, handle, &contents);
  30. 190 if (s.ok()) {
  31. 191 block = new Block(contents);
  32. 192 }
  33. 193 }
  34. 194 }
  35. 195
  36. 196 Iterator* iter;
  37. 197 if (block != NULL) {
  38. 198 iter = block->NewIterator(table->rep_->options.comparator);
  39. 199 if (cache_handle == NULL) {
  40. 200 iter->RegisterCleanup(&DeleteBlock, block, NULL);
  41. 201 } else {
  42. 202 iter->RegisterCleanup(&ReleaseBlock, block_cache, cache_handle);
  43. 203 }
  44. 204 } else {
  45. 205 iter = NewErrorIterator(s);
  46. 206 }
  47. 207 return iter;
  48. 208 }
技术分享

     1) 首先从block cache中查找block,如果找不到,直接从持久化存储中读取,获取到一个新的block,插入block cache;

     2) 对于查到的block,返回对应的迭代器(LevelDB中,所有的get\merge操作均是抽象成iterator实现的);

     3)如果仔细读代码,iter->RegisterCleanup函数实现会有点绕,这个函数在iter析构时被调用,执行注册的ReleaseBlock,ReleaseBlock调用cache_handle的Unref方法,对cache中缓存的block减少一个引用计数;cache执行insert函数时,会给所有的LRUHandle的引用计数设成2,其中1用于LRUCache自身,在执行cache的Release操作时被Unref,从而真正释放。

     3.table cache的读取逻辑,代码片段如下:

     leveldb-1.7.0/db/table_cache.cc 

技术分享
  1. 45 Status TableCache::FindTable(uint64_t file_number, uint64_t file_size,
  2. 46 Cache::Handle** handle) {
  3. 47 Status s;
  4. 48 char buf[sizeof(file_number)];
  5. 49 EncodeFixed64(buf, file_number);
  6. 50 Slice key(buf, sizeof(buf));
  7. 51 *handle = cache_->Lookup(key);
  8. 52 if (*handle == NULL) {
  9. 53 std::string fname = TableFileName(dbname_, file_number);
  10. 54 RandomAccessFile* file = NULL;
  11. 55 Table* table = NULL;
  12. 56 s = env_->NewRandomAccessFile(fname, &file);
  13. 57 if (s.ok()) {
  14. 58 s = Table::Open(*options_, file, file_size, &table);
  15. 59 }
  16. 60
  17. 61 if (!s.ok()) {
  18. 62 assert(table == NULL);
  19. 63 delete file;
  20. 64 // We do not cache error results so that if the error is transient,
  21. 65 // or somebody repairs the file, we recover automatically.
  22. 66 } else {
  23. 67 TableAndFile* tf = new TableAndFile;
  24. 68 tf->file = file;
  25. 69 tf->table = table;
  26. 70 *handle = cache_->Insert(key, tf, 1, &DeleteEntry);
  27. 71 }
  28. 72 }
  29. 73 return s;
  30. 74 }
技术分享

      和block cache类似,首先查找cache,如果找不到,直接从硬盘中读取。注意代码70行Insert函数的第3个参数,1表示每个sstable的索引信息在cache总占用1个单位的capacity_。其他内容不再赘述。

  • 总结

     LevelDB是Jeff Dean, Sanjay Ghemawat的作品,实在是值得大家仔细品读。Cache机制非常简单,相信大家通过这篇文章能够非常清楚的了解其cache实现,其思路其实和文件系统的cache是一样的。另外,淘宝已经在Tair线上环境中大量使用了LevelDB存储引擎,推荐那岩写的《LevelDB实现解析》,35页的文档,结合着读代码,会对理解代码,有非常大的帮助。

LevelDB Cache实现机制分析

标签:

人气教程排行