写在前面
前几天发的大数据组件之Hive(Hive学习一篇就够了),其访问量和点赞数很多,这次打算更新一点关于hive的语句优化和关于hive的一些配置的文章,希望本文能帮助大数据小白和刚从事大数据开发的你一些帮助。希望大家持续关注,我会继续更新更多的文章。
一、概述
首先。我们为啥要进行优化。从事大数据开发的人员都体验过这样的感觉。当我们写完一个查询指令。当我们按下回车的时候,我们等到map的进度条到99%时,系统就像卡住一样。就是出不来结果。这个时候,我们就知道出现了数据倾斜了。这个时候,我们就要对于语句进行响应的优化。下面我将从多个方面对于数据倾斜等hive优化问题进行解析。
1.1 数据倾斜
首先。我们需要弄清楚什么是数据倾斜,百度百科给出如下的定义:
简单来说,是由于数据特征或者数据方式不合适,导致某个处理节点处理太多的数据,导致其他数据节点处理数据量过少,进而导致整个集群数据处理效率很低
1.2 MapReduce
我们需要知道的是,我们使用的hive虽然用的SQL语句,hive是将我们写的SQL语句通过抽象语法树转换为MapReduce的。
关于MapReduce,百度百科是这样定义的
MapReduce通俗来说就是将数据进行切片,在多个计算节点进行计算处理,这样就可以完成大量的数据的处理,可以解决单个机器的计算性能的瓶颈
二、产生原因
根据数据处理的过程中,我将数据倾斜分为以下原因
- key分布不均匀
- 业务数据本身的特性
- 建表时考虑不周
- 某些SQL语句本身就有数据倾斜
三、解决方案和避免方案
3.1 Hive语句初始化配置
3.1.1 join过程的配置
#默认join键对应的记录数超过该值则进行倾斜分析
set hive.skewjoin.key=100000;
#默认fa1se,如果join键倾斜则设为true
set hive.optimize.skewjoin=true;
#默认10000,倾斜处理mapper数量上限
set hive.skewjoin.mapjoin.map.tasks=10000;
#默认32M,倾斜最小切片大小
set hive.skewjoin.mapjoin.min.split=32M;
3.1.2 map join过程的设置
#默认true
set hive.auto.convert.join=true;
#默认小表<=25M 300M
set hive.mapjoin.sma71table.filesize=25M;
#默认false,分桶表表mapjoin专用
set hive.optimize.bucketmapjoin=true;
3.1.3 combiner过程
set hive.map.aggr=true;#默认true
3.1.4 group by 过程
#倾斜的倍数n = skew_data_avg/other_data_avg
set mapred.reduce.tasks=n;
#默认false
set hive.groupby . skewindata=true;
3.1.5 map 或者reduce 过程
#默认为false,mapreduce输出是否合并
set hive.merge.mapredfiles=true;
#默认256M,合并文件上限
set hive.merge.size.per.task=256M;
#默认16M,文件小于该阈值则合并
set hive.merge.sma11files.avgsize=16M;
3.1.6 mapper 设置
#默认为2,只有大于default_num时才会生效
set mapred .map.tasks=2;
#默认128M
set dfs.b1ock.size=128M;
#默认单个Mapper处理数据上限256M
set mapred .max.split.size=256M;
#默认1字节
set mapred.min.split.s1Ze=1;
#默认单个节点处理的数据下限1字节
set mapred.min.split.size.per.node=1;
#默认单个机架处理的数据下限1字节
set mapred.min.split.size.per.rack=1;
3.1.7 reducer设置
#默认-1,可以根据需要在客户端设置
set mapred .reduce.tasks=1;
#默认每个Reducer的数据为256M
set hive.exec.reducers.bytes.per.reducer=256M;
#默认单个任务最大Reducer数量
set hive.exec.reducers.max=1009;
3.1.8 存储与压缩格式
#默认false,任务输出是否压缩
set hive.exec.compress.output=true;
#默认false,任务过程输出是否压缩
set hive.exec.compress.intermediate=true;
#默认mr,tezl spark DAG
set hive.execution.engine=tez;
#默认为true
set hive.optimize.ppd=true;
#默认true
set hive.cbo.enab7e=true;
3.1.9动态分区
#默认开启
set hive.exec.dynamic.partition=true;
#默认strict
set hive.exec.dynamic.partition.mode=nonstrict;
#默认最大动态分区数1000
set hive.exec.max.dynamic.partitions=1000;
#默认单节点最大动态分区数100
set hive.exec.max.dynamic.partitions .pernode=100;
3.1.10并行执行job
#默认false(关闭)
set hive.exec.paral7le1=true;
#默认8,最大并行任务数
set hive.exec.para1le7.thread. number=8;
#每个jvm运行的任务数
mapreduce.job.jvm.numtasks = 8;
3.1.11 本地化运算
#默认1,启动本地化模式reducer数量必须为O|1
set mapred .reduce.tasks=0/1;
#默认4,本地化文件数量上限
set hive.exec.mode.local. auto.input.files.max=4;
#默认128M,本地化文件大小上限
set hive.exec.mode.loca1.auto.inputbytes.max=128M;
#默认fa1se,hive决定是否启用本地化模式
set hive.exec.mode.loca7.auto=true;
#默认more,简单查询不走mr,直接提取
set hive.fetch.task.conversion=more;
3.2 建表层面
在建表之前,我们可以通过建立分区表或者分桶将数据进行分区分桶进而使得数据进行分散。下面我将介绍分桶表和分区表的建立。
3.2.1 创建分区表
在建立数据表的时候,我们可以通过建立分区表,按照某个字段对于数据用分区表进行存储
create [temporary][external] table [if not exits] [dbname.]tabname
[(
colname data_type [comment col_comment],
...,
[constraint_specification]
)]
[comment table_comment]
#创建分区表
[partitioned by (extrenal_colname data_type [comment col_comment],...)]
[skewed by (colname,colname,...) on (colvalue,...),(colvalue,...),...][stored as directories]
[rowformat row_format]
[stored as file_format| stored by 'storge.handler.classname'[with serdeproperrties]]
[LOCATION hdfs_path]
[TBL PROPERTIES (property_name=value, ...)]
[AS select_statement]
#创建分区表,向分区表插入数据时,需要增加分区字段
create external table kb16.user_movie_rating_par(
userid bigint,
movieid bigint,
rating decimal(2,1),
`timestamp` bigint
)
partitioned by (dt string)
row format delimited
fields terminated by ',';
分区表的建立值关注分区字段,在插入数据的时候,需要增加一个分区字段,为了能够很好的进行分区,我们通常采取时间进行分区。
3.2.2 创建分桶表
create [temporary][external] table [if not exits] [dbname.]tabname
[(
colname data_type [comment col_comment],
...,
[constraint_specification]
)]
[comment table_comment]
#创建分桶表
[clustered by (colname,...)[sorted by (colname ASC|DESC,...)] into num_buckets buckets]
[skewed by (colname,colname,...) on (colvalue,...),(colvalue,...),...][stored as directories]
[rowformat row_format]
[stored as file_format| stored by 'storge.handler.classname'[with serdeproperrties]]
[LOCATION hdfs_path]
[TBL PROPERTIES (property_name=value, ...)]
[AS select_statement]
#创建分桶表
create external table user_movie_ratin_par_bucket(
userid bigint,
movieid bigint,
rating decimal(2,1),
`timestamp` bigint,
)
clustered by (`timestamp`) sorted by (`timestamp` ASC) into 5 buckets
row format delimited fields terminated by ',';
3.2.3 指定存储格式
常见的存储格式有:TextFile、 Sequence File、RC File、ORC File和Parquet File。我们一般都使用TextFlies和Parent进行存储,当创建宽表时,建议采取ORC、ParquetFile 这些列式存储格式。
3.2.3.1TextFile
1、存储方式:行存储。默认格式,如果建表时不指定默认为此格式。
2、每一行都是一条记录,每行都以换行符"\n"结尾。数据不做压缩时,磁盘会开销比较大,数据解析开销也比较大。
3、可结合Gzip、Bzip2等压缩方式一起使用(系统会自动检查,查询时会自动解压),推荐选用可切分的压缩算法。
3.2.3.2 Sequence File
1、一种Hadoop API提供的二进制文件,使用方便、可分割、个压缩的特点。
2、支持三种压缩选择:NONE、RECORD、BLOCK。RECORD压缩率低,一般建议使用BLOCK压缩。
3.2.3.3 RC File
1、存储方式:数据按行分块,每块按照列存储 。
2、相对来说,RCFile对于提升任务执行性能提升不大,但是能节省一些存储空间。可以使用升级版的ORC格式。
3.2.3.4 ORC File
1、存储方式:数据按行分块,每块按照列存储
2、Hive提供的新格式,属于RCFile的升级版,性能有大幅度提升,而且数据可以压缩存储,压缩快,快速列存取。
3、ORC File会基于列创建索引,当查询的时候会很快。
3.2.3.5 Parquet File
1、存储方式:列式存储。
2、Parquet对于大型查询的类型是高效的。对于扫描特定表格中的特定列查询,Parquet特别有用。Parquet一般使用Snappy、Gzip压缩。默认Snappy。
3、Parquet支持Impala查询引擎。
4、表的文件存储格式尽量采用Parquet或ORC,不仅降低存储量,还优化了查询,压缩,表关联等性能。
3.2.4数据压缩模式
3.2.4.1 常见压缩格式
压缩格式 | 是否可拆分 | 是否自带 | 压缩率 | 速度 | 是否hadoop自带 |
---|---|---|---|---|---|
gzip | 否 | 是 | 很高 | 比较快 | 是 |
lzo | 是 | 是 | 比较高 | 很快 | 否,要安装 |
snappy | 否 | 是 | 比较高 | 很快 | 否,要安装 |
bzip2 | 是 | 否 | 最高 | 慢 | 是 |
3.2.4.2 压缩包
压缩格式 | 类 |
---|---|
zlib | org.apache.hadoop.io.compress.DefaultCodec |
gzip | org.apache.hadoop.io.compress.GzipCodec |
bzip2 | org.apache.hadoop.io.compress.Bzip2Codec |
lzo | org.apache.hadoop.io.compress.lzo.LzoCodec |
lz4 | org.apache.hadoop.io.compress.LzoCodec |
snappy | org.apache.hadoop.io.compress.SnappyCodec |
3.3 HQL语法与运行层面
3.3.1 查看SQL语句的执行计划
#查看HQL的执行计划
explain HQL;
3.3.2 列裁剪
写HQL语句时,不能直接使用select * from table ;
这条指令会进行全表查询,查询时间和内存开销很大。就算我们进行全表查询,我们也尽量不要使用select *
。
#列裁剪,取数只取查询中需要用到的列,默认是true
set hive.optimize.cp = true;
3.3.3 谓语下推
在写HQL指令时,尽量利用子查询或者join进行缩小数据规模,最后在写where指令,这样可以减少指令的筛选负担。
简单来说,就是在where之前尽量降低数据量
3.3.4合并小文件
众所周知,当小文件过多时,也会导致hive在执行时发生数据倾斜的效果。这个产生的原因就是造成开辟的map数量偏多,map的开启和关闭都会造成大量的资源消耗,进而可以进行小文件合并,进而减少mapTask数量。
3.3.4.1 map端的临时设置
# 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;
3.3.4.2 Map/Reduce输出合并
对于hadoop来说,其对于小文件的存储不适合存储小文件,会造成HDFS的过大的压力。因此在输出时,需要对数据进行合并。
# 是否合并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; // 服务器机架