大数据组件-Hive
1.Hive概念
Hive 是基于 Hadoop 的数据仓库工具,用于处理大规模结构化数据。它将 SQL 查询转换为 MapReduce 任务,简化了分布式计算操作。Hive 适合离线批处理场景,支持数据存储、查询和分析。
2.Hive核心特性
- 类 SQL 语法(HQL):支持类似 SQL 的查询语言,降低学习成本。
- 数据存储:数据以表形式存储在 HDFS 中,支持文本、Parquet、ORC 等格式。
- 元数据管理:通过元存储(Metastore)管理表结构、分区等信息。
- 扩展性:支持自定义函数(UDF)、SerDe(序列化/反序列化)等扩展功能。
3.Hive 的架构组件
- Driver:接收 HQL 查询,生成执行计划并协调任务执行。
- Compiler:解析 HQL,优化逻辑计划并转换为物理计划(如 MapReduce)。
- Metastore:存储表定义、分区等元数据,通常使用关系型数据库(如 MySQL)。
- Execution Engine:默认使用 MapReduce,也可配置为 Tez 或 Spark。
4.Hive 的数据模型
- 表(Table):逻辑概念,数据存储在 HDFS 的目录中。
- 分区(Partition):按列值划分数据,加速查询(如按日期分区)。
- 分桶(Bucket):对数据哈希分片,优化 JOIN 和采样效率。
5.Hive的局限性
- 延迟高:批处理模式不适合实时查询。
- 事务支持有限:早期版本不支持 ACID,新版本通过 Hive 3.x 改进。
6.Hive内部表和外部表
- 内部表(MANAGED_TABLE):Hive 全权管理数据和元数据
- 外部表(EXTERNAL_TABLE):Hive 只管理元数据,数据由 HDFS 管理
7.Hive元数据存储
默认有自己的数据开Derby,但是生产中常用mysql
hive存储的元数据是描述数据信息的数据,比如:
数据库、表名
字段名、字段类型
表是内部表还是外部表
分区信息(dt=20260401 这些)
数据在 HDFS 上的存储路径
分桶信息、存储格式(ORC/Parquet)
SerDe 信息
8.Hive的分区和分桶
分区
- 概念:
分区是一种将大型表的数据根据特定维度(通常是日期、地区、类别等列)物理划分到不同子目录中的技术。
- 目的:
优化查询性能: 当查询语句的WHERE子句包含分区键时,Hive 可以只扫描相关分区的目录,避免全表扫描,显著减少 I/O 和数据量。这称为分区裁剪。
数据管理: 便于按分区加载、删除或归档数据。
-- 创建分区表 CREATE TABLE logs ( id INT, user_id STRING, event_type STRING ) PARTITIONED BY (event_date STRING, region STRING) STORED AS ORC;- 注意事项:
避免分区过多(如按天分区持续多年可能导致成千上万分区),过多的小文件会降低 NameNode 性能和查询效率。
分区键应选择常用于WHERE过滤的列,且基数不宜过高。
分桶
- 概念:
分桶是将一个表或分区内的数据进一步划分为固定数量的文件(桶)。划分规则基于对分桶键(一列或多列)进行哈希取模计算。例如,一个分区内按user_id分成 4 个桶,Hive 会根据hash(user_id) % 4的结果将数据放入对应的桶文件。
- 目的:
高效采样:可以快速对特定桶进行随机采样,用于数据预览或测试。
优化 JOIN:如果两个表都以相同方式对 JOIN 键分桶,且桶数量相同或成倍数关系,Hive 可以执行高效的桶映射 JOIN。只需将对应桶的数据进行 JOIN,无需对整个表进行 Shuffle 和 Reduce。
优化聚合:对于GROUP BY分桶键的查询,可以更快完成。
更均匀的数据分布:有助于避免数据倾斜。
-- 创建分桶表 (通常先分区再分桶) CREATE TABLE user_actions ( user_id INT, action STRING, timestamp BIGINT ) PARTITIONED BY (event_date STRING) CLUSTERED BY (user_id) INTO 4 BUCKETS STORED AS ORC;分区 vs 分桶 总结
9.Hivesql的执行流程
- 解析与编译:首先,SQL 语句会被 Client 提交到 Driver。Driver 内部的解析器 (Parser) 会将 SQL 转换成抽象语法树 (AST)。然后,编译器 (Compiler)会去连接 Metastore,获取 user_log 表的元数据,进行语义分析,并将 AST 编译成一个由逻辑操作符组成的 DAG(有向无环图),也就是逻辑执行计划。
- 优化:接着,优化器 (Optimizer) 会对这个逻辑计划进行优化。针对写的 WHERE dt='2023-11-25' 这个条件,优化器会执行分区裁剪 (Partition Pruning),确保计算任务只去扫描 dt=2023-11-25 这个分区目录下的文件,极大地减少了 IO。优化后会生成物理执行计划。
- 提交与执行:执行器 (Executor) 将物理计划(一系列的 MapReduce 或 Spark 任务)提交给 YARN 的 ResourceManager (RM)。
- 资源分配与计算:RM 会在集群中找一个 NodeManager (NM) 启动 ApplicationMaster (AM)。AM 负责向 RM 申请计算所需的资源(Container),然后在这些 Container 里启动 Map 和 Reduce Task。这些 Task 会去 HDFS 上读取指定分区下的数据文件进行计算。
- 返回结果:所有 Task 计算完成后,最终的结果会汇总,由 Driver 获取并返回到我的客户端屏幕上。”
10.Hive的相关调优
切记:hive只是一个工具,提供类似sql的操作方式,本质是运行MR程序,因此,调优是在优化MR,而不是把mysql的优化拿过来。
主要从四个方面来说:
hive的建表设计,Hivesql本身的优化,Hive配置参数, 底层引擎MR方面的调整
调优注意事项
- 对于大数据计算引擎来说:数据量大不是问题,数据倾斜是个问题。
- Hive 的复杂 HQL 底层会转换成多个 MapReduce Job 并行或者串行执行,Job 数比较多的作业运行效率相对比较低,比如即使只有几百行数据的表,如果多次关联多次汇总,产生十几个 Job,耗时很长。原因是 MapReduce 作业初始化的时间是比较长的。
- 在进行 Hive 大数据分析时,常见的聚合操作比如 sum, count, max, min, UDAF 等,不怕数据倾斜问题,MapReduce 在 Mapper 阶段的预聚合操作,使数据倾斜不成问题。
- 好的建表设计,模型设计事半功倍。
- 设置合理的 MapReduce 的 Task 并行度,能有效提升性能。比如,10w + 数据量级别的计算,用 100 个reduceTask,那是相当的浪费,1 个足够,但是如果是亿级别的数据量,那么 1 个 Task 又显得捉襟见肘。
- 了解数据分布,自己动手解决数据倾斜问题是个不错的选择。这是通用的算法优化,但算法优化有时不能适应特定业务背景,开发人员了解业务,了解数据,可以通过业务逻辑精确有效的解决数据倾斜问题。
- 数据量较大的情况下,慎用 count (distinct),group by 容易产生倾斜问题。
- 对小文件进行合并,是行之有效的提高调度效率的方法,假如所有的作业设置合理的文件数,对任务的整体调度效率也会产生积极的正向影响。
- 优化时把握整体,单个作业最优不如整体最优。
- 优化 MySQL 的 SQL 优化技巧,是不适用在 Hive 的。
(1)hive的建表设计层面
①利用分区表优化
当一个hive表查询的大多数情况下都需要根据某个字段进行筛选时,那么非常时候将该字段作为分区字段进行分区
②利用分桶表优化
分桶表最大的作用就是帮助我们提高join查询以及采样。
select a.*, b.* from a join b on a.id = b.id;解决方案:
- 两张表都得按照这个连接字段来进行分桶
- 分桶数量要成倍数关系
③选择合适的存储格式
- 原始数据,一般来说,都是普通文本格式
- 计算的结果数据
- 如果这种结果数据,通常还要作为另外一种计算的输入,那么可以让当前这个结果数据的格式易于被读取(序列化文件格式可用)
- 如果这种结果数据,就是最终的结果了,那么看数据量的大小关系:
- 如果比较大:可以考虑压缩
- 如果不是特别大:直接普通文本格式,甚至存储在 RDBMS
- 计算引擎中应用程序在执行的时候:
- 中间临时数据:执行引擎的特性:列式存储 + 序列化存储文件格式
第一种: TextFile
- 存储方式:行存储。默认格式,如果建表时不指定默认为此格式。
- 每一行都是一条记录,每行都以换行符 “\n” 结尾。数据不做压缩时,磁盘会开销比较大,数据解析开销也比较大。
- 可结合 Gzip、Bzip2 等压缩方式一起使用(系统会自动检查,查询时会自动解压),推荐选用可切分的压缩算法。
第二种: Sequence File
- 一种 Hadoop API 提供的二进制文件,使用方便、可分割、可压缩的特点。
- 支持三种压缩选择:NONE、RECORD、BLOCK。RECORD 压缩率低,一般建议使用 BLOCK 压缩。
第三种: RC File
- 存储方式:数据按行分块,每块按照列存储 。
- A、首先,将数据按行分块,保证同一个 record 在一个块上,避免读一个记录需要读取多个 block。
- B、其次,块数据列式存储,有利于数据压缩和快速的列存取。
- 相对来说,RCFile 对于提升任务执行性能提升不大,但是能节省一些存储空间。可以使用升级版的 ORC 格式。
第四种: ORC File
- 存储方式:数据按行分块,每块按照列存储 。
- Hive 提供的新格式,属于 RCFile 的升级版,性能有大幅度提升,而且数据可以压缩存储,压缩快,快速列存取。
- ORC File 会基于列创建索引,当查询的时候会很快。
第五种:Parquet File
- 存储方式:列式存储。
- Parquet 对于大型查询的类型是高效的。对于扫描特定表格中的特定列查询,Parquet 特别有用。Parquet 一般使用 Snappy、Gzip 压缩。默认 Snappy。
- Parquet 支持 Impala 查询引擎。
- 表的文件存储格式尽量采用 Parquet 或 ORC,不仅降低存储量,还优化了查询,压缩,表关联等性能。
④选择合适的压缩格式
| 压缩格式 | 是否可拆分 | 是否自带 | 压缩率 | 速度 | 是否 hadoop 自带 |
|---|---|---|---|---|---|
| gzip | 否 | 是 | 很高 | 比较快 | 是 |
| lzo | 是 | 是 | 比较高 | 很快 | 否,要安装 |
| snappy | 否 | 是 | 比较高 | 很快 | 否,要安装 |
| bzip2 | 是 | 否 | 最高 | 慢 | 是 |
(2)Hive语法和运行参数
1. 查看 Hive 执行计划
Hive 的 SQL 语句在执行之前需要将 SQL 语句转换成 MapReduce 任务,因此需要了解具体的转换过程,可以在 SQL 语句中输入如下命令查看具体的执行计划。
查看执行计划,添加 extended 关键字可以查看更加详细的执行计划
explain [extended] query例子:
explain select department, count(*) as total from student where age >= 18 group by department order by total desc limit 3;关于 Hive 的执行计划中的 Operator 的概念: (逻辑执行计划: Operator Tree)
select ... from ... where ... group by ... having ... order by ..... limit .....一条 Hive SQL 写完,底层会被拆成一串 “操作步骤”,这一串步骤就叫 Operator Tree(算子树)
每个节点(例如select,group by)都是一个Operate
2. 列裁剪
列裁剪就是在查询时只读取需要的列,分区裁剪就是只读取需要的分区。当列很多或者数据量很大时,如果 select * 或者不指定分区,全列扫描和全表扫描效率都很低。
Hive 在读数据的时候,可以只读取查询中所需要用到的列,而忽略其他的列。这样做可以节省读取开销:中间表存储开销和数据整合开销。
set hive.optimize.cp = true; ## 列裁剪,取数只取查询中需要用到的列,默认是true3. 谓词下推
将 SQL 语句中的 where 谓词逻辑都尽可能提前执行,减少下游处理的数据量。对应逻辑优化器是PredicatePushDown。
set hive.optimize.ppd=true; ## 默认是true示例程序:
select a.*, b.* from a join b on a.id = b.id where b.age > 20; select a.*, c.* from a join (select * from b where age > 20) c on a.id = c.id;4. 分区裁剪
列裁剪就是在查询时只读取需要的列,分区裁剪就是只读取需要的分区。当列很多或者数据量很大时,如果select *或者不指定分区,全列扫描和全表扫描效率都很低。
在查询的过程中只选择需要的分区,可以减少读入的分区数目,减少读入的数据量。
Hive 中与分区裁剪优化相关的规则:
set hive.optimize.pruner=true; ## 默认是true在 HiveSQL 解析阶段对应的规则是ColumnPruner逻辑优化器。
示例:
select * from student where department = "AAAA";5. 合并小文件
大数据技术组件最怕的就是两个事儿:
- 存储组件:海量小文件SQL 示例:
1000000M = 100000 × 10M→ 100000 个小文件 - 计算组件:数据倾斜SQL 示例:
1000000M = 100000 × 10M→ 100000 个小文件,但是其中有两个超级大
如果一个 MapReduce Job 碰到一堆小文件作为输入,一个小文件会启动一个 Task。
在 MR 编程模型中,提供了一个叫做
CombineFileInputFormat的类:具备把一个节点甚至一个机架上的多个小文件,划分到同一个输入切片的能力。Hive 的默认
InputFormat:TextInputFormat
Map 输入合并
在执行 MapReduce 程序的时候,一般情况是一个文件的一个数据分块需要一个mapTask来处理。但是如果数据源是大量的小文件,这样就会启动大量的mapTask任
务,会浪费大量资源。可以将输入的小文件进行合并,从而减少mapTask任务数量。
Map 端输入、合并文件之后按照 block 的大小分割(默认) set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; Map 端输入,不合 set hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat;Map/Reduce 输出合并
大量的小文件会给 HDFS 带来压力,影响处理效率。可以通过合并 Map 和 Reduce 的结果文件来消除影响。
是否合并 Map 输出文件,默认值为 true set hive.merge.mapfiles=true; ## 是否合并 Reduce 端输出文件,默认值为 false set hive.merge.mapredfiles=true; ## 合并文件的大小,默认值为 256000000 = 256M set hive.merge.size.per.task=256000000; ## 每个 Map 最大分割大小 set mapred.max.split.size=256000000; ## 一个节点上 split 的最小值 set mapred.min.split.size.per.node=1; // 服务器节点 如果有多个节点,都是只有一个小文件,这种情况当中,意味着压根没合并 如果这个节点有 300 个 1M 的文件。指定的输入切片大小是:256M, 44M 的数据会传送给其他节点做合并 如果这个节点有 500 个 1M 的文件。指定的输入切片大小是:256M, 244M 的数据会传送给其他节点做合并 ## 一个机架上 split 的最少值 set mapred.min.split.size.per.rack=1; // 服务器机架6.合理设置Maptask并行度
7.合理设置ReduceTask并行度
8.join优化
Join 优化整体原则:
- 优先过滤后再进行 Join 操作,最大限度的减少参与 join 的数据量(where 能用就用)
- 小表 join 大表,最好启动 mapjoin,hive 自动启用 mapjoin,小表不能超过 25M,可以更改
- Join on 的条件相同的话,最好放入同一个 job,并且 join 表的排列顺序从小到大:
select a.*, b.*, c.* from a join b on a.id = b.id join c on a.id = c.i - 如果多张表做 join,如果多个链接条件都相同,会转换成一个 Job
优先过滤数据
尽量减少每个阶段的数据量,对于分区表要用上分区字段的尽量使用,同时只选择后面需要使用到的列,最大限度的减少参与 Join 的数据量。
小表 join 大表原则
小表 join 大表的时应遵守小表 join 大表原则,原因是 join 操作的 reduce 阶段,位于 join 左边的表内容会被加载进内存,将条目少的表放在左边,可以有效减少发生内存溢出的几率。join 中执行顺序是从左到右生成 Job,应该保证连续查询中的表的大小从左到右是依次增加的。
使用相同的连接键
在 hive 中,当对 3 个或更多张表进行 join 时,如果 on 条件使用相同字段,那么它们会合并为一个 MapReduce Job,利用这种特性,可以将相同的 join on 放入一个 job 来节省执行时间。
尽量原子操作
尽量避免一个 SQL 包含复杂的逻辑,可以使用中间表来完成复杂的逻辑。
大表 Join 大表
- 空 key 过滤:有时 join 超时是因为某些 key 对应的数据太多,而相同 key 对应的数据都会发送到相同的 reducer 上,从而导致内存不够。此时我们应该仔细分析这些异常的 key,很多情况下,这些 key 对应的数据是异常数据,我们需要在 SQL 语句中进行过滤。
- 空 key 转换:有时虽然某个 key 为空对应的数据很多,但是相应的数据不是异常数据,必须要包含在 join 的结果中,此时我们可以表 a 中 key 为空的字段赋一个随机的值,使得数据随机均匀地分布到不同的 reducer 上。
9.启用 MapJoin
关于 Hive 调优中,“能用就用” 的原则和方法:
- where —— 能用就用
- mapjoin —— 资源允许的情况下
- 局部聚合 combiner —— 不改变业务结果的情况下
MapJoin 是将 join 双方比较小的表直接分发到各个 map 进程的内存中,在 map 进程中进行 join 操作,这样就不用进行 reduce 步骤,从而提高了速度。只有 join 操作才能启用 MapJoin。
# 是否根据输入小表的大小,自动将reduce端的common join 转化为map join,将小表刷入内存中。 # 对应逻辑优化器是MapJoinProcessor set hive.auto.convert.join = true; # 刷入内存表的大小(字节) 25M = 2G set hive.mapjoin.smalltable.filesize = 25000000; # hive会基于表的size自动的将普通join转换成mapjoin set hive.auto.convert.join.noconditionaltask=true; # 多大的表可以自动触发放到内层LocalTask中,默认大小10M set hive.auto.convert.join.noconditionaltask.size=10000000;Hive 可以进行多表 Join。Join 操作尤其是 Join 大表的时候代价是非常大的。MapJoin 特别适合大小表 join 的情况。在 Hive join 场景中,一般总有一张相对小的表和一张相对大的表,小表叫 build table,大表叫 probe table。
Hive 在解析带 join 的 SQL 语句时,会默认将最后一个表作为 probe table,将前面的表作为 build table 并试图将它们读进内存。如果表顺序写反,probe table 在前面,引发 OOM 的风险就高了。在维度建模数据仓库中,事实表就是 probe table,维度表就是 build table。这种 Join 方式在 map 端直接完成 join 过程,消灭了 reduce,效率很高。而且 MapJoin 还支持非等值连接。
当 Hive 执行 Join 时,需要选择哪个表被流式传输(stream),哪个表被缓存(cache)。Hive 将 JOIN 语句中的最后一个表用于流式传输,因此我们需要确保这个流表在两者之间是最大的。如果要在不同的 key 上 join 更多的表,那么对于每个 join 集,只需在 ON 条件右侧指定较大的表。
也可以手动开启 mapjoin:
-- SQL方式,在SQL语句中添加MapJoin标记(mapjoin hint) -- 将小表放到内存中,省去shuffle操作 -- 在没有开启mapjoin的情况下,执行的是reduceJoin SELECT /*+ MAPJOIN(smallTable) */ smallTable.key, bigTable.value FROM smallTable JOIN bigTable ON smallTable.key = bigTable.key; /*+mapjoin(a,b,c)*/ /*+mapjoin(a)*/10.Sort-Merge-Bucket(SMB) Map Join
它是另一种 Hive Join 优化技术,使用这个技术的前提是所有的表都必须是分桶表(bucket)和分桶排序的(sort)。分桶表的优化!
distribute by .... sort by ....具体实现:
- 针对参与 join 的这两张做相同的 hash 散列,每个桶里面的数据还要排序
- 这两张表的分桶个数要成倍数。
- 开启 SMB join 的开关!
一些常见参数设置:
-- 当用户执行bucket map join的时候,发现不能执行时,禁止查询 set hive.enforce.sortmergebucketmapjoin=false; -- 如果join的表通过sort merge join的条件,join是否会自动转换为sort merge join set hive.auto.convert.sortmerge.join=true; -- 当两个分桶表 join 时,如果 join on的是分桶字段,小表的分桶数是大表的倍数时,可以启用 mapjoin 来提高效率。 -- bucket map join优化,默认值是 false set hive.optimize.bucketmapjoin=false; -- bucket map join 优化,默认值是 false set hive.optimize.bucketmapjoin.sortedmerge=false;