【Hive】十一、Hive SQL 底层执行过程:编译、任务生成与 YARN 调度流程

文章目录

  • HQL->QueryPlan概述图
  • [步骤1:客户端提交 SQL](#步骤1:客户端提交 SQL)
      • [1.1 通过 Beeline/JDBC + Hive Server2 提交SQL](#1.1 通过 Beeline/JDBC + Hive Server2 提交SQL)
      • [1.2 通过Hive CLI 提交SQL](#1.2 通过Hive CLI 提交SQL)
  • [步骤2:Driver 内部启动编译](#步骤2:Driver 内部启动编译)
  • [步骤3:COMPILER生成 Hive 内部 QueryPlan](#步骤3:COMPILER生成 Hive 内部 QueryPlan)
    • [步骤3.3:Logical Plan Generator(生成逻辑计划)](#步骤3.3:Logical Plan Generator(生成逻辑计划))
    • 步骤3.4:Optimizer(优化器)
    • [步骤3.5:Physical Plan Generator(生成物理计划)](#步骤3.5:Physical Plan Generator(生成物理计划))
      • [1)rootTasks/Task DAG](#1)rootTasks/Task DAG)
        • [第一层:rootTasks / Task DAG(任务骨架)](#第一层:rootTasks / Task DAG(任务骨架))
        • [第二层:Task 内部结构(Work + Operator)](#第二层:Task 内部结构(Work + Operator))
        • [第三层:执行元信息 Operator](#第三层:执行元信息 Operator)
      • [2)inputs / outputs:输入输出实体](#2)inputs / outputs:输入输出实体)
      • 3)schema:结果结构
      • [4)queryId / queryString / queryState](#4)queryId / queryString / queryState)
      • 5)queryProperties:查询特征信息
  • [步骤4:Executor 执行计划](#步骤4:Executor 执行计划)
  • QueryPlan->Yarn概述图
  • [步骤5:YARN 执行 MapReduce Job](#步骤5:YARN 执行 MapReduce Job)
    • [步骤 5.1:RM分配 AM 所需 Container](#步骤 5.1:RM分配 AM 所需 Container)
    • [步骤 5.2:NodeManager 启动 AM Container,并在其中运行 MRAppMaster](#步骤 5.2:NodeManager 启动 AM Container,并在其中运行 MRAppMaster)
    • [步骤 5.3:ApplicationMaster 分析任务,并请求资源](#步骤 5.3:ApplicationMaster 分析任务,并请求资源)
    • [步骤 5.4:ResourceManager 按ResourceRequest返回containers给AM](#步骤 5.4:ResourceManager 按ResourceRequest返回containers给AM)
    • [步骤 5.5:NodeManager 启动 MapTask](#步骤 5.5:NodeManager 启动 MapTask)
    • [步骤 5.6:MapTask 进展到阈值,MRAppMaster 申请 ReduceTask Container](#步骤 5.6:MapTask 进展到阈值,MRAppMaster 申请 ReduceTask Container)
    • [步骤 5.7:ResourceManager 分配 ReduceTask Container](#步骤 5.7:ResourceManager 分配 ReduceTask Container)
    • [步骤 5.8:NodeManager 启动 ReduceTask](#步骤 5.8:NodeManager 启动 ReduceTask)
    • [步骤 5.9:MRAppMaster 通知 ResourceManager 作业完成](#步骤 5.9:MRAppMaster 通知 ResourceManager 作业完成)

我的网站原文: https://eleanora-lyh.github.io/MyLearningNotes/
csdn处的文章会尽快同步更新,欢迎大家来访问!

首先需要声明一下Hive是运行在Hadoop上层的数据仓库工具,它依赖 HDFS 和 YARN 运行,但不属于 Hadoop 内核,而是 Hadoop 生态圈的重要成员。

  • Hadoop 核心 ​ 包含三大组件:HDFS (分布式存储)、YARN (资源调度)、MapReduce(分布式计算框架)。

    此外Hadoop 还包含 Common 等基础模块,但这里不涉及

  • Metastore 是 Hive 的元数据管理服务,用于保存表结构、分区、存储位置、文件格式等元数据信息。Metastore 后端通常使用关系型数据库保存这些元数据;本地测试可使用 Derby,生产环境常用 MySQL/PostgreSQL 等外部数据库。


本文讲解 Hive SQL 从客户端提交,到 Hive 编译成内部 QueryPlan,再由执行引擎提交到集群运行的底层过程,简要概括如下:

当 Hive 使用 MapReduce 引擎时,Compiler 会将 SQL 编译成包含 MapRedTask 的 Hive QueryPlan。MapRedTask 是 Hive 对一个 MapReduce Job 的封装,它携带了本次 SQL 的 Operator Tree、输入输出路径、Shuffle Key、Reducer 数量等信息。

Executor 执行 MapRedTask 时,会通过 Hadoop API 提交一个Hadoop MapReduce Job。随后 MapReduce 框架根据输入切片、Reducer 数量和 JobConf创建出来运行时任务实例 MapTask / ReduceTask

MapTask / ReduceTask在 YARN Container 中运行,并加载 Hive 的通用 Mapper/Reducer 类执行逻辑来执行 SQL 对应的 Operator Tree。

需要说明:本文主要以 MapReduce 执行引擎为例。如果使用 Tez 或 Spark,Hive 内部编译流程类似,但提交到执行后端和 YARN 的细节不同。

HQL->QueryPlan概述图

概述图的步骤和标题是一一对应的,所以可以结合后面的详细步骤来看

步骤1:客户端提交 SQL

用户通过 Beeline/JDBC+Hive server2 或 Hive CLI提交 SQL

1.1 通过 Beeline/JDBC + Hive Server2 提交SQL

HiveServer2,简称 HS2,可以理解为 Hive 的服务端网关(一般绑定 10000端口)

你启动 HS2 后,它会监听 Thrift 请求、处理 认证(Kerberos/LDAP/Plain...) 、建立 Session 、每个 Session 的执行请求会走到 HS2 内部的 Driver

  • 启动HS2

    shell 复制代码
    $ hive --service hiveserver2 &
    # 使用 nohup 让它在后台持续运行(前台运行的话关闭窗口则消失)
    $ nohup bin/hive --service metastore >> logs/metastore.log 2>&1 & # 先启动metastore,再启动hiveserver2
    $ mkdir -p /logs # 进入¥HIVE_HOME后创建日志目录(如果不存在的话)
    $ nohup bin/hive --service hiveserver2 >> logs/hiveserver2.log 2>&1 &
  • JDBC 建立连接

    shell 复制代码
    beeline -u "jdbc:hive2://hs2-host:10000/default"

    JDBC 程序也是类似:

    java 复制代码
    Connection conn = DriverManager.getConnection(
        "jdbc:hive2://hs2-host:10000/default",
        "user",
        "password"
    );
  • HS2 为这次连接创建一个 Session 上下文,包含:用户身份、当前数据库、Hive 配置参数、临时表信息、执行上下文、权限上下文

    执行下面语句时,配置就会保存在当前 Session 中。

    sql 复制代码
    set hive.execution.engine=tez;
    use dwd;
Hive Server2能力 说明
接收客户端连接 Beeline、JDBC、ODBC、BI 工具
用户认证 Kerberos、LDAP、用户名密码等
Session 管理 每个用户连接一个或多个 Session
SQL 接收与执行 将 SQL 交给内部 Hive Driver
权限控制 集成 Ranger/Sentry/SQL Standard Authorization
并发管理 多用户同时提交 SQL
结果返回 将查询结果返回给客户端

1.2 通过Hive CLI 提交SQL

  • Hive CLI 是"客户端自己就是 Hive 执行入口,直接在机器上执行:hive -e "select count(*) from table_name;"(目前几乎不用)

步骤2:Driver 内部启动编译

HiveServer2 收到 SQL 后,会在服务端调用 Hive 的 Driver.

Driver 收到 SQL 后,调用 Compiler 进行编译。

plain 复制代码
SQL 文本
  ↓
AST:SQL 的语法结构
  ↓
语义分析:确认这些表、字段、函数、分区是否真的存在、是否合法
  ↓
逻辑计划 / Operator Tree:描述这条 SQL 要做哪些计算
  ↓
Hive 内部 QueryPlan
  ↓
Executor 调度 Hive Task
  ↓
如果是 MapReduce 引擎,MapRedTask 再提交 MR Job 到 YARN

步骤3:COMPILER生成 Hive 内部 QueryPlan

Compiler 负责把 SQL 变成物理执行计划,具体包括(注意:这里还不是最终交给YARN的 Job )

plain 复制代码
Driver传递给Compiler的 SQL 文本
↓
Parser 解析器(step 1)
↓
AST 抽象语法树
↓
Semantic Analyzer 语义分析(step 2)
↓
Logical Plan 逻辑执行计划(step 3)
↓
Optimizer 优化器(step 4)
↓
Physical Plan 物理执行计划(step 5)
↓
Execution Engine 执行引擎
↓
MapReduce / Tez / Spark 等任务

步骤3.1:Parser(语法解析)

解析 SQL,生成对应的Abstract Syntax Tree(AST)抽象语法树

Parser 会识别出具体子句,并判断语法是否错误:

context 复制代码
SELECT 子句
FROM 子句
WHERE 子句
GROUP BY 子句
聚合函数 COUNT(*)

如果 SQL 语法写错,会在这个阶段报错,如下:

sql 复制代码
SELECT FROM user_log;

SQL 最后会被解析成抽象语法树 AST,也就是 Abstract Syntax Tree。(可以简单理解为: Hive 把 SQL 变成一棵结构化的语法树

例如 SQL:

sql 复制代码
SELECT province, COUNT(*)
FROM user_log
WHERE dt = '2026-06-01'
GROUP BY province;

可以抽象理解为:

plain 复制代码
TOK_QUERY
 ├── TOK_FROM
 │    └── user_log
 ├── TOK_INSERT
 │    ├── TOK_SELECT
 │    │    ├── province
 │    │    └── COUNT(*)
 │    ├── TOK_WHERE
 │    │    └── dt = '2026-06-01'
 │    └── TOK_GROUPBY
 │         └── province

AST 还不是最终执行计划,只是 SQL 的结构化表达。

在 Hive 中,AST 主要作为后续语义分析的输入。Semantic Analyzer 会基于 AST 访问 Metastore,确认表、字段、分区、文件格式、HDFS 路径等信息,然后生成 Hive 内部的逻辑计划 Logical Plan和 Operator Tree。

步骤3.2:Semantic Analyzer(语义分析)

根据AST访问 Metastore,确认表、字段、分区、文件格式、HDFS路径等,完成语义检验

语义分析会检查如下内容(一定会访问 Metastore):

检查内容 例子
表是否存在 user_log 是否存在
字段是否存在 province 是否是表字段
字段类型是否合法 字符串能否参与当前计算
聚合是否合法 SELECT a, COUNT(*) 是否需要 GROUP BY a
权限是否合法 当前用户是否有读写权限
分区是否合法 分区字段是否存在

例如下面的错误写法:

sql 复制代码
SELECT city, COUNT(*)
FROM user_log;

如果没有:GROUP BY city

Hive 会报错,因为非聚合字段 city 不能和聚合函数一起随便查询。

步骤3.3:Logical Plan Generator(生成逻辑计划)

逻辑执行计划关注的是:这条 SQL 逻辑上要做哪些操作

比如:

Plain 复制代码
读取 user_log
过滤 dt = '2026-06-01'
选择 province 字段
按 province 分组
计算 COUNT(*)
输出结果

它不太关心具体用几个 Map Task、几个 Reduce Task,也不一定关心具体在哪台机器执行。

逻辑计划里的常见操作包括:

Plain 复制代码
TableScan
Filter
Select
Join
GroupBy
ReduceSink
FileSink
Limit
Union
PTF / Window

步骤3.4:Optimizer(优化器)

生成初始逻辑计划后,Hive 会做一系列优化

常见优化包括:

优化类型 作用
谓词下推 尽早执行 WHERE 过滤
分区裁剪 只读取需要的分区
列裁剪 只读取 SQL 用到的列
Map 端聚合 在 Map 阶段先做局部聚合
Join 重排 调整多表 Join 顺序
MapJoin 小表广播到 Map 端,避免 Shuffle
CBO 成本优化 基于统计信息选择更优计划
向量化执行 批量处理数据,减少逐行计算开销
Reduce 数量估算 根据数据量估算 Reducer 数量

例如这条 SQL:

sql 复制代码
SELECT province, COUNT(*)
FROM user_log
WHERE dt = '2026-06-01'
GROUP BY province;

如果 dt 是分区字段,Hive 会做分区裁剪:只扫描 dt='2026-06-01' 这个分区而不是扫描整张表。

步骤3.5:Physical Plan Generator(生成物理计划)

物理计划仍然 是Hive 自己的执行描述,是查询执行计划封装java对象 QueryPlan(带引擎类型),还不是物理层面可以在集群底层执行的 MapReduce/Tez/Spark 的 Job

  • ✅ 物理执行计划 = Hive 视角(描述"怎么计算")

  • ✅ Job = 执行引擎视角(描述"怎么在集群上跑")

QueryPlan不是单个任务,而是包含整条 SQL 执行所需的全量信息:Task 节点 + parent/child 依赖Tree

plain 复制代码
QueryPlan(整条 SQL 的计划封装)
│
├── rootTasks(入口 Task,告诉 Executor 从哪些 Task 开始执行)
│   │
│   ├── MapRedTask / TezTask / SparkTask(任务依赖图,决定 Task 执行顺序)
│   │   │
│   │   └── MapredWork(这里以MapRedTask为例内部的工作描述,此外还有TezWork,SparkWork)
│   │       ├── MapWork
│   │       └── ReduceWork
│   │           │
│   │           └── Operator Tree(Work 内部的算子树)
│   │                  ├── TableScanOperator
│   │                  ├── FilterOperator
│   │                  ├── SelectOperator
│   │                  ├── GroupByOperator
│   │                  ├── ReduceSinkOperator
│   │                  └── FileSinkOperator
│   │
│   ├── MoveTask(移动临时目录结果到最终表/分区目录)
│   ├── StatsTask(收集统计信息)
│   ├── DDLTask(执行 DDL)
│   ├── ConditionalTask (条件分支任务,例如 MapJoin 选择)
│   └── 其他 Task
├── FetchTask(小查询直接拉取结果,不启动 MR/Tez/Spark 计算任务)
├── inputs(输入对象,记录读取了哪些表/分区/路径)
├── outputs(输出对象,记录写到了哪些表/路径)
├── schema(视图,用于结果返回和客户端展示)
├── queryProperties(参数配置,决定查询特性,例如是否有 join/groupby/orderby)
├── queryId(日志、追踪、YARN application 关联)
└── queryString(用于审计、日志、Explain、Hook)

简略层级拆分如下

plain 复制代码
QueryPlan
└── Task DAG
    └── Task(执行节点)
        └── Work(Task节点内部要干的活)
            └── Operator Tree(Work里的具体操作)

1)rootTasks/Task DAG

每个 Task 节点的 payload 已经是"面向具体引擎"的了。从上面的示例可以看出QueryPlan是整条 SQL 的计划封装,描述整个 SQL 怎么执行。

Compiler 在编译阶段会参考 hive.execution.engine 等配置,生成不同类型的计算 Task,例如 MapRedTaskTezTaskSparkTask。每种Task又会对应不同的Work:MapredWork / TezWork / SparkWork

其中Task DAG 内部的层级可以分为三层:Task -> Work -> Operator

plain 复制代码
Task DAG(最顶层:QueryPlan 中的任务依赖图)
 └──MapRedTask(第一层:Task DAG 中的一个计算类 Task 节点)
     └── MapredWork  (第二层:work,Task 内部的工作描述)      
          ├── MapWork   (operator tree for map side:
          │               ├── root operator: TableScan → Filter → Select → ReduceSink(第三层:operator,Work 内部的算子树)    
          │               ├── input format class
          │               ├── serde class
          │               └── tag / path info
          │
          ├── ReduceWork (operator tree for reduce side:
          │               ├── root operator: GroupBy → Select → FileSink
          │               ├── reducer key/value schema
          │               └── numReduceTasks
          │
          └── paths / aliases / partitions ...
第一层:rootTasks / Task DAG(任务骨架)

这是 QueryPlan 最核心的部分。rootTasks 表示没有父依赖、可以最先执行的 Task。

Compiler 在编译阶段会参考 hive.execution.engine 等配置,生成不同类型的计算 Task,例如 MapRedTaskTezTaskSparkTask。Executor 执行时并不是再临时选择引擎,而是调度 QueryPlan 中已经生成好的 Task DAG。

不同执行引擎会生成不同的 Task 子类

引擎 Task 子类(核心) 本质
MapReduce MapRedTask MapRedTask -> Hadoop MapReduce Job -> MapTask / ReduceTask 内含:Map operator tree / Reduce operator tree、需要的 JobConf信息、输入输出路径
Tez TezTask TezTask -> Tez DAG -> Vertex / Tez Task 内含:一个 Tez DAG 描述(Vertices / Edges / Vertex config)
Spark SparkTask SparkTask -> Spark Job / Stage DAG -> Spark Task 内含:SparkWork(RDD action 描述)→ 通过 Hive on Spark 提交

Hive 的一个 SQL 往往会被拆成多个执行阶段,Task DAG 描述 Hive 层 Task 的依赖关系。例如复杂 SQL 可能是:

plain 复制代码
MapRedTask_1
  ↓
MapRedTask_2
  ↓
MoveTask
  ↓
StatsTask
或者
MapRedTask_A
      ↓
MapRedTask_C
      ↑
MapRedTask_B

Task DAG 告诉 Executor:哪些 Task 可以并行?哪些 Task 必须等待上游完成?哪些 Task 失败会导致整个查询失败?

其他非计算类 Task有:

Task 类型 作用 举例
MoveTask 把临时目录结果移动到最终表目录 INSERT OVERWRITE 通常先写临时目录/tmp/hive/xxx 成功后再移动到正式表目录:/warehouse/result_table/ 来避免任务失败污染正式目录。
StatsTask 收集表/分区统计信息 统计信息后续可以服务优化器 row countfile sizepartition statistics column statistics
FetchTask 小结果直接拉回客户端 如果满足条件,Hive 可能不启动 MR/Tez/Spark, 而是直接通过 FetchTask 从文件读取少量结果返回客户端 例如:SELECT * FROM small_table LIMIT 10;
DDLTask 执行建表、删表等元数据操作 直接操作 Metastore/HDFS 元数据 CREATE/DROP/ALTER TABLE

rootTasks = MapRedTask时,可能执行的步骤:

sql 复制代码
Stage-1: MapRedTask
    读取 dwd_user/dt=2026-06-17
    过滤 dt
    按 province 做 partial aggregation
    shuffle by province
    reduce 端做 final aggregation
    输出到临时目录

Stage-2: MoveTask
    将临时目录移动到 ads_user_cnt 的目标目录

Stage-3: StatsTask
    更新表或分区统计信息
第二层:Task 内部结构(Work + Operator)

计算类 Task 内部通常有 Work,Work 是Task 内部的工作描述

plain 复制代码
MapRedTask
└── MapWork(描述 Map 阶段做什么)
    └── Operator Tree(scan/filter/groupby)
└── ReduceWork(描述 Reduce 阶段做什么:)
    └── Operator Tree(final agg/filesink)

Tez 下可能是:

plain 复制代码
TezTask
└── TezWork
     ├── MapWork
     ├── ReduceWork
     ├── MergeJoinWork
     └── BaseWork DAG    

Spark 下可能是:

plain 复制代码
SparkTask
└── SparkWork
     ├── MapWork
     ├── ReduceWork
     └── Spark Work DAG  
第三层:执行元信息 Operator

Operator Tree 是 Hive 内部表达数据处理逻辑的算子树

常见 Operator:

Operator 作用
TableScanOperator 扫描表数据
FilterOperator 过滤数据
SelectOperator 选择字段/表达式
GroupByOperator 聚合
ReduceSinkOperator 产生 shuffle 边界
JoinOperator join
FileSinkOperator 写出结果
LimitOperator limit
UnionOperator union
ScriptOperator transform/script

例如

sql 复制代码
SELECT user_id, COUNT(*)
FROM page_view
WHERE dt = '2026-06-16'
GROUP BY user_id;

Operator Tree 可能是:

plain 复制代码
TableScan
  ↓
Filter
  ↓
Select
  ↓
GroupBy partial
  ↓
ReduceSink(ReduceSink 通常代表需要 shuffle)
  ↓
GroupBy final
  ↓
FileSink

2)inputs / outputs:输入输出实体

QueryPlan 会记录该 SQL 涉及的输入和输出,不是单纯给 Task 用的,也服务 Hive 的治理和管理能力

例如输入:

plain 复制代码
page_view 表
page_view 的 dt='2026-06-16' 分区
对应 HDFS 路径

输出:

plain 复制代码
user_pv_result 表
目标分区
目标 HDFS 路径

这些信息用于:

  • 权限检查

  • 血缘分析

  • 锁管理

  • Hook

  • 审计日志

  • Explain

  • 执行监控

3)schema:结果结构

对于查询类 SQL,QueryPlan 还需要知道返回结果的 schema

sql 复制代码
SELECT user_id, COUNT(*) AS pv
FROM page_view
GROUP BY user_id;

结果 schema 类似:

plain 复制代码
user_id string
pv bigint

用于:

  • 返回给客户端

  • JDBC ResultSet metadata

  • Beeline 展示列名

  • FetchTask 读取结果

4)queryId / queryString / queryState

这些用于追踪和管理查询,在生产排查 Hive/Spark/Scope 类任务时,这类 ID 非常重要。例如:

plain 复制代码
queryId = hive_20260618141414_xxxxx
queryString = 原始 SQL

作用包括:

  • 日志排查

  • YARN application 关联

  • 审计

  • Hook

  • Explain

  • 错误定位

  • 查询历史

5)queryProperties:查询特征信息

queryProperties 记录这条 SQL 的特征,例如:

plain 复制代码
是否有 join
是否有 group by
是否有 order by
是否是 CTAS
是否是 analyze
是否是 query
是否需要 reducer
是否涉及 script
是否有 window function

这些信息可能影响:

  • 优化策略

  • 执行路径

  • Explain 展示

  • 结果处理方式

步骤4:Executor 执行计划

步骤3回顾:

注意:以下所有内容都是在假设执行引擎是MapReduce的前提下展开的

步骤3 COMPLILER 会将 SQL 编译成包含MapRedTask生成的 Hive QueryPlan 这里还只是Hive内部才看的懂的执行说明:Hive Task+调度上下文,直接交给Hadoop是无法识别的,所以才需要Driver内部的Executor

这个 MapRedTask 是 Hive 对一个 MapReduce Job 的封装,内部包含很多信息,例如:

plain 复制代码
输入表路径
输出临时目录
Map 端 Operator Tree
Reduce 端 Operator Tree
表的 SerDe 信息
列信息
过滤条件
聚合逻辑
Join 逻辑
Group By 逻辑
Reduce 数量
Shuffle Key
JobConf 配置

COMPLILER 编译完成后,把 QueryPlan 对象返回给 DriverDriver 在执行阶段把这个 QueryPlan 交给内部的执行逻辑 Executor 去调度

Executor 调度

Executor 执行 MapRedTask 时,会通过 Hadoop API 提交一个Hadoop MapReduce Job。随后 MapReduce 框架根据输入切片、Reducer 数量和 JobConf创建出来运行时任务实例 MapTask / ReduceTask

这里插入一个问题:我们俗称的 Hive on MR 的"MapReduce 程序"在这些步骤的哪里?

如果你自己写 MapReduce 程序,通常是这样:

java 复制代码
Mapper 类
Reducer 类
Driver 类
JobConf 配置

但 Hive 不会为每条 SQL 临时生成一份新的 Java 源码,然后编译成新的 Mapper/Reducer 类,Hive 通常使用的是一套通用的Mapper / Reducer 框架,例如类似:

java 复制代码
ExecMapper
ExecReducer

也就是说广义上底层真实操作HDFS的"MapReduce 程序"就在这里,由 Hadoop MapReduce 框架创建出来的运行时任务实例MapTask / ReduceTask = Hive 通用 Mapper/Reducer 类 + 本次 SQL 对应的 Operator Tree + JobConf 配置

最后Hadoop MapReduce 框架将生成MapTask / ReduceTask提交到 YARN。直观的衔接步骤如下

plain 复制代码
Driver.compile(sql)
    ↓
Compiler.compile()
    ↓
生成 QueryPlan
    ↓
Driver 持有 QueryPlan
    ↓
Driver.execute(queryPlan)
    ↓
Executor
    ↓
TaskRunner
    ↓
MapRedTask.execute()
    ↓
生成 Hadoop JobConf
    ↓
计算 InputSplit(根据表数据在 HDFS 上的文件大小、Block Size,算出要起几个 Map)
    ↓
上传 job 资源到 HDFS staging 目录(MRAppMaster 和 MapTask/ReduceTask 启动时需要的执行说明书和依赖包)
    ↓
调用 Hadoop MR JobClient / JobSubmitter
    ↓
提交 Application 到 YARN ResourceManager

步骤3中我们知道QueryPlan中最重要的就是QueryPlan.rootTasks,因为Executor 的第一步就是找到没有父依赖的 root task,然后开始调度。

Executor 本质上是:Hive Task DAG 的调度器 ,通过 TaskRunner 的执行包装器来运行各种类型的 Task。

Compiler 可能生成:

plain 复制代码
QueryPlan
└── rootTasks
    └── MapRedTask
        └── childTasks
            └── MoveTask
                └── childTasks
                    └── StatsTask

Executor 执行顺序是:

plain 复制代码
1. 执行 MapRedTask
2. MapRedTask 成功后,执行 MoveTask
3. MoveTask 成功后,执行 StatsTask
4. 全部成功,Driver 标记 SQL 成功

QueryPlan->Yarn概述图

当执行引擎选择 MapReduce 时,MR Job 提交到 YARN 后具体运行步骤如下

步骤5:YARN 执行 MapReduce Job

YARN 是"全局一套 + 每节点一部分",在一个 Hadoop/YARN 集群中:

  • YARN = 决定"在哪台机器的哪个进程里跑"

  • HDFS = 决定"数据存在哪、怎么读"

YARN中包含如下组成部分:

  • ResourceManager(RM) :RM 管全局资源,是全局唯一的 active 主服务(生产一定是 HA:一个Active RM + 一个Standby RM

  • ApplicationMaster(AM) :AM 管单个应用,现在已知一次 Hive SQL 在 MapReduce 引擎下会生成的一个或多个 MapReduce Job,一个 MapReduce Job通常对应一个 YARN Application,这个 Application 通常由一个正在运行的 AM 负责协调;如果 AM 失败,YARN 可能按配置重试。

    • 一开始由 RM 批准、并经过NM 在某个节点上拉起的一个 Container, AM则会在这个里Container运行

    • 在 Tez 引擎下,Hive SQL 通常会被编译成 Tez DAG。Tez DAG 与 YARN Application 的对应关系受 Tez Session 等配置影响,不确定,需要结合具体版本/实现确认。

  • NodeManager(NM) :NM 管本机资源,每台机器(每个Node)一个的常驻守护进程(daemon),一直活着,负责心跳汇报资源、接RM/AM指令拉起/清理容器

  • Container :Container 是资源槽位,是 YARN 分配给某个 Application 的资源分配单元,包含内存、vcore、环境变量、本地化资源、启动命令等运行上下文。任务(Application)跑完/失败就销毁;一台机器空闲的时候 container 数量是 0,不是永远挂着多个 container。

    • YARN Container = 资源分配单位,不等于 JVM。根据执行引擎的不同,Container 里包含的内容也有区别

      Task Attempt​特指一个 Task(Map 任务或 Reduce 任务)的某一次具体执行实例。一个 Container 在同一时刻只会服务于一个 Task Attempt。之所以叫 "Attempt",是因为分布式系统中任务可能因节点故障、资源竞争等原因失败,系统会多次"尝试"执行同一个 Task,每一次尝试就是一个 Task Attempt。只要有一次 Attempt 成功,这个 Task 就算完成。

      场景 Container 里通常跑什么
      MapReduce AM Container MRAppMaster JVM
      MapReduce Task Container YARNChild JVM,里面运行一个 MapTask 或 ReduceTask attempt
      Tez AM Container Tez ApplicationMaster JVM
      Tez Task Container Tez Container JVM,可复用,执行多个 Tez Task
      Spark on YARN Executor Container Executor JVM,里面可以并发/连续执行多个 Spark Task
      非 Java 程序 Python / Shell / C++ / 其他进程

Container里的MapTask/ReduceTask 是真正干活的进程

粒度 数量规则
整个集群 只有一个active状态的ResourceManager(RM) ,集群n个机器就对应了n个node,其中有一个node安装了ResourceManager,每个node都安装了NodeManager(NM)
某台机器 即某个node,包含一个NodeManager(NM) 。 当需要执行Application任务时,RM会给node分配一个AM Container 资源 用于运行ApplicationMaster NodeManager(NM) 则启动对应的ApplicationMaster(AM) 根据任务的不同ApplicationMaster(AM) 会继续向ResourceManager(RM) 申请多个Task Container用于执行MapTask / ReduceTask 等具体任务
Container 某台机器上可以同时运行 0~N 个 AM Container 和 0~N 个 Task Container,具体数量受资源、队列、调度策略和当前负载限制。 在 MapReduce 场景下,一个 AM Container 通常运行 MRAppMaster,一个 Task Container 通常运行一个 MapTask 或 ReduceTask attempt。
Application(以 MapReduce Job 为例) 在 MapReduce 引擎下,一个 MR Job 通常对应一个 YARN Application;一个 Application 通常由一个正在运行的 AM 协调,1 个 AM(生命周期跟着Job走,Job结束AM就退)。如果同一时刻跑10个Job = 10个AM,分布在不超过10台机器(也可能更集中,看调度)

如果这个 Job 需要:

  • 1 个 ApplicationMaster
  • 3 个 MapTask
  • 1 个 ReduceTask

那么分配结果可能如下

plain 复制代码
Node1
├── ResourceManager Active
└── NodeManager

Node2
├── ResourceManager Standby(备用的ResourceManager,提供高可用)
└── NodeManager
    └── Container 1 → MRAppMaster(启动Application)

Node3
└── NodeManager
    ├── Container 2 → MapTask 1
    └── Container 3 → MapTask 2

Node4
└── NodeManager
    ├── Container 4 → MapTask 3
    └── Container 5 → ReduceTask 1

步骤 5.1:RM分配 AM 所需 Container

回顾:

在步骤4.4:Client​ 把作业资源(jar、job.xml、splits 元数据等)上传到共享存储(如 HDFS)

在步骤4.5:调用 Hadoop MR JobClient / JobSubmitter提交 Application 到 YARN ResourceManager之后(MapReduce 在 YARN 上运行时,一个 MR Job 本质上会对应一个 YARN Application。)

提交时会告诉 ResourceManager:

plain 复制代码
Application 名称
用户
队列
ApplicationMaster 启动命令
ApplicationMaster 所需资源
staging 目录位置
job.xml 位置
job.jar 位置
安全 token

ResourceManager 收到后,会生成一个YARN Application ID:application_XXXX_YYYY


RM 接纳 Application调度器(Capacity/Fair)+ 策略 ​ 决定把 AM 容器(AM Container) 分配到哪台 NM;然后 RM 让那台 NM 启动该 AM Container ,再把启动命令下发给 NM。图中假设为node2中的NM,用于配置启动ApplicationMaster的环境

步骤 5.2:NodeManager 启动 AM Container,并在其中运行 MRAppMaster

步骤 5.1中RM 接纳 App 后ApplicationMaster Container 调度出来,再把启动命令下发给 NM

NM 启动 AM Container → 在里面执行 ApplicationMaster 进程(比如 MRAppMaster

步骤 5.3:ApplicationMaster 分析任务,并请求资源

之前的步骤4.5中包含Hive Excutor中生成的 job.xml、split 信息,但是这些属于上层(Hive→MR 的编译/翻译产物),不是 YARN 的概念。

YARN 只知道:这是一个 Application ,它有 AM

1、AM 从作业规划(splits/stages)计算需要多少 map task、多少 reduce task、每个 task 的资源形态

  • 比如 HDFS 上有 10 个 input split,那么通常会有 10 个 MapTask。

  • MRAppMaster 向 ResourceManager 申请资源

    plain 复制代码
    我要 10 个 Map container
    每个 container 需要 2GB 内存、1 vcore
    最好靠近数据所在节点

2、AM 向 RM 发送 ResourceRequest (需要的资源、优先级、可能还带 locality 偏好),并根据RM返回的 allocated containers ​ 去 向 NodeManager 发送 StartContainer 请求派发 Task(给 NM launch)

步骤 5.4:ResourceManager 按ResourceRequest返回containers给AM

这里需要注意RM不具备思考的功能,只是根据ResourceRequest的内容进行分配(在"满足资源"的前提下尽量满足 locality preference)

  • RM 只负责资源调度/分配(资源维度:内存、vcore、队列、ACL、节点健康、标签等)

  • "根据数据分配"更多是 AM 在做 locality 偏好(NODE_LOCAL/RACK_LOCAL/ANY),并把偏好写进 ResourceRequest;RM 尽量尊重(但不保证一定 local)

步骤 5.5:NodeManager 启动 MapTask

AM 会构造 task-launch 上下文(环境变量、jar、参数等)

→ 向目标 NM 发起 StartContainer

→ NM 在该 Task Container里启动 JVM

→ 跑 MapTaskRunner,MapTask 执行 Hive 的 Map Operator Tree,每个 MapTask 启动后,会读取自己的 input split。然后执行类似这样的逻辑

plain 复制代码
读取一行数据
  ↓
反序列化成 Hive Row
  ↓
TableScanOperator
  ↓
FilterOperator
  ↓
SelectOperator
  ↓
GroupByOperator(partial)
  ↓
ReduceSinkOperator
  ↓
输出 shuffle key/value

步骤 5.6:MapTask 进展到阈值,MRAppMaster 申请 ReduceTask Container

需要注意:

  • 是否有 Reduce 阶段,是 Hive 编译生成 MR Job 时决定的。
  • Reducer 数量也是 Job 配置的一部分。
  • MRAppMaster 不是根据 SQL 重新判断是否需要 Reduce。

AM 会跟踪 MapTask 的执行进度。当 Map 完成比例达到配置的 slow-start 阈值后,如果该 MR Job 配置了 Reduce 阶段,ApplicationMaster 会继续向 ResourceManager 申请 ReduceTask Container资源 用于ReduceTask

步骤 5.7:ResourceManager 分配 ReduceTask Container

步骤 5.8:NodeManager 启动 ReduceTask

reduce 启动后会做 shuffle fetch、sort、reduce、输出(未完成 map 输出也会边产出边拉)

步骤 5.9:MRAppMaster 通知 ResourceManager 作业完成

所有 task 完成(或失败达到上限),AM 向 RM 报告最终状态,RM 标记 Application 结束;AM 退出,NM 清理相关 Container 本地临时数据。