大白话解析LevelDB:TableCache

TableCacheLevelDB中的作用是管理和缓存SST(Sorted String Tables)的读取。

为了提高读取效率,TableCache会缓存已打开的SST。这样,对同一SST的多次读取操作就不需要每次都打开文件。

我们来看下TableCache里都有哪些接口:

class TableCache {
   public:
    // 构造时接受一个 entries 参数,用于指定最大的缓存 SST 数量。当缓存的 SST 数量超过
    // 这个限制时,TableCache 会根据某种策略(如最近最少使用,LRU)从 Cache 里移除一些
    / SST。
    TableCache(const std::string& dbname, const Options& options, int entries);
    ~TableCache();

    // 返回一个指定 SST 的迭代器,用于遍历 SST 中的键值对。
    Iterator* NewIterator(const ReadOptions& options, uint64_t file_number, uint64_t file_size,
                          Table** tableptr = nullptr);

    // 从指定 SST 中查找某个 Key。如果这个 Key 找到了,则调用 handle_result 函数。
    Status Get(const ReadOptions& options, uint64_t file_number, uint64_t file_size, const Slice& k,
               void* arg, void (*handle_result)(void*, const Slice&, const Slice&));

    // 将某个 SST 从 TableCache 中移除。
    void Evict(uint64_t file_number);
};

TableCache 的构造函数

TableCache::TableCache(const std::string& dbname, const Options& options, int entries)
    : env_(options.env), dbname_(dbname), options_(options), cache_(NewLRUCache(entries)) {}

TableCache的构造函数里主要是cache_的初始化,构造一个LRUCache

TableCache其实是一个包装类,核心是cache_TableCache的所有接口都是对cache_的封装,方便使用。

NewLRUCache(entries)是个典型的工厂模式,用于创建一个LRUCache对象:

Cache* NewLRUCache(size_t capacity) { return new ShardedLRUCache(capacity); }

LRUCache的实现可移步参考大白话解析LevelDB:ShardedLRUCache

使用工厂模式的好处是替换方便,如果我们想要替换成其他类型的LRUCache,比如SingleLRUCache,只需要修改NewLRUCache函数即可,而不需要每一处构造LRUCache的上层代码。

TableCache::Get

TableCache::Get用于从Cache中查找指定的SST,再从这个SST中查找指定的Key

如果SST不在Cache中,TableCache会打开这个SST,并将其添加到Cache中。

Status TableCache::Get(const ReadOptions& options, uint64_t file_number, uint64_t file_size,
                       const Slice& k, void* arg,
                       void (*handle_result)(void*, const Slice&, const Slice&)) {
    
    // 在 Cache 中找到指定的 SST。
    // 如果目标 SST 不在缓存中,它会打开文件并将其添加到 Cache。
    // handle 指向 Cache 中的 SST Item。
    Cache::Handle* handle = nullptr;
    Status s = FindTable(file_number, file_size, &handle);
    if (s.ok()) {
        // 通过 handle 在 cache 中获取 SST 对应的 Table 对象。
        Table* t = reinterpret_cast<TableAndFile*>(cache_->Value(handle))->table;
        // 调用 Table::InternalGet() 方法从 SST 中查找指定的 key。
        s = t->InternalGet(options, k, arg, handle_result);
        cache_->Release(handle);
    }
    return s;
}

在 Cache 中查找指定的 SST

TableCache::Get的核心是FindTable函数,它用于在Cache中查找指定的SST

先尝试在cache_中查找指定的SST,如果找到了,就直接返回handle

如果没找到,就打开这个SST,并将其添加到cache_中,然后再返handle

Status TableCache::FindTable(uint64_t file_number, uint64_t file_size, Cache::Handle** handle) {
    Status s;

    // 将 file_number 编码为 fixed64,作为 key 到
    // cache_ 中查找 handle。
    char buf[sizeof(file_number)];
    EncodeFixed64(buf, file_number);
    Slice key(buf, sizeof(buf));
    *handle = cache_->Lookup(key);

    // 如果 cache_ 中木有找到,就打开该 SST 文件,并将其添加到 cache_ 中。
    if (*handle == nullptr) {
        // 根据 file_number 构造出 SST 的文件名。
        // 早期版本的 LevelDB 使用的是 .sst 后缀,后来改为了 .ldb。
        // 为了兼容这两种命名方式,这里会尝试两种后缀。
        // TableFileName() 会构建 .ldb 后缀的 SST 文件名,
        // SSTTableFileName() 会构建 .sst 后缀的 SST 文件名。
        std::string fname = TableFileName(dbname_, file_number);
        RandomAccessFile* file = nullptr;
        Table* table = nullptr;
        s = env_->NewRandomAccessFile(fname, &file);
        if (!s.ok()) {
            std::string old_fname = SSTTableFileName(dbname_, file_number);
            if (env_->NewRandomAccessFile(old_fname, &file).ok()) {
                s = Status::OK();
            }
        }

        // SST 文件打开后,通过 Table::Open 创建一个 Table 对象。
        if (s.ok()) {
            s = Table::Open(options_, file, file_size, &table);
        }

        if (!s.ok()) {
            // 如果创建 Table 对象失败,就关闭 SST 文件的句柄。
            assert(table == nullptr);
            delete file;
        } else {
            // Table 对象创建成功,将其添加到 cache_ 中。
            TableAndFile* tf = new TableAndFile;
            tf->file = file;
            tf->table = table;
            *handle = cache_->Insert(key, tf, 1, &DeleteEntry);
        }
    }
    return s;
}

TableCache::FindTable里的核心操作是cache_->Lookupcache_->Insert

其实现细节可移步参考 cache_->Lookup 的实现 cache_->Insert 的实现

env_->NewRandomAccessFile(fname, &file)的实现细节可移步参考大白话解析LevelDB: Env

Table::Open(options_, file, file_size, &table)的实现细节可移步参考大白话解析LevelDB: Table

从 SST 中查找指定的 Key

找到SST后就好说了,从SST中查找指定的Key的逻辑甩给Table::InternalGet函数就行了。

t->InternalGet(options, k, arg, handle_result)的实现细节可移步参考大白话解析LevelDB: Table

TableCache::NewIterator

TableCache::NewIteratorTableCache::Get类似,先在Cache中查找指定的SST,再把NewIterator的逻辑甩给Table::NewIterator函数。

Iterator* TableCache::NewIterator(const ReadOptions& options, uint64_t file_number,
                                  uint64_t file_size, Table** tableptr) {
    if (tableptr != nullptr) {
        *tableptr = nullptr;
    }

    // 在 Cache 中找到指定的 SST。
    // 如果目标 SST 不在缓存中,它会打开文件并将其添加到 Cache。
    // handle 指向 Cache 中的 SST Item。
    Cache::Handle* handle = nullptr;
    Status s = FindTable(file_number, file_size, &handle);
    if (!s.ok()) {
        return NewErrorIterator(s);
    }

    // 通过 handle 在 cache 中获取 SST 对应的 Table 对象。
    Table* table = reinterpret_cast<TableAndFile*>(cache_->Value(handle))->table;
    // 调用 Table::NewIterator() 方法创建该 SST 的 Iterator。
    Iterator* result = table->NewIterator(options);
    result->RegisterCleanup(&UnrefEntry, cache_, handle);
    if (tableptr != nullptr) {
        *tableptr = table;
    }
    return result;
}

table->NewIterator(options)的实现细节可参考大白话解析LevelDB: Table

TableCache::Evict

file_number包装成一个cache_能识别的Key,再调用cache_->Erase函数,将这个Keycache_中移除。

void TableCache::Evict(uint64_t file_number) {
    // 将 file_number 编码为 fixed64,
    // 作为 cache_ 中的 key,将该 key 从
    // cache_ 中移除。
    char buf[sizeof(file_number)];
    EncodeFixed64(buf, file_number);
    cache_->Erase(Slice(buf, sizeof(buf)));
}

cache_->Erase(Slice(buf, sizeof(buf)))的实现细节可移步参考大白话解析LevelDB:ShardedLRUCache

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值