[Hive]七 Hive 内核

发布于:2025-02-11 ⋅ 阅读:(10) ⋅ 点赞:(0)

1. Hive架构

Hive架构主要包括:

  • 用户界面:命令行(CLI)和web UI
  • Thrift Server:公开了一个非常简单的客户端执行HiveQL语句的API,包括JDBC(Java)和ODBC(C++),python等
  • Metastore:系统的目录。Hive的其他组件都与metastore交互
  • Driver:管理HiveQL语句在编译、优化和执行期间的生命周期。收到来自Thrift Server或其他接口的HiveQL语句时,它创建一个Session Handle用于跟踪统计信息,如执行时间、输出行数等。

        SQL Parser:将SQL字符串转换成抽象语法树(Abstract Syntax Tree, AST)

        语义分析(Semantic Analyzer):将AST进一步划分为QueryBlock。

        逻辑计划生成器(Logical Plan Generator):将抽象语法树生成逻辑计划

        逻辑优化器(Logical Optimizer):对逻辑计划进行优化(比如谓词下推)

        物理计划生成器(Physical Plan Generator):根据优化后的逻辑计划生成物理计划(比如MR任务,Spark任务,Tez任务)

        物理优化器(Physical Optimizer):对物理计划进行优化(比如Map join)

        执行器(Executor):执行计划,并将结果返回给客户端。

  • Compiler:在Driver收到HQL语句之后引用,将语句翻译为DAG(有向无环图)形式的MapReduce任务组成的计划
  • Driver按照拓扑顺序将各个Mapreduce作业从DAG提交到Execution Engine(执行引擎)

 1.1 Metastore

Metastore是包含存储在hive中的表的元数据的系统目录。此元数据在表创建期间指定,并在HiveQL中每次引用该表时被重用。元数据包括:

DataBase 默认是default
Table 表的元数据包含列及其类型、所有者、存储信息和SerDe信息的列表。它还可以包含任何用户提供的键值数据(TBLPROPERTIES)。存储信息包括表数据在底层系统中的位置、数据格式和bucketing分桶信息。SerDe元数据包括序列化器和反序列化器方法的实现类以及该实现类所需的任何支持信息。所有这些信息都可以在创建表的过程汇总提供。
Partition 每个分区都可以有自己的列,SerDe和存储信息。

CREATE TABLE `call_center`(
  `cc_call_center_sk` bigint, 
  `cc_call_center_id` char(16), 
  `cc_rec_start_date` date, 
  `cc_rec_end_date` date, 
  `cc_closed_date_sk` bigint, 
  `cc_open_date_sk` bigint, 
  `cc_name` varchar(50), 
  `cc_class` varchar(50), 
  `cc_employees` int, 
  `cc_sq_ft` int, 
  `cc_hours` char(20), 
  `cc_manager` varchar(40), 
  `cc_mkt_id` int, 
  `cc_mkt_class` char(50), 
  `cc_mkt_desc` varchar(100), 
  `cc_market_manager` varchar(40), 
  `cc_division` int, 
  `cc_division_name` varchar(50), 
  `cc_company` int, 
  `cc_company_name` char(50), 
  `cc_street_number` char(10), 
  `cc_street_name` varchar(60), 
  `cc_street_type` char(15), 
  `cc_suite_number` char(10), 
  `cc_city` varchar(60), 
  `cc_county` varchar(30), 
  `cc_state` char(2), 
  `cc_zip` char(10), 
  `cc_country` varchar(20), 
  `cc_gmt_offset` decimal(5,2), 
  `cc_tax_percentage` decimal(5,2))
ROW FORMAT SERDE 
  'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
  'obs://bigdata-test1233/hive/warehouse/hive_tpcds_parquet_2tb/call_center'
TBLPROPERTIES (
  'bucketing_version'='2', 
  'transient_lastDdlTime'='1735377714') 

1.2  Compiler

Driver使用HiveQL字符串调用Compiler,HiveQL字符串可以是DDL、DML或DQL语句之一。Compiler将字符串转换为Plan。对于DDL语句,Plan只包含元数据操作,而对于Load语句,则包含HDFS操作。对于Insert语句和查询语句,该计划由MapReduce作业的Directed Acyclical Graph(DAG,有向无环图)组成。

  • Parser将一个Query字符串转化成一个AST(Abstract Syntax Tree,抽象语法树)。
  • 语义分析器(Semantic Analyzer)将AST转换为基于Block的内部查询表现形式(QueryBlock)。它从Metastore中检索输入表的模式信息(Schema Information)。使用此信息去验证列名,展开select * 并执行类型检查,包括添加隐式类型转换。

QueryBlock代表了查询语句中的一个逻辑块,通常由一个SELECT语句和其相关的子查询、JOIN操作或UNION操作组成。QueryBlock可以看作是查询语句的一个子部分,它有自己的语义和语法规则,并且可以独立进行语义分析和优化。

举个例子,假设有以下HiveQL查询语句:

```sql
SELECT a.id, b.name
FROM table1 a
JOIN table2 b ON a.id = b.id
WHERE a.salary > 1000;
```

在这个查询语句中,可以将其分解为两个QueryBlock:

QueryBlock 1:
```sql
SELECT a.id, b.name
FROM table1 a
JOIN table2 b ON a.id = b.id
```

QueryBlock 2:
```sql
SELECT a.salary
FROM table1 a
WHERE a.salary > 1000
```
每个QueryBlock都有自己的语义和语法规则,语义分析器会分别对它们进行分析和验证。在分析过程中,语义分析器会检查表和列是否存在、列的数据类型是否匹配、JOIN条件是否有效等,并生成相应的查询计划用于后续的执行阶段。

通过将查询语句拆分为多个QueryBlock,语义分析器可以更好地理解和处理复杂的查询语句,提高查询的效率和准确性。

  • 逻辑计划生成器(Logical Plan Generator)将内部查询表示形式转换为逻辑计划,逻辑计划由逻辑运算符树组成。
  • 优化器(Optimizer)对逻辑计划执行多次传递,并以多种方式重写它。
  • 物理计划生成器(Physical Plan Generator)将逻辑计划转换为物理计划,其中包含MapReduce作业的DAG。它为逻辑计划中的每个标记Operator-repartition和union all创建一个新的MapReduce作业,然后它将包含在标记之间的逻辑计划的一部分分配给MapReduce作业的Mapper和Reducer。

1.3 Hive链接到数据库的模式

1.3.1 单用户模式

此模式链接到一个In-memory 数据库Derby,一般用于Unit Test。

1.3.2 多用户模式

通过网络连接到一个数据库中,是最经常使用到的模式。hive-site.xml

<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://hadoop101:3306/metastore?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;allowPublicKeyRetrieval=true</value>
</property>

<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.cj.jdbc.Driver</value>
<value>com.mysql.jdbc.Driver</value>
</property>

<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>root</value>
</property>

<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>xxx</value>
</property>

1.3.3 远程服务器模式

用于非Java客户端访问元数据库,在服务器端启动MetaStore Server,客户端利用thrift协议通过MetaStore Server访问元数据库。

 hive-site.xml

<property>
<name>hive.metastore.uris</name>
<value>thrift://hadoop101:9083</value>
</property>

 启动HiveQL MetaStore Server:

java -Xmx1024m -Dlog4j.configuration=file://$HIVE_HOME/conf/hms-log4j.properties -cp $CLASSPATH org.apache.hadoop.hive.metastore.HiveMetaStore

Hive 客户端命令:

HIVE_LOG_DIR=$HIVE_HOME/logs

nohup hive --service metastore > $HIVE_LOG_DIR/metastore.log 2>&1 &

1.4 Hive 数据模型

Hive中所有的数据都存储在HDFS中,存储结构主要包括数据库、文件、表和视图。

Hive中包含以下数据模型:Inner Table内部表(也叫管理表),External Table外部表,Partition 分区,Bucket分桶。

1.4.1 数据库

类似传统数据库中的DataBase,使用方法如下:

操作 HiveQL语句
创建数据库 create database [db_name]
使用数据库 use db_name
查看所有数据库 show databases
查看某个数据库的创建语句 show create database db_name

1.4.2 表 

Hive的表包括内部表外部表临时表

内部表Managed Table与数据库中的Table在概念上是类似。每一个Table在Hive中都有一个相应的目录存储数据。内部表数据由Hive自身管理,内部表数据存储的位置是由配置项hive.metastore.warehouse.dir控制(默认值:/user/hive/warehouse)删除内部表时,data(存储在HDFS上)和meta data(存储在MySQL)都会被删除。

外部表(External Table)数据由HDFS管理,外部表数据的存储位置由自己指定LOCATION,删除外部表时,meta data会被删除,数据data被删除

临时表(Temporary Table)只对当前session有效,session退出后,表自动删除。

使用临时表时需注意:

1、如果创建的临时表表名已存在,那么当前session引用到该表名时实际用的是临时表。

2、临时表限制:不支持分区字段和创建索引。

操作

HiveQL命令

创建内部表

CREATE TABLE [TABLE_NAME] …

创建外部表

CREATE EXTERNAL TABLE [TABLE_NAME] …

创建临时表

CREATE TEMPORARY TABLE [TABLE_NAME] …

辨别表的类型

DESCRIBE FORMATTED [TABLE_NAME]

1.4.3 分区

Hive的Partition对应于数据库中的Partition列的密集索引,但是Hive中Partition的组织方式和数据库中的很不相同。在Hive中,表中的一个Partition对应于表下的一个目录,所有的Partition的数据都存储在对应的目录中。

创建分区表:

使用CREATE TABLE语句创建表,并在后面添加PARTITIONED BY子句来指定分区字段。例如:

 

```

CREATE TABLE table_name (

column1 data_type,

column2 data_type,

...

)

PARTITIONED BY (partition_column data_type);

```

 

在上述示例中,`table_name`是表的名称,`column1`、`column2`等是表的列名和数据类型,`partition_column`是分区字段的名称和数据类型。

1.4.4 桶

Buckets是将表的列通过Hash算法进一步分解成不同的文件存储。它对指定列计算hash,根据hash值切分数据,目的是为了并行,每一个Bucket对应一个文件。

创建分桶表:

使用CREATE TABLE语句创建表,并在后面添加CLUSTERED BY子句来指定分桶字段,再使用INTO子句指定分桶数量。例如:

 

```

CREATE TABLE table_name (

column1 data_type,

column2 data_type,

...

)

CLUSTERED BY (bucket_column) INTO num_buckets;

```

 

在上述示例中,`table_name`是表的名称,`column1`、`column2`等是表的列名和数据类型,`bucket_column`是分桶字段的名称,`num_buckets`是分桶数量。

 

注意:创建分区表和分桶表时,需要确保分区字段和分桶字段的数据类型与列定义的数据类型一致。

1.4.5 视图

视图与传统数据库的视图类似。视图是只读的,它基于的基本表,如果改变,数据增加不会影响视图的呈现;如果删除,会出现问题。如果不指定视图的列,会根据select语句后的生成。

操作

HiveQL命令

创建视图

 CREATE VIEW [VIEW_NAME] AS SELECT * FROM [TABLE_NAME]

1.5 Hive数据存储

Hive默认可以直接加载文本文件,还支持sequence file 、RCFile以及自定义的输入和输出格式。当前版本Hive默认的创建表的存储格式由参数hive.default.fileformat控制,可配置的参数值包括:"TextFile", "SequenceFile", "RCfile", "ORC", "parquet",内核默认值:TextFile,产品默认值为RCFile。

提供当前主流的ORC与Parquet介绍文档:

ORC官网: https://orc.apache.org/docs/

ORC基础:https://www.cnblogs.com/zhangshihai1232/articles/6021277.html

ORC与Parquet介绍:https://blog.csdn.net/yu616568/article/details/51868447

ORC调优化文档:

https://community.hortonworks.com/articles/75501/orc-creation-best-practices.html;

https://community.hortonworks.com/articles/68631/optimizing-hive-queries-for-orc-formatted-tables.html

2. Hive运行过程

Main()方法调用了CliDriver实体的run()方法:

public static void main(String[] args) throws Exception {

  int ret = new CliDriver().run(args);

  System.exit(ret);

}

在run()方法中最后返回的是executeDriver方法

return executeDriver(ss, conf, oproc);

org.apache.hadoop.hive.cli.CliDriver#executeDriver将一条sql用“;”拆分成多条语句,每条语句执行 ret = cli.processLine(line, true);

ret = cli.processLine(line, true);

在processLine()方法中进入processCmd()方法:

ret = processCmd(command);

processCmd()首先判断是不是退出命令,然后是source和“!”开始的特殊命令(非SQL)的处理,最后是SQL的处理逻辑

CommandProcessor proc = CommandProcessorFactory.get(tokens, (HiveConf) conf)

这句生成了一个CommandProcessor,在CommandProcessor里面会返回一个Driver:

return DriverFactory.newDriver(conf);

返回processCmd()方法之后会进入processLocalCmd(),其中proc即是Driver:

ret = processLocalCmd(cmd, proc, ss);

在processLocalCmd()方法里会调用Driver的run():

ret = qp.run(cmd).getResponseCode();

再进入run()中的runinternal():

public CommandProcessorResponse run(String command, boolean alreadyCompiled) {

  try {

    runInternal(command, alreadyCompiled);

...

org.apache.hadoop.hive.ql.Driver#runInternal()方法执行SQL的编译和返回结果:

compileInternal(command, true);

在compileInternal()方法里就会加锁并执行Hive的语法解析和语义分析操作了:

compile(command, true, deferClose);

compile(command, true, deferClose)就是hive的入口了。

private void compile(String command, boolean resetTaskIds, boolean deferClose) throws CommandProcessorResponse {

}

3. Hive SQL解析过程

Hive简单理解的功能就是把一条SQL (HiveQL)进行解析成MR任务去给Hadoop执行,那么Hive的核心就是怎么去解释这条SQL (HiveQL)。HiveQL解析流程图:

3.1 Parser

Parser首先将HiveQL解析成抽象语法树(AST树):tree = ParseUtils.parse(command, ctx);

ParseUtils封装了ParseDriver对SQL的解析工作,ParseUtils的parse()方法:

public static ASTNode parse(
  String command, Context ctx, String viewFullyQualifiedName) throws ParseException {
  ParseDriver pd = new ParseDriver();
  ASTNode tree = pd.parse(command, ctx, viewFullyQualifiedName);
  tree = findRootNonNullToken(tree);
  handleSetColRefs(tree);
  return tree;
}

ParseDriver对command进行词法分析和语法解析(统称为语法分析),返回一个抽象语法树AST,进入parseDriver的parse()方法中:

HiveLexerX()是词法分析的方法

HiveLexerX lexer = new HiveLexerX(new ANTLRNoCaseStringStream(command));

再根据词法分析的结果得到tokens(不只是单纯的字符串,而是具有特殊意义的字符串的封装,其本身是一个流)

TokenRewriteStream tokens = new TokenRewriteStream(lexer);

最后生成AST树返回:

ASTNode tree = (ASTNode) r.getTree();

Antlr对Hive SQL解析的代码如上述代码逻辑,HiveLexerX,HiveParser分别是Antlr对语法文件HiveLexer.g编译后自动生成的词法解析和语法解析类,在这两个类中进行复杂的解析。

Hive 中语法规则的定义文件在 0.10 版本以前是 Hive.g 一个文件,随着语法规则越来越复 杂,由语法规则生成的 Java 解析类可能超过 Java 类文 件的最大上限,0.11 版本将 Hive.g 拆成了 5 个文件,词法规则 HiveLexer.g 和语法规则的 4 个文件 SelectClauseParser.g, FromClauseParser.g,IdentifiersParser.g,HiveParser.g

3.1.1 语法分析器HiveLexer

HiveLexer.g文件定义了一些hive的关键字,from、where,数字的定义格式【0–9】,分隔符,比较符之类的etc。每一个关键字都会变成一个token。规定Hive中以数字或者下划线开头,如果对这个规则不满意可以修改它。org/apache/hadoop/hive/ql/parse/HiveLexer.g

KW_BY : 'BY';
KW_HAVING : 'HAVING';
KW_WHERE : 'WHERE';
KW_FROM : 'FROM';
KW_AS : 'AS';
KW_SELECT : 'SELECT';
KW_DISTINCT : 'DISTINCT';
KW_INSERT : 'INSERT';
KW_OVERWRITE : 'OVERWRITE';

3.1.2 语法解析器HiveParser

HiveParser是Antlr根据HiveParser.g生成的文件。org/apache/hadoop/hive/ql/parse/HiveParser.g将每种语法Statement翻译成TOK_形式的tree,比如select字句:

selectStatement
   :
   a=atomSelectStatement
   set=setOpSelectStatement[$atomSelectStatement.tree]?
   o=orderByClause?
   c=clusterByClause?
   d=distributeByClause?
   sort=sortByClause?
   l=limitClause?
   {
   if(set == null){
   $a.tree.getFirstChildWithType(TOK_INSERT).addChild($o.tree);
   $a.tree.getFirstChildWithType(TOK_INSERT).addChild($c.tree);
   $a.tree.getFirstChildWithType(TOK_INSERT).addChild($d.tree);
   $a.tree.getFirstChildWithType(TOK_INSERT).addChild($sort.tree);
   $a.tree.getFirstChildWithType(TOK_INSERT).addChild($l.tree);
   }
   }
   -> {set == null}?
      {$a.tree}
   -> {o==null && c==null && d==null && sort==null && l==null}?
      {$set.tree}
   -> ^(TOK_QUERY
          ^(TOK_FROM
            ^(TOK_SUBQUERY
              {$set.tree}
              {adaptor.create(Identifier, generateUnionAlias())}
             )
          )
          ^(TOK_INSERT
             ^(TOK_DESTINATION ^(TOK_DIR TOK_TMP_FILE))
             ^(TOK_SELECT ^(TOK_SELEXPR TOK_SETCOLREF))
             $o? $c? $d? $sort? $l?
          )
      )
   ;

TMP_FIEL是输出路径,Hive是基于MapReduce的上层框架,MR必须要有一个数据文件,MR任务完毕之后结果会存放在TMP_FIEL此路径下边,然后CLI回去读取这个结果文件,展示数据结果。

如果想增加一种语法,就要先修改HiveParser.g,如果要引入关键字,还需要修改HiveLexer.g。

4. MapReduce原理

5. UDF

6. MetaStore模块

7. Hive元数据说明

8. Hive权限说明

9. Hive On Spark

10. Hive 提交任务到Yarn

11. HiveSQL调优