Impala3.4源码阅读笔记(二)data-cache的Lookup实现

本文详细介绍了ApacheImpala中Lookup过程,包括从缓存元数据中查找条目,以及根据缓存条目读取数据的步骤。涉及DataCache、ShardedCache、RLCacheShard等组件,以及文件读取的实现,强调了缓存的LRU策略和内存管理。
摘要由CSDN通过智能技术生成

前言

本文为笔者个人阅读Apache Impala源码时的笔记,仅代表我个人对代码的理解,个人水平有限,文章可能存在理解错误、遗漏或者过时之处。如果有任何错误或者有更好的见解,欢迎指正。

正文

本文介绍Lookup的具体流程和细节,如果对data-cache的工作流程还不了解,建议先阅读完Impala3.4源码阅读笔记(一)data-cache功能后再继续。
Lookup的关键步骤有二,一是根据缓存键去缓存元数据中查找缓存条目,二是根据缓存条目去缓存文件读取数据,还是那张图:
在这里插入图片描述

1. 缓存元数据

我们首先来看第一步查找缓存元数据的实现:

DataCache::Partition::Lookup中可以看到调用缓存元数据的语句如下:

Slice key = cache_key.ToSlice();
Cache::UniqueHandle handle(meta_cache_->Lookup(key, Cache::EXPECT_IN_CACHE));

其中Slice是Kudu提供的一个小工具,此处可以简单地把它理解为一个字节数组。UniqueHandle是一个包装不透明句柄的指针结构,此处可以简单地把它理解为一个handle指针。其中meta_cache_就是缓存元数据,是Partition类的私有成员,其定义为:

std::unique_ptr<Cache> meta_cache_;

Cache类是一个抽象类,定义了缓存的各种接口,我们直接看meta_cache_的具体类型,在Partition的构造函数初始化列表可以看到meta_cache_通过NewCache初始化:

meta_cache_(NewCache<Cache::EvictionPolicy::LRU, Cache::MemoryType::DRAM>(capacity_, path_))

这是一个NewCache的特化模板,从中可以看见meta_cache_实际类型为ShardedCache,其实现了缓存分片的管理功能,其内部包括了一组缓存分片,缓存分片完成缓存的具体功能:

template<>
Cache* NewCache<Cache::EvictionPolicy::LRU, Cache::MemoryType::DRAM>
    (size_t capacity, const std::string& id) {
  return new ShardedCache<Cache::EvictionPolicy::LRU>(capacity, id);
}

继续沿着Lookup的调用路径深入,meta_cache_->Lookup实际调用了ShardedCache::Lookup

UniqueHandle Lookup(const Slice& key, CacheBehavior caching) override {
    const uint32_t hash = HashSlice(key);
    HandleBase* h = shards_[Shard(hash)]->Lookup(key, hash, caching == EXPECT_IN_CACHE);
    return UniqueHandle(reinterpret_cast<Cache::Handle*>(h), Cache::HandleDeleter(this));
}

可以发现其根据Key的哈希值调用了某缓存分片的Lookup,我们继续看缓存分片的定义:

for (int s = 0; s < num_shards; s++) {
	unique_ptr<CacheShard> shard(NewCacheShard<policy>(mem_tracker_.get()));
	shard->SetCapacity(per_shard);
	shards_.push_back(shard.release());
}

其中NewCacheShard函数构造了具体的分片类型为RLCacheShard

template<Cache::EvictionPolicy policy>
CacheShard* NewCacheShard(kudu::MemTracker* mem_tracker) {
  return new RLCacheShard<policy>(mem_tracker);
}

继续沿着Lookup的调用路径深入,ShardedCache::Lookup又调用了RLCacheShard::Lookup,在RLCacheShard::Lookup中我们可以发现又调用了table_.Lookup

e = static_cast<RLHandle*>(table_.Lookup(key, hash));

table_RLCacheShard的私有成员,类型为HandleTable,这是Impala自己实现的轻量级开链哈希表,继续分析其Lookup方法:

HandleBase* Lookup(const Slice& key, uint32_t hash) {
	return *FindPointer(key, hash);
}

FindPointer之后就是开链哈希表的具体实现逻辑了,非本文重点,此处不再展开。接下来我们分析返回值HandleBase*HandleBase可以理解为哈希表的键值对,每个HandleBase以字节数组的形式保存了一对(CacheKey,CacheEntry)HandleBase*RLCacheShard::Lookup中被转换为RLHandle*类型,RLHandleHandleBase的派生类,其额外增加了引用计数和实现双链表的一些成员。获取到RLHandle后,我们返回到最外层的调用处DataCache::Partition::Lookup,根据返回的RLHandle构建CacheEntry

CacheEntry entry(meta_cache_->Value(handle));

其中meta_cache_->Value间接调用了HandleBase::value,其会以Slice的形式返回其保存的键值对的值,然后依据该Slice来构造CacheEntry。至此,我们就根据CacheKey获取到了对应的CacheEntry

2. 缓存文件

紧接上一步获取的CacheEntry,我们继续看第二步缓存文件读取的实现。

CacheEntry包括了缓存数据所在缓存文件CacheFile的指针、缓存数据在文件中的偏移量offset和缓存长度len,然后就可以调用CacheFile::Read方法读取缓存文件中的缓存数据,数据会被读进buffer,一个上层调用者传进来的字节数组地址:

 CacheFile* cache_file = entry.file();
 bytes_to_read = min(entry.len(), bytes_to_read);
 if (UNLIKELY(!cache_file->Read(entry.offset(), buffer, bytes_to_read))) {
     meta_cache_->Erase(key);
     return 0;
 }

如果有读取失败的情况则会通过meta_cache_->Erase(key)删除无效的缓存数据。我们继续看CacheFile::Read的实现,其核心读取部分为:

kudu::Status status = file_->Read(offset, Slice(buffer, bytes_to_read));

file_CacheFile的私有成员,其定义如下:

unique_ptr<RWFile> file_;

其中RWFile是一个抽象类,定义了可读写文件的接口,我们直接看file_的具体类型,CacheFile的构造函数是私有的,只允许通过其静态方法CacheFile::Create来创建CacheFile对象,在其中我们可以看见的file_初始化:

KUDU_RETURN_IF_ERROR(kudu::Env::Default()->NewRWFile(path, &cache_file->file_), "Failed to create cache file");

其调用了Env::NewRWFile来创建文件,Env也是一个抽象类,定义了一些环境接口,Default()返回一个适合当前操作系统的默认环境,实际上是Env的一个派生类PosixEnv

static Env* default_env;
static void InitDefaultEnv() { default_env = new PosixEnv; }
Env* Env::Default() {
    pthread_once(&once, InitDefaultEnv);
    return default_env;
}

继续看PosixEnvNewRWFile方法:

result->reset(new PosixRWFile(fname, fd, opts.sync_on_close));

可以看出file_的实际类型为PosixRWFile,我们继续看其Read方法:

virtual Status Read(uint64_t offset, Slice result) const OVERRIDE {
    return DoReadV(fd_, filename_, offset, ArrayView<Slice>(&result, 1));
}

此时最初的buffer已经被包装进了一个Slice对象result,可以看见其调用了DoReadV实现文件读取,这是一个比较复杂的函数,最终调用了系统API的pread函数实现文件读取,此处也不再展开。DoReadV执行完,数据被读入buffer之后Lookup的过程也就结束了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值