本文是关于如何理解Impala元数据的。边肖觉得很实用,所以分享给大家学习。希望你看完这篇文章能有所收获。让我们和边肖一起看看。
背景
Impala是一款高性能的OLAP查询引擎。与其他SQL-on-Hadoop的ROLAP解决方案如Presto、SparkSQL不同,Impala缓存元数据(Metadata/Catalog),因此在进行查询计划生成时不再依赖外部系统(如Hive、HDFS、Kudu),可以实现毫秒级的生成时间。此外,缓存元数据还可以大大降低底层系统主节点(Hive Metastore、HDFS NameNode、Kudu Master)的负载。
然而事物总是有两面性,元数据缓存给Impala的系统设计带来了极大的复杂性。一方面在功能上,为了维护缓存的正确性,引入了两个Impala特有的SQL语句:Invalidate Metadata和Refresh,此外还引入了查询选项SYNC_DDL。这些都使得用户参与到缓存的维护中。另一方面,在架构设计中,有许多与元数据相关的复杂设计,如元数据的增量传播、缓存一致性的维护等。
当数据仓库规模较大时,Impala的元数据设计暴露出很多问题,比如大表的元数据加载和刷新时间极长,元数据的广播会被DDL阻塞,导致广播延迟较大,元数据缓存会导致节点Full GC或OOM等。因此,Impala的元数据设计一直在演进,最新进展主要集中在Fetch-on-demand协调器的设计上(也称为本地目录模式、catalog-v2等)。).
Impala Server简介
Impala集群由一个目录服务器(Catalogd)、一个状态存储服务器(Statestored)和几个Impala守护进程(Impalad)组成。Catalogd主要负责元数据获取和DDL执行,Statestored主要负责消息/元数据广播,Impalad主要负责查询接收和执行。
Impalad可以配置为三种模式:仅协调器、仅执行器或协调器和执行器(默认)。协调者角色中的Impalad负责接收查询、生成计划、调度查询等。Impalad在Executor角色中负责读取和计算数据。默认情况下,每个黑斑羚既是协调者又是执行者。生产环境建议角色应该分开,即每个Impalad要么是协调者,要么是执行者,可以1:50的比例进行配置。更多详情请参考官方文件[1]。
Impala元数据的构成
Impala的元数据缓存在每个协调员角色的catalogd和Impala中。catalog d中的缓存是最新的,每个协调器都会在Catalogd中缓存一份元数据的副本,如下图所示,元数据由Catalogd从外部系统获取,通过Statestored传播给各个协调器。
元数据缓存主要由Java代码实现,主要代码在FE中。还有一些由C实现的代码,主要处理FE和BE之间的交互以及元数据的广播。在代码中,Catalogd和Coordinator (Impalad)中相同的元数据管理逻辑被提取并放在Catalog.java,Catalogd中的实现是CatalogServiceCatalog.java,Coordinator中的实现是ImpaladCatalog.java。
目录是一个层次结构,第一层是从数据库名称到数据库的映射,第二层是每个数据库下的函数映射和表映射:
目录
| - dbCache_=MapString,Db
| - functions_=MapString,ListFunction
|-tablecache _=catalogobjectcachetablefunctions _ map中的值是函数列表,主要表示同名函数的不同重载。TableCache_由CatalogObjectCache维护。CatalogObjectCache封装了一个ConcurrentHashMap,并增加了版本管理的逻辑,比如避免较低版本的更新覆盖较高版本的缓存,跟踪所有缓存的版本。
号等。这些版本管理逻辑在Impalad中尤其重要。我们在后续的文章中会详细介绍。
Table 在代码里有五个具体的子类:HdfsTable、KuduTable、HBaseTable、View、IncompleteTable、DataSourceTable。前4个都比较直白,解释下最后两个:
-
IncompleteTable 表示未加载元数据的表或视图(View)。Catalogd 启动时,为了减少启动时间,只加载了所有表的表名,每个表用IncompleteTable来表示。如果执行了INVALIDATE METADATA,则表的元数据也会被清空,其表现就是回置成了IncompleteTable。IncompleteTable可能代表一个视图,但这在元数据未加载时是无法确定的。因此在HUE等可视化界面中使用Impala时,常常会看到一个View是用Table的图标表示的,但一旦有被使用过,就又变回成了View的图标。
-
DataSourceTable 属于external data source的实现,这块没有任何文档提及,因为一直处于实验状态。其初衷是提供一个Java接口来自定义外部数据源,只需要实现 prepare、open、getNext、close 这几个接口。具体可参考代码里的 EchoDataSource 和 AllTypesDataSource。
接下来我们重点介绍下前三个的元数据构成。
HdfsTable
HdfsTable 代表一张底层存储为 HDFS 的 Hive 表。无分区表的元数据比较简单,少了各个分区对应的元数据。这里以分区表为例,其元数据如图所示:
其中 msTable 和 msPartition 表示 HMS API 里返回的对象:
org.apache.hadoop.hive.metastore.api.Tableorg.apache.hadoop.hive.metastore.api.Partition
HdfsPartition 代表一个分区的元数据,其一大部分内容是 HDFS 文件和块的信息。图中的 FileDescriptor 和 BlockDescriptor,就是从 HDFS API 里返回的 FileStatus 和 BlockLocation 对象抽取数据后生成的。为了节省空间,实际缓存的并不是上图展示的 FileDescriptor 和 FileBlock。IMPALA-4029 引入了 FlatBuffer 来压缩 FileDescriptor 和 FileBlock。FlatBuffer 的好处是不需要像 protobuf 或 thrift 一样做序列化和反序列化,但却可以直接访问对象里的内容,同时带来了一定的压缩比。更多关于 FlatBuffer 参见文末文档 [2].
HdfsPartition 的另一大部分内容是增量统计信息,缓存的是deflate算法压缩后的数据,具体详见:PartitionStatsUtil#partStatsFromCompressedBytes()。解压之后是一个 TPartitionStats 对象,主要包含了各列在该partition里的统计信息,每列的统计信息用一个 TIntermediateColumnStats 表示:
struct TIntermediateColumnStats {
1: optional binary intermediate_ndv // NDV HLL 计算的中间结果
2: optional bool is_ndv_encoded // HLL中间结果是否有用 RLE 压缩
3: optional i64 num_nulls // 该列在该分区的 NULL 数目
4: optional i32 max_width // 该列在该分区的最大长度
5: optional double avg_width // 该列在该分区的平均长度
6: optional i64 num_rows // 该分区行数,用于聚集HLL中间结果
}
关于 Impala 里 ndv() 的实现,可参考 be/src/exprs/aggregate-functions-ir.cc 中的 HllInit()、HllUpdate()、HllMerge()、HllFinalEstimate() 的逻辑。ndv 的中间结果用一个string表示,长度为 1024。在传输时一般会用 RLE (Run Length Encoding) 压缩。
Impala的统计信息受限于Hive(因为要保存在Hive Metastore中),目前并没有统计数值类型列的最大最小、平均值等信息。这块有个古老的 JIRA: IMPALA-2416,目前还没有进展。
一个HDFS分区表的元数据在各种压缩后,在内存中的大小约为
分区数*2048 + 分区数*列数*400 + 文件数*500 + 块数目*150
实际应用中要降低大表的元数据大小,就需要在分区数、列数、文件数、块数目上寻求优化的空间。其中 2048、400、500、150 这些数都是各对象压缩大小的估计值,"分区数 * 列数 * 200" 指的是增量统计信息的大小,如果表的统计信息是非增量的,即一直用 Compute Stats 来统计,则不需要这部分。实际应用中很少直接对大表做 Compute Stats,因为执行时间可能很长,一般都是使用 Compute Incremental Stats,因此这部分的内存占用不可忽略。
KuduTable
HdfsTable 代表一张底层存储为 Kudu 的 Hive 表。Impala 缓存的 Kudu 元数据特别有限:
-
msTable: HMS API 返回的 Table 对象,主要是 Hive 中的元数据
-
TableStats: HMS 中存的统计信息,主要是各列统计信息和整张表的行数等
-
kuduTableName: Kudu 存储中的实际表名,该名字可以跟 Hive 中的表名不同。
-
kuduMasters: Kudu 集群的 master 列表
-
primaryKeyColumnNames: Kudu 表的主键列
-
partitions: Kudu 表的分区信息
-
kuduSchema: Kudu API 返回的 Schema 信息
关于分区信息,只缓存了分区的列是哪些,以及 hash 分区的分区数,并没有缓存 Range 分区的各个 Range 是什么,因此在用 SHOW CREATE TABLE 语句时,看到的 range partition 信息只包含了列名。比如下面这个例子,"Partition by range(id)" 部分的各个 range 被省略了:
Query: show create table functional_kudu.dimtbl
+-------------------------------------------------------------------------------------------------------------------------------------------+
| result |
+-------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE TABLE functional_kudu.dimtbl ( |
| id BIGINT NOT NULL ENCODING AUTO_ENCODING COMPRESSION DEFAULT_COMPRESSION, |
| name STRING NULL ENCODING AUTO_ENCODING COMPRESSION DEFAULT_COMPRESSION, |
| zip INT NULL ENCODING AUTO_ENCODING COMPRESSION DEFAULT_COMPRESSION, |
| PRIMARY KEY (id) |
| ) |
| PARTITION BY RANGE (id) (...) |
| STORED AS KUDU |
| TBLPROPERTIES ('STATS_GENERATED'='TASK', 'impala.lastComputeStatsTime'='1573922577', 'kudu.master_addresses'='localhost', 'numRows'='10') |
+-------------------------------------------------------------------------------------------------------------------------------------------+
如果需要查看具体有哪些 range 分区,还是需要用 SHOW RANGE PARTITIONS 语句,Impala 会从 Kudu 中获取结果来返回,然而还是不会缓存这些 range 信息。
Query: show range partitions functional_kudu.dimtbl
+-----------------------+
| RANGE (id) |
+-----------------------+
| VALUES < 1004 |
| 1004 <= VALUES < 1008 |
| VALUES >= 1008 |
+-----------------------+
Fetched 3 row(s) in 0.07s
这块个人觉得还有很多工作可做,比如把 range 分区的分界点缓存下来后,可以用来优化 Insert 语句,提升批量导入 Kudu 的性能(IMPALA-7751)。另外关于更细节的信息如每个 kudu tablet 的复本位置,kudu tserver 地址等都是没有缓存的,利用这些信息实际也能做很多优化,欢迎大家一起来参与开发!
HBaseTable
Impala 对 HBase 的支持始于对 Hive 的兼容(Hive 可以读 HBase 的数据),但目前已经处于维护状态,社区不再在这方面投入精力。一方面是 Kudu 更适合替代 HBase 来做 OLAP,另一方面是 Impala 也不适合太高并发的 DML 操作。
HBaseTable 代表底层存储为 HBase 的 Hive 表,缓存了 HMS 中的 Table 定义和表的大小(行数)这些基本的统计信息,另外也缓存了底层 HBase 表的所有列族名。
总结
Impala 缓存了外部系统(Hive、HDFS、Kudu等)的元数据,主要目的是让查询计划生成阶段不再需要跟外部系统交互。元数据统一由Catalogd向外部系统获得,并通过Statestored广播给所有Coordinator。
生成查询计划需要哪些元数据,哪些元数据就会被缓存下来:
-
Table: Schema(表名、字段名、字段类型、分区字段等)、各列统计信息
-
HdfsTable: 分区目录、文件路径、文件分块及复本位置、各分区的增量统计信息
-
KuduTable: 分区列及分区类型(Hash、Range)
-
HBaseTable: 各列族名
-
View: 具体的查询语句
以上就是怎么理解Impala元数据,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注行业资讯频道。
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/112504.html