Impala3.4源码阅读笔记(一)data-cache功能

news2024/11/24 22:55:10

前言

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

基本信息

data-cache是impala在本地的数据缓存,采用LRU策略存储频繁使用的表数据,避免每次使用都要从HDFS再次读取,从而加快数据读取速度。data-cache可以包括多个缓存分区,分区大小配额和存放路径通过参数配置,可参考Data Cache for Remote Reads。

模块结构

data-cache功能由DataCache类实现,这个类被定义在be\src\runtime\io\data-cache.h中。DiskIoMgr类是一个管理器类,负责为所有磁盘和远程文件系统上的所有查询进行IO调度,DiskIoMgr对象是DataCache对象唯一的管理者。HdfsFileReader类则是DataCache对象的唯一实际调用者,HdfsFileReader通过DiskIoMgr获取到DataCache对象的指针并使用。

读取HDFS数据由HdfsFileReader::ReadFromPos函数负责,ReadFromPos会尝试从data-cache中读取数据,缓存MISS的数据部分再去HDFS中读取,全部读取结束后若有缓存MISS发生ReadFromPos会尝试将本次读取的数据写入data-cache。

data-cache通过Partition类管理缓存分区,一个Partition对象主要包括一份缓存元数据和若干缓存文件。当向一个Partition中插入新数据时,数据将被追加到该Partition当前使用的备份文件中。每条缓存数据的存储消耗计入该分区的总大小,当一个分区大小达到其配额时,该分区中最近最少使用的数据将被逐出(LRU策略)。

一个缓存文件由一个CacheFile对象负责管理,包括文件的创建删除、读写和PunchHole操作。缓存文件名格式为“impala-cache-file-” + UUID,缓存文件大小受到data_cache_file_max_size_bytes参数限制,默认1TB,插入数据时若超过该限制时会创建一个新的缓存文件来插入。数据逐出则是通过PunchHole来实现的,PunchHole按照给定偏移量和数据长度在文件中打洞,文件中的所有洞都不消耗存储空间,具体可以参考用fallocate进行"文件预留"或"文件打洞"。

每个分区都有一份缓存元数据meta_cache_,其类型为ShardedCache类的独占指针,底层使用的HandleTable类实现了一个开链哈希表,以Handle为中介记录缓存键CacheKey到缓存条目CacheEntry的映射。

data-cache通过CacheKey来唯一标识一条缓存数据,DataCache也通过CacheKey的Hash值来决定该条目插入哪个缓存分区。CacheKey包括filename(数据对应的HDFS文件完整路径),mtime(该文件的修改时间)和offset(数据在该文件的起始偏移量)三部分,这三部分表明了数据来自哪个版本的哪个文件的哪个部分,只有这三部分完全匹配才能保证缓存数据与HDFS文件数据一致。CacheKey仅包括offset而不包括数据长度使得data-cache目前有一个缺陷,就是会数据重复缓存且不支持子范围查找。举个例子,假如data-cache缓存了某文件[10,50]的数据,也就是文件第10~50个字节,其缓存键的offset=10。此时若要查找[20,30]的数据时则会MISS,即使[10,50]包含了[20,30],这是因为没有offset=20的缓存条目,同理插入[20,30]的数据也不会与[10,50]合并,它们的offset不同所以CacheKey不同,不被认为是同一条目。

CacheKey一一对应的是缓存条目CacheEntry类,CacheEntry并不保存缓存数据,而是保存数据所在的CacheFile对象指针、数据在缓存文件中的偏移量、数据长度和校验和。通过CacheEntry对象获取缓存文件指针等参数才能去缓存文件中读取缓存数据。

此外data-cache中还有TracerDataCacheTest两个类,分别负责记录data-cache工作情况到日志和data-cache的测试。

工作流程

我们从HdfsFileReader::ReadFromPos开始分析data-cache的工作流程,这个函数会被ScanRange对象调用去读取HDFS文件,下面贴出了部分关键代码和注释说明:

Status HdfsFileReader::ReadFromPos(...) {
    ...
    // 首先,尝试从datacache读取数据
    // 从DiskIoMgr拿到datacache对象指针,并进行检查是否可用
    DataCache* remote_data_cache = io_mgr->remote_data_cache();
    bool try_data_cache = scan_range_->UseDataCache() && remote_data_cache != nullptr;
    int64_t cached_read = 0;
    if (try_data_cache) {
      // ReadDataCache函数调用DataCache的API读取缓存数据到buffer,然后返回读取的字节数
      cached_read = ReadDataCache(remote_data_cache, file_offset, buffer, bytes_to_read);
      DCHECK_GE(cached_read, 0);
      *bytes_read = cached_read;
    }
    // *bytes_read为已经读取的字节数,bytes_to_read为需要读取的字节数
    // 通过循环去hdfs读取数据直到读取完所有需要的数据
    while (*bytes_read < bytes_to_read) {
      int bytes_remaining = bytes_to_read - *bytes_read;
      ...
      int64_t position_in_file = file_offset + *bytes_read;
      // ReadFromPosInternal从hdfs读取数据到buffer
      status = ReadFromPosInternal(hdfs_file, queue, position_in_file,
          buffer + *bytes_read, bytes_remaining, ¤t_bytes_read);
      ...
      *bytes_read += current_bytes_read;
    }
    // 计算缓存miss的字节数,尝试将miss的数据写入缓存
    int64_t cached_bytes_missed = *bytes_read - cached_read;
    if (try_data_cache && status.ok() && cached_bytes_missed > 0) {
      DCHECK_LE(*bytes_read, bytes_to_read);
      // WriteDataCache函数调用DataCache的API尝试将buffer数据写入缓存
      WriteDataCache(remote_data_cache, file_offset, buffer, *bytes_read,
          cached_bytes_missed);
    }
  }
  return status;
}

可以发现HdfsFileReader::ReadFromPos通过ReadDataCacheWriteDataCache来读写data-cache,这两个函数比较简单,下面贴出了两个函数的代码:

int64_t HdfsFileReader::ReadDataCache(DataCache* remote_data_cache, int64_t file_offset,
    uint8_t* buffer, int64_t bytes_to_read) {
  // 直接调用datacache的Lookup函数读取数据到buffer,并返回读取字节数
  int64_t cached_read = remote_data_cache->Lookup(*scan_range_->file_string(),
      scan_range_->mtime(), file_offset, bytes_to_read, buffer);
  // 下面都是一些与功能无关的metric更新
  ...
  return cached_read;
}
// WriteDataCache没有返回值是因为缓存插入并非一定成功的,多线程并发写、数据条目过大都可能导致失败
void HdfsFileReader::WriteDataCache(DataCache* remote_data_cache, int64_t file_offset,
    const uint8_t* buffer, int64_t buffer_len, int64_t bytes_missed) {
  // 直接调用datacache的Store函数将buffer数据写入到缓存
  remote_data_cache->Store(*scan_range_->file_string(), scan_range_->mtime(),
      file_offset, buffer, buffer_len);
  // 下面都是一些与功能无关的metric更新
  ...
}

接下来我对DataCache的两个关键读写函数进行分析,首先是负责查找并读取缓存的Lookup及其相关函数:

int64_t DataCache::Lookup(const string& filename, int64_t mtime, int64_t offset,
    int64_t bytes_to_read, uint8_t* buffer) {
  ...
  // 构造一个缓存键,并计算其哈希值以确定其对应条目所在的分区索引。
  const CacheKey key(filename, mtime, offset);
  int idx = key.Hash() % partitions_.size();
  // 转而调用对应分区的Lookup函数,完成数据读取
  int64_t bytes_read = partitions_[idx]->Lookup(key, bytes_to_read, buffer);
  ...
  return bytes_read;
}

int64_t DataCache::Partition::Lookup(const CacheKey& cache_key, int64_t bytes_to_read,
    uint8_t* buffer) {
  // 从缓存元数据meta_cache_中拿到CacheKey对应的Handle
  Slice key = cache_key.ToSlice();
  Cache::UniqueHandle handle(meta_cache_->Lookup(key, Cache::EXPECT_IN_CACHE));
  // Handle为空说明缓存MISS了,记录到追踪文件(如果开启了对应功能)并退出
  if (handle.get() == nullptr) {
    if (tracer_ != nullptr) {
      tracer_->Trace(Tracer::MISS, cache_key, bytes_to_read, /*entry_len=*/-1);
    }
    return 0;
  }
  // 将Handle转换为CacheEntry
  CacheEntry entry(meta_cache_->Value(handle));
  if (tracer_ != nullptr) {
    tracer_->Trace(Tracer::HIT, cache_key, bytes_to_read, entry.len());
  }
  // 从CacheEntry拿到数据所在的CacheFile对象指针
  CacheFile* cache_file = entry.file();
  bytes_to_read = min(entry.len(), bytes_to_read);
  ...
  // 调用CacheFile的Read读取缓存文件数据,如果失败会删除该条目并退出
  if (UNLIKELY(!cache_file->Read(entry.offset(), buffer, bytes_to_read))) {
    meta_cache_->Erase(key);
    return 0;
  }
  // 验证校验和是否启用,若启用会检查校验和并删除校验和不匹配的条目并退出
  if (FLAGS_data_cache_checksum && bytes_to_read == entry.len() &&
      !VerifyChecksum("read", entry, buffer, bytes_to_read)) {
    meta_cache_->Erase(key);
    return 0;
  }
  return bytes_to_read;
}

Lookup过程的示意图如下所示(示意缓存HIT的情况并省略了缓存分区等细节):
在这里插入图片描述

然后是负责写入缓存的Store及其相关函数:

bool DataCache::Store(const string& filename, int64_t mtime, int64_t offset,
    const uint8_t* buffer, int64_t buffer_len) {
  ...
  // 构造一个缓存键,同样计算其哈希值以确定其对应条目需要放入的分区索引。
  const CacheKey key(filename, mtime, offset);
  int idx = key.Hash() % partitions_.size();
  bool start_reclaim;
  // 转而调用对应分区的Store函数,完成数据读取
  bool stored = partitions_[idx]->Store(key, buffer, buffer_len, &start_reclaim);
  ...
  return stored;
}
bool DataCache::Partition::Store(const CacheKey& cache_key, const uint8_t* buffer,
    int64_t buffer_len, bool* start_reclaim) {
  ...
  // 检查是否已经缓存了该条目,只有该条目未被缓存或比原有缓存条目更大才继续缓存
  {
    Cache::UniqueHandle handle(meta_cache_->Lookup(key, Cache::EXPECT_IN_CACHE));
    if (handle.get() != nullptr) {
      if (HandleExistingEntry(key, handle, buffer, buffer_len)) return false;
    }
  }
  // 然后是一些写缓存文件的准备工作
  ...
  // 执行InsertIntoCache函数开始写入缓存
  return InsertIntoCache(key, cache_file, insertion_offset, buffer, buffer_len);
}
bool DataCache::Partition::InsertIntoCache(const Slice& key, CacheFile* cache_file,
    int64_t insertion_offset, const uint8_t* buffer, int64_t buffer_len) {
  ...
  // 分配缓存handle
  Cache::UniquePendingHandle pending_handle(
      meta_cache_->Allocate(key, sizeof(CacheEntry), charge_len));
  if (UNLIKELY(pending_handle.get() == nullptr)) return false;
  // 计算校验和(如果开启了对应功能)
  int64_t checksum = FLAGS_data_cache_checksum ? Checksum(buffer, buffer_len) : 0;
  // 调用CacheFile的Write将数据写入缓存文件
  if (UNLIKELY(!cache_file->Write(insertion_offset, buffer, buffer_len))) {
    return false;
  }
  // 构造对应的缓存条目并转换为handle
  CacheEntry entry(cache_file, insertion_offset, buffer_len, checksum);
  memcpy(meta_cache_->MutableValue(&pending_handle), &entry, sizeof(CacheEntry));
  // Insert按照LRU策略将handle插入缓存元数据meta_cache_
  Cache::UniqueHandle handle(meta_cache_->Insert(std::move(pending_handle), this));
  ...
  return true;
}

Store过程的示意图如下所示(示意缓存条目不存在的情况并省略了缓存分区、多线程并发等细节):
在这里插入图片描述

至此,data-cache的工作流程就分析完了。

高级配置项

在这里插入图片描述

在impalad.conf中配置-data_cache_enable_tracing=true可以开启data-cache追踪功能,开启后data-cache的工作情况会被记录在data-cache目录的impala-cache-trace.txt中:

在这里插入图片描述
通过阅读源代码,我们可以知道其中各项的含义:

  1. ts为timestamp,时间戳;
  2. s为status,cache状态,可能的值有H(命中)、M(未命中)、S(存储成功)和F(存储失败)四种;
  3. f为filename,请求文件的完整HDFS路径;
  4. m为mtime,请求文件的修改时间;
  5. o为offset,请求区域在文件中的起始偏移量;
  6. lLen为lookup_len,查找长度,也就是期望从cache读取的字节数;
  7. eLen为entry_len,缓存数据长度,也就是实际读取或写入缓存的字节数;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/719242.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决github无法打开问题

第一步&#xff0c;去如下目录复制hosts文件副本到桌面【切记&#xff0c;要复制到其他文件夹下&#xff0c;不要直接改原文件】。 C:\Windows\System32\drivers\etc 第二步&#xff0c;以文本文档形式打开复制的副本文件&#xff0c;添加如下语句保存后【替换】掉原有hosts文件…

Ubuntu 16.04 安装Arduino ESP32开发环境记录

文章目录 安装arduino开发环境&#xff1a;安装ESP32开发环境编译上传 安装arduino开发环境&#xff1a; Arduino IDE 2.x好像对于ubuntu16.04不太支持&#xff0c;尝试了一下执行不了。这里 我们可以下载早期的1.8.x版本。 根据自己的电脑类型在红框中选择对应的版本进行下载…

CHI 控制信号说明

&#xff08;部分描述采用了他人的文章&#xff0c;待后续补充出处&#xff0c;此处为草稿&#xff09; Address PA/VA 位宽之间的对应关系&#xff1b; Non-secure bit 该bit指示了secure和non-secure空间&#xff0c;对于snoopable的trans, 即使地址相同&#xff0c;secure和…

阿里首次公布 Java10W 字面试复盘笔记,面面俱到、太全了

Java 面试 “金三银四&#xff0c;金九银十”这个字眼对于程序员应该是再熟悉不过的了&#xff0c;每年的金三银、金九银十都会有很多程序员找工作、跳槽等一系列的安排。说实话&#xff0c;面试中 7 分靠能力&#xff0c;3 分靠技能&#xff1b;在刚开始的时候介绍项目都是技…

代码行数统计插件(Intellij IDEA 代码统计插件 Statistic 详细使用教程)

代码行数统计插件&#xff08;Intellij IDEA 代码统计插件 Statistic 详细使用教程&#xff09; 在项目的开发过程中&#xff0c;你有没有遇到以下的一些场景&#xff1a; 想统计一下整个项目的代码量有多少&#xff0c;比如有多少源代码文件&#xff0c;总体有多少行代码&…

多元分类预测 | Matlab基于灰狼优化深度置信网络(GWO-DBN)的分类预测,多特征输入模型,GWO-DBN分类预测

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元分类预测 | Matlab基于灰狼优化深度置信网络(GWO-DBN)的分类预测,多特征输入模型,GWO-DBN分类预测 多特征输入单输出的二分类及多分类模型。程序内注释详细,直接替换数据就可以用。程序语言为matlab,程序可…

基于深度强化学习的人岗匹配算法研究

一.需求分析 面向HR的人岗匹配功能&#xff0c;帮助HR高效挑选简历。模型能够根据给出的不同岗位需求&#xff0c;在简历库中挑选出与岗位需求最匹配的几个简历推荐给HR。岗位的常见需求包括&#xff1a;年龄、学历、工作年限三方面。简历也具有以下几个特征&#xff1a;应聘人…

第一章 SSD综述

SSD&#xff08;Solid State Drive&#xff09;&#xff0c;即固态硬盘&#xff0c;以半导体存储数据&#xff0c;用纯电子电路实现&#xff0c;没有任何机械设备。 HDD&#xff08;Hard DiskDrive&#xff09;&#xff0c;即传统机械硬盘。 一、SSD与HDD 1.1 两者的异同 1…

50从零开始学Java之万类之王Object是怎么回事?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在前面的文章中&#xff0c;壹哥跟大家说过&#xff0c;Java是面向对象的编程语言&#xff0c;而在面…

Ymodem协议应知应会

1.优势 在嵌入式环境中&#xff0c;一旦需要和设备之间通过某种协议传输文件&#xff0c;Ymodem协议因为具备如下特征&#xff1a; 基本的流控基本的握手支持多文件传输支持校验协议精简&#xff0c;代码量少用众多既有客户端软件可以供测试&#xff0c;免写上位机程序。 因…

C++11:右值引用

文章目录 1. C左值和右值2. C右值引用 右值引用是一种新的 C 语法&#xff0c;基于右值引用引申出了 2 种 C 编程技巧&#xff0c;分别为移动语义和完美转发。本文主要介绍什么是右值引用以及它的基本用法。 1. C左值和右值 右值引用可以从字面意思上理解&#xff0c;指的是以…

【springboot+云计算】B/S医院信息管理系统源码(云HIS)

一、基于云计算技术的B/S架构的医院管理系统(简称云HIS) 采用前后端分离架构&#xff0c;前端由Angular框架、JavaScript语言开发&#xff1b;后端使用Java语言开发。系统遵循服务化、模块化原则开发&#xff0c;具有强大的可扩展性&#xff0c;二次开发方便快捷。为医疗机构提…

2023最新ChatGPT商业运营网站源码+支持ChatGPT4.0+新增GPT联网功能+支持ai绘画+实时语音识别输入+用户会员套餐+免费更新版本

2023最新ChatGPT商业运营网站源码支持ChatGPT4.0新增GPT联网功能支持ai绘画实时语音识别输入用户会员套餐免费更新版本 一、AI创作系统二、系统程序下载三、系统介绍四、安装教程五、主要功能展示六、更新日志 一、AI创作系统 提问&#xff1a;程序已经支持GPT3.5、GPT4.0接口…

Python之全-新-深-细详解

文章目录 第一章 Python环境搭建1.1. 计算机基础1.1.1. 什么是编程1.1.2. 什么是进制1.1.2.1. 进制的简介1.1.2.2. 进制的分类1.1.2.3. 进制的表示1.1.2.4. 进制的转换1.1.2.5. 原反补(了解)数据的转换负数的表示补码的引入 1.2. Python的介绍1.3. Python的安装与使用1.3.1. Py…

SSM简单项目遇到的几个问题

这几个问题&#xff0c;干扰了我很长时间。 主要因为书本的例子&#xff0c;是通过controller层返回到jsp层。但是&#xff0c;最后一个SSM项目&#xff0c;它用的是controller返回信息给Service层&#xff0c;再由Service层返回Jsp层。 实训&#xff1a;编写一个模糊查询姓名…

前端(三)——MVC与MVVM模式的battle

&#x1f604;博主&#xff1a;小猫娃来啦 &#x1f604;文章核心&#xff1a;mvc模式mvvm模式的battle 文章目录 mvc模式是什么mvc模式的优缺点优化mvc模式致命的缺点mvvm是什么&#xff0c;和mvc有什么关系&#xff1f;细看mvvm和mvc的不同 mvvm的缺点简化视图层开发 mvc模式…

E类逆变器Ltspice仿真

1 参数计算&#xff08;待续&#xff09; &#xff08;1&#xff09;确定振荡频率&#xff1a; &#xff08;2&#xff09;计算各器件参数&#xff1b; 2 电路仿真 &#xff08;1&#xff09;电路图 &#xff08;2&#xff09;电路分析 3 结果 &#xff08;1&#xff09;…

vue项目业务实现,视频监控-文件流,大屏适配方案(v-scale-screen),websocket前端

最近把以前的业务场景及解决方案整理了一下&#xff0c;具体实现的工具如下&#xff1a; 监控-视频文件流>video.js videojs-contrib-hls 大屏适配方案> v-scale-screen websocket>sockjs-client webstomp-client 视频监控-文件流 使用方法 下载video插件&#xf…

数字IC基础:状态化简与等价状态

相关阅读 数字IC基础知识&#xff1a;著名EDA公司与工具介绍 如果时序机的两个状态对于所有可能的输入序列都具有相同的输出序列&#xff08;和相同的下一状态&#xff09;&#xff0c;则称这两个状态是等价的。时序机的等价状态无法通过观察输出序列的异同对其加以区分&#…

【Web3】认识以太坊钱包

目录 区块链钱包概念 密码 私钥 Private Key 公钥Public Key Keystore 助记词 Mnemonic 如何解锁账户 区块链钱包概念 钱包用来存钱的&#xff0c;在区块链中&#xff0c;我们的数字资产都会对应到一个账户地址上&#xff0c; 只有拥 有账户的钥匙&#xff08;私钥&…