目录
- 背景
- 第一部分 静态分区
- 第二部分 动态分区
- 第三部分 两者的比较
- 第四部分 动态分区使用的问题
- 参考文献及资料
背景
在Hive
中有两种类型的分区:静态分区(Static Partitioning)和动态分区(Dynamic Partitioning)。
- 静态分区。对于静态分区,从字面就可以理解:表的分区数量和分区值是固定的。
- 动态分区。会根据数据自动的创建新的分区。
本文会详细介绍两种分区方法、使用场景以及生产中常见问题和解决方法。
第一部分 静态分区
静态分区的使用场景主要是分区的数量是确定的。例如人力资源信息表中使用“部门”作为分区字段,通常一段时间是静态不变的。例如:
1 | CREATE EXTERNAL TABLE employee_dept ( |
上面的外部表以dept_name
字段为分区字段,然后导入数据需要指定分区。
第二部分 动态分区
通常在生产业务场景中,我们使用的都是灵活的动态分区。例如我们使用时间字段(天、小时)作为分区字段。新的数据写入会自动根据最新的时间创建分区并写入对应的分区。例如下面的例子:
1 | hive > insert overwrite table order_partition partition (year,month) select order_id, order_date, order_status, substr(order_date,1,4) year, substr(order_date,5,2) month from orders; |
写入报错。这是因为Hive默认配置不启用动态分区,需要使用前开启配置。开启的方式有两种:
在Hive服务配置文件中全局配置;
每次交互时候进行配置(只影响本次交互);
通常我们生产环境使用第二种。
1 | set hive.exec.dynamic.partition=true; |
其中参数hive.exec.dynamic.partition.mode
表示动态分区的模式。默认是strict
,表示必须指定至少一个分区为静态分区,nonstrict
模式表示允许所有的分区字段都可以使用动态分区。
第三部分 两者的比较
两种分区模式都有各自的使用场景,我们总结如下:
静态分区(Static Partitioning) | 动态分区(Dynamic Partitioning) | |
---|---|---|
分区创建 | 数据插入分区之前,需要手动创建每个分区 | 根据表的输入数据动态创建分区 |
适用场景 | 需要提前知道所有分区。适用于分区定义得早且数量少的用例 | 有很多分区,无法提前预估新分区,动态分区是合适的 |
另外动态分区的值是MapReduce
任务在reduce
运行阶段确定的,也就是所有的记录都会distribute by
,相同字段(分区字段)的map
输出会发到同一个reduce
节点去处理,如果数据量大,这是一个很弱的运行性能。而静态分区在编译阶段就确定了,不需要reduce
任务处理。所以如果实际业务场景静态分区能解决的,尽量使用静态分区即可。
第四部分 动态分区使用的问题
Hive
表中分区架构,使得数据按照分区分别存储在HDFS
文件系统的各个目录中,查询只要针对指定的目录集合进行查询,而不需要全局查找,提高查询性能。
但是分区不是”银弹”,如果分区数据过多,就会在HDFS
文件系统中创建大量的目录和文件,对于集群NameNode
服务是有性能压力的,NameNode
需要将大量元数据信息保留在内存中。另外大分区表在用户查询时候由于分析size
太大,也容易造成Metastore
服务出现OMM
报错。
上面两个现象均在生产环境发生,分别造成NameNode
和Metastore
不可用。
事实上,Hive
为了防止异常生产大量分区,甚至默认动态分区是关闭的。另外对于生成动态分区的数量也做了性能默认限制。
4.1 动态分区创建限制
当我们在一个Mapreduce
任务(hive
写入会编译成mapreduce
任务)中创建大量分区的时候,经常会遇到下面的报错信息:
1 | 2015-06-15 17:27:44,614 ERROR [LocalJobRunner Map Task Executor #0]: mr.ExecMapper (ExecMapper.java:map(171)) - org.apache.hadoop.hive.ql.metadata.HiveException: Hive Runtime Error while processing row .... |
这个报错就是因为Hive对于动态分区创建的限制,涉及的参数有:
1 | hive.exec.max.dynamic.partitions = 1000; |
hive.exec.max.dynamic.partitions.pernode
,参数限制MapReduce
任务单个任务(mapper或reducer任务)创建的分区数量为100;hive.exec.max.dynamic.partitions
,参数限制单次整体任务创建分区的数量上限为1000个;hive.exec.max.created.files
,参数限制所有单次整体map和reduce任务创建的最大文件数量上限为10000个;
以上三个阀值超过就会触发错误,集群会杀死任务。为了解决报错,我们通常将两个参数调大。但是也需要用户对自己的Hive表的分区数量进行合理规划,避免过多的分区。
4.2 特殊分区
如果动态分区列输入的值为NULL或空字符串,则Hive将该行将放入一个特殊分区,其名称由参数hive.exec.default.partition.name
控制。默认值为__HIVE_DEFAULT_PARTITION__
。
用户可以使用(查看表分区)命令进行查看:
1 | show partitions 'table名称'; |
有时候异常生产这些分区数据,需要进行清理。如果使用下面的语句:
1 | ALTER TABLE Table_Name DROP IF EXISTS PARTITION(process_date='__HIVE_DEFAULT_PARTITION__'); |
这时候Hive会报错:
1 | Error: Error while compiling statement: FAILED: SemanticException Unexpected unknown partitions for (process_date = null) (state=42000,code=40000) |
这是Hive一个已知bug(编号:HIVE-11208),在Hive 2.3.0
版本修复。
但是有个有修复方法(不建议在生产环境中实施):
1 | -- update the column to be "string" |
4.3 乱码分区字段
有时候表分区字段由于处理不当,会出现乱码分区,例如:
1 | hp_stat_time=r_ready%3D91;r_load%3D351 |
原因是Hive会自动对一些UTF-8
字符编码成Unicode
(类似网址中中文字符和一些特殊字符的编码处理)。此处%3D
解码后是’=’。可以使用在线转换进行解码:https://www.matools.com/code-convert-utf8。
最后使用解码后的字段即可(注意分号转义):
1 | alter table dpdw_traffic_base drop partition(hp_stat_time='r_ready=91\;r_load=351'); |
参考文献及资料
1、动态分区,链接:https://cwiki.apache.org/confluence/display/Hive/DynamicPartitions
2、Hive Tutorial,链接:https://cwiki.apache.org/confluence/display/Hive/Tutorial
3、Apache Hive 中文手册,链接:https://www.docs4dev.com/docs/zh/apache-hive/3.1.1/reference