hive的数据压缩/数据存储/调优

161 阅读12分钟

#博学谷IT学习技术支持#

hive的数据压缩

在实际工作当中,hive当中处理的数据,一般都需要经过压缩,可以使用压缩来节省我们的MR处理的网络带宽

  MR支持的压缩编码

压缩格式工具算法文件扩展名是否可切分
DEFAULTDEFAULT.deflate
GzipgzipDEFAULT.gz
bzip2bzip2bzip2.bz2
LZOlzopLZO.lzo
LZ4LZ4.lz4
SnappySnappy.snappy

为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器,如下表所示

压缩格式对应的编码/解码器
DEFLATEorg.apache.hadoop.io.compress.DefaultCodec
gziporg.apache.hadoop.io.compress.GzipCodec
bzip2org.apache.hadoop.io.compress.BZip2Codec
LZOcom.hadoop.compression.lzo.LzopCodec
LZ4org.apache.hadoop.io.compress.Lz4Codec
Snappyorg.apache.hadoop.io.compress.SnappyCodec

压缩性能的比较

压缩算法原始文件大小压缩文件大小压缩速度解压速度
gzip8.3GB1.8GB17.5MB/s58MB/s
bzip28.3GB1.1GB2.4MB/s9.5MB/s
LZO8.3GB2.9GB49.3MB/s74.6MB/s
Snappy8.3GB3G250MB/S500 MB/s

google.github.io/snappy/

  • On a single core of a Core i7 processor in 64-bit mode, Snappy compresses at about 250 MB/sec or more and decompresses at about 500 MB/sec or more.

  压缩配置参数

要在Hadoop中启用压缩,可以配置如下参数(mapred-site.xml文件中):

参数默认值阶段建议
io.compression.codecs(在core-site.xml中配置)org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec,org.apache.hadoop.io.compress.Lz4Codec输入压缩Hadoop使用文件扩展名判断是否支持某种编解码器
mapreduce.map.output.compressfalsemapper输出这个参数设为true启用压缩
mapreduce.map.output.compress.codecorg.apache.hadoop.io.compress.DefaultCodecmapper输出使用LZO、LZ4或snappy编解码器在此阶段压缩数据
mapreduce.output.fileoutputformat.compressfalsereducer输出这个参数设为true启用压缩
mapreduce.output.fileoutputformat.compress.codecorg.apache.hadoop.io.compress. DefaultCodecreducer输出使用标准工具或者编解码器,如gzip和bzip2
mapreduce.output.fileoutputformat.compress.typeRECORDreducer输出SequenceFile输出使用的压缩类型:NONE和BLOCK

  开启Map输出阶段压缩

开启map输出阶段压缩可以减少job中map和Reduce task间数据传输量。具体配置如下:

案例实操:

  1. 开启hive中间传输数据压缩功能
hive ( default )> set hive . exec . compress . intermediate=true ; 
  1. 开启mapreduce中map输出压缩功能
hive  ( default )> set mapreduce . map . output . compress=true ; 
  1. 设置mapreduce中map输出数据的压缩方式
hive  ( default )> set mapreduce . map . output . compress . codec= org . apache . hadoop . io . compress . SnappyCodec
  1. 执行查询语句
select count ( 1 )  from score ; 

  开启Reduce输出阶段压缩

当Hive将输出写入到表中时,输出内容同样可以进行压缩。属性hive.exec.compress.output控制着这个功能。用户可能需要保持默认设置文件中的默认值false,这样默认的输出就是非压缩的纯文本文件了。用户可以通过在查询语句或执行脚本中设置这个值为true,来开启输出结果压缩功能。

案例实操:

- 1)开启hive最终输出数据压缩功能
set hive .exec.compress.output=true ; -- 2)开启mapreduce最终输出数据压缩
set mapreduce .output. fileoutputformat .compress=true ; -- 3)设置mapreduce最终数据输出压缩方式
set mapreduce .output. fileoutputformat .compress. codec = org . apache . hadoop . io .compress. SnappyCodec ; -- 4)设置mapreduce最终数据输出压缩为块压缩
set mapreduce .output. fileoutputformat .compress. type=BLOCK ; -- 5)测试一下输出结果是否是压缩文件
insert overwrite local directory '/export/data/compress' select  *  from score distribute by sid sort by sscore desc; 

hive的数据存储格式

  • Hive支持的存储数的格式主要有:TEXTFILE(行式存储) 、SEQUENCEFILE(行式存储)、ORC(列式存储)、PARQUET(列式存储)。

  列式存储和行式存储

行存储的特点 查询满足条件的一整行数据的时候,列存储则需要去每个聚集的字段找到对应的每个列的值,行存储只需要找到其中一个值,其余的值都在相邻地方,所以此时行存储查询的速度更快。

列存储的特点: ****因为每个字段的数据聚集存储,在查询只需要少数几个字段的时候,能大大减少读取的数据量;每个字段的数据类型一定是相同的,列式存储可以针对性的设计更好的设计压缩算法。

相比于行式存储,列式存储在分析场景下有着许多优良的特性:

1)分析场景中往往需要读大量行但是少数几个列。在行存模式下,数据按行连续存储,所有列的数据都存储在一个block中,不参与计算的列在IO时也要全部读出,读取操作被严重放大。而列存模式下,只需要读取参与计算的列即可,极大的减低了IO开销,加速了查询。

2)同一列中的数据属于同一类型,压缩效果显著。列存储往往有着高达十倍甚至更高的压缩比,节省了大量的存储空间,降低了存储成本。

3)更高的压缩比意味着更小的数据空间,从磁盘中读取相应数据耗时更短。

4)自由的压缩算法选择。不同列的数据具有不同的数据类型,适用的压缩算法也就不尽相同。可以针对不同列类型,选择最合适的压缩算法。

TEXTFILE和SEQUENCEFILE的存储格式都是基于行存储的;

ORC和PARQUET是基于列式存储的。

  主流文件存储格式对比实验

从存储文件的压缩比和查询速度两个角度对比。

存储文件的压缩比测试:

1)TextFile

(1)创建表,存储数据格式为TEXTFILE

create table log_text  ( track_time string , url string , session_id string , referer string , ip string , end_user_id string , city_id string
 ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE  ; 

(3)向表中加载数据

load data local inpath '/export/data/hivedatas/log.data' into table log_text  ; 
  1. 查看表中数据大小
hadoop fs  -du -h /user/hive/warehouse/myhive.db/log_text;    

18.1 M /user/hive/warehouse/log_text/log.data

2)ORC

(1)创建表,存储数据格式为OR

create table log_orc ( track_time string , url string , session_id string , referer string , ip string , end_user_id string , city_id string
 ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS orc  ; 
  1. 向表中加载数据
insert into table log_orc select  *  from log_text  ; 
  1. 查看表中数据大小
hadoop fs  -du -h  /user/ hive / warehouse / myhive . db / log_orc ; 

2.8 M /user/hive/warehouse/log_orc/123456_0

3)Parquet

(1)创建表,存储数据格式为parquet

create table log_parquet ( track_time string , url string , session_id string , referer string , ip string , end_user_id string , city_id string
 ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS PARQUET  ; 
  1. 向表中加载数据
insert into table log_parquet select  *  from log_text  ; 
  1. 查看表中数据大小
hdoop fs  -du -h  /user/ hive / warehouse / myhive . db / log_parquet ; 

13.1 M /user/hive/warehouse/log_parquet/123456_0

存储文件的压缩比总结:

ORC > Parquet > textFile

存储文件的查询速度测试:

1)TextFile
hive  ( default )>  select count(*)  from log_text ; _c0
100000
Time taken :  21.54 seconds ,  Fetched :  1 row( s ) 2)ORC
hive  ( default )>  select count(*)  from log_orc ; _c0
100000
Time taken :  20.867 seconds ,  Fetched :  1 row( s ) 3)Parquet
hive  ( default )>  select count(*)  from log_parquet ; _c0
100000
Time taken :  22.922 seconds ,  Fetched :  1 row( s ) 

存储文件的查询速度结:

ORC > TextFile > Parquet

  存储和压缩结合

ORC存储方式的压缩:

KeyDefaultNotes
orc.compressZLIBhigh level compression (one of NONE, ZLIB, SNAPPY)
orc.compress.size262,144number of bytes in each compression chunk
orc.stripe.size67,108,864number of bytes in each stripe
orc.row.index.stride10,000number of rows between index entries (must be >= 1000)
orc.create.indextruewhether to create row indexes
orc.bloom.filter.columns""comma separated list of column names for which bloom filter should be created
orc.bloom.filter.fpp0.05false positive probability for bloom filter (must >0.0 and <1.0)

1)创建一个非压缩的的ORC存储方式

(1)建表语句

create table log_orc_none ( track_time string , url string , session_id string , referer string , ip string , end_user_id string , city_id string
 ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS orc tblproperties  ( "orc.compress"="NONE" ); 
  1. 插入数据
insert into table log_orc_none select  *  from log_text  ; 
  1. 查看插入后数据
hadoop fs -du -h  /user/ hive / warehouse / myhive . db / log_orc_none ; 

7.7 M /user/hive/warehouse/log_orc_none/123456_0

2)创建一个SNAPPY压缩的ORC存储方式

(1)建表语句

create table log_orc_snappy ( track_time string , url string , session_id string , referer string , ip string , end_user_id string , city_id string
 ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS orc tblproperties  ( "orc.compress"="SNAPPY" ); 
  1. 插入数据
insert into table log_orc_snappy select  *  from log_text  ; 
  1. 查看插入后数据
hadoop fs  -du -h  /user/ hive / warehouse / myhive . db / log_orc_snappy  ; 

3.8 M /user/hive/warehouse/log_orc_snappy/123456_0

3)上一节中默认创建的ORC存储方式,导入数据后的大小为

2.8 M /user/hive/warehouse/log_orc/123456_0

比Snappy压缩的还小。原因是orc存储文件默认采用ZLIB压缩。比snappy压缩的小。

4)存储方式和压缩总结:

在实际的项目开发当中,hive表的数据存储格式一般选择:orc或parquet。压缩方式一般选择snappy。

orc + snappy

hive调优

  本地模式

大多数的Hadoop Job是需要Hadoop提供的完整的可扩展性来处理大数据集的。不过,有时Hive的输入数据量是非常小的。在这种情况下,为查询触发执行任务时消耗可能会比实际job的执行时间要多的多。对于大多数这种情况,Hive可以通过本地模式在单台机器上处理所有的任务。对于小数据集,执行时间可以明显被缩短。

用户可以通过设置hive.exec.mode.local.auto的值为true,来让Hive在适当的时候自动启动这个优化。

set hive.stats.column.autogather=false;
set hive .exec.mode.local. auto=true ;   --开启本地mr
--设置local mr的最大输入数据量,当输入数据量小于这个值时采用local  mr的方式,默认为134217728,即128M
set hive .exec.mode.local. auto . inputbytes .max=51234560 ; --设置local mr的最大输入文件个数,当输入文件个数小于这个值时采用local mr的方式,默认为4
set hive .exec.mode.local. auto .input. files .max=10 ; 

案例实操:

-1)开启本地模式,并执行查询语句
hive  ( default )>  set hive .exec.mode.local. auto=true ; hive  ( default )>  select  *  from score cluster by sid ; 18 rows selected  ( 1.568 seconds ) 
--2)关闭本地模式,并执行查询语句
hive  ( default )>  set hive .exec.mode.local. auto=false ;  
hive  ( default )>  select  *  from score cluster by sid ; 18 rows selected  ( 11.865 seconds ) 

  空key处理

    空KEY过滤

有时join超时是因为某些key对应的数据太多,而相同key对应的数据都会发送到相同的reducer上,从而导致内存不够。此时我们应该仔细分析这些异常的key,很多情况下,这些key对应的数据是异常数据,我们需要在SQL语句中进行过滤。例如key对应的字段为空,操作如下:

环境准备:

create table ori ( id bigint,  time_val bigint,  uid string ,  keyword string ,  url_rank int ,  click_num int ,  click_url string )  row format delimited fields terminated by '\t' ; 
create table nullidtable ( id bigint,  time_val bigint,  uid string ,  keyword string ,  url_rank int ,  click_num int ,  click_url string )  row format delimited fields terminated by '\t' ; 
create table jointable ( id bigint,  time_valbigint,  uid string ,  keyword string ,  url_rank int ,  click_num int ,  click_url string )  row format delimited fields terminated by '\t' ; 
load data local inpath '/export/data/hivedatas/hive_big_table/*' into table ori ;  
load data local inpath '/export/data/hivedatas/hive_have_null_id/*' into table nullidtable ; 

不过滤:

INSERT OVERWRITE TABLE jointable
SELECT a .*  FROM nullidtable a JOIN ori b ON a . id = b . id ; 结果:
No rows affected  ( 152.135 seconds ) 

过滤:

INSERT OVERWRITE TABLE jointable
SELECT a .*  FROM  ( SELECT  *  FROM nullidtable WHERE id IS NOT NULL  )  a JOIN ori b ON a . id = b . id ; 结果:
No rows affected  ( 141.585 seconds ) 

空key转换

有时虽然某个key为空对应的数据很多,但是相应的数据不是异常数据,必须要包含在join的结果中,此时我们可以表a中key为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的reducer上。例如:

不随机分布:

set hive .exec. reducers . bytes . per . reducer=32123456 ; set mapreduce . job . reduces=7 ; INSERT OVERWRITE TABLE jointable
SELECT a .* FROM nullidtable a
LEFT JOIN ori b ON CASE WHEN a . id IS NULL THEN 'hive' ELSE a . id END = b . id ; 
No rows affected  ( 41.668 seconds )    52.477

结果:这样的后果就是所有为null值的id全部都变成了相同的字符串,及其容易造成数据的倾斜(所有的key相同,相同key的数据会到同一个reduce当中去)

为了解决这种情况,我们可以通过hive的rand函数,随记的给每一个为空的id赋上一个随机值,这样就不会造成数据倾斜

随机分布:

set hive .exec. reducers . bytes . per . reducer=32123456 ; set mapreduce . job . reduces=7 ; INSERT OVERWRITE TABLE jointable
SELECT a .* FROM nullidtable a
LEFT JOIN ori b ON CASE WHEN a . id IS NULL THEN concat( 'hive' ,  rand())  ELSE a . id END = b . id ; 

No rows affected  ( 42.594 seconds ) 

  SQL优化

    Combiner

默认情况下,Map阶段同一Key数据分发给一个reduce,当一个key数据过大时就倾斜了。

并不是所有的聚合操作都需要在Reduce端完成,很多聚合操作都可以先在Map端进行部分聚合,最后在Reduce端得出最终结果。

开启Map端聚合参数设置

-(1)是否在Map端进行聚合,默认为True
set hive . map . aggr = true ; --(2)在Map端进行聚合操作的条目数目(阈值)
set hive . groupby . mapaggr . checkinterval = 100000 ; --(3)有数据倾斜的时候进行负载均衡(默认是false)
set hive . groupby . skewindata = true ; 

当选项设定为 true,生成的查询计划会有两个MR Job。第一个MR Job中,Map的输出结果会随机分布到Reduce中,每个Reduce做部分聚合操作,并输出结果,这样处理的结果是相同的Group By Key有可能被分发到不同的Reduce中,从而达到负载均衡的目的;第二个MR Job再根据预处理的数据结果按照Group By Key分布到Reduce中(这个过程可以保证相同的Group By Key被分布到同一个Reduce中),最后完成最终的聚合操作。

Count(distinct)

数据量小的时候无所谓,数据量大的情况下,由于COUNT DISTINCT操作需要用一个Reduce Task来完成,这一个Reduce需要处理的数据量太大,就会导致整个Job很难完成,一般COUNT DISTINCT使用先GROUP BY再COUNT的方式替换:

方式1:

SELECT count(DISTINCT id )  FROM bigtable ; 

方式2

SELECT count( id )  FROM  (SELECT id FROM bigtable GROUP BY id )  a ; 

虽然会多用一个Job来完成,但在数据量大的情况下,这个绝对是值得的。

避免笛卡尔积

尽量避免笛卡尔积,即避免join的时候不加on条件,或者无效的on条件,Hive只能使用1个reducer来完成笛卡尔积。

  并行执行

Hive会将一个查询转化成一个或者多个阶段。这样的阶段可以是MapReduce阶段、抽样阶段、合并阶段、limit阶段。或者Hive执行过程中可能需要的其他阶段。默认情况下,Hive一次只会执行一个阶段。不过,某个特定的job可能包含众多的阶段,而这些阶段可能并非完全互相依赖的,也就是说有些阶段是可以并行执行的,这样可能使得整个job的执行时间缩短。不过,如果有更多的阶段可以并行执行,那么job可能就越快完成。

通过设置参数hive.exec.parallel值为true,就可以开启并发执行。不过,在共享集群中,需要注意下,如果job中并行阶段增多,那么集群利用率就会增加。

set hive .exec. parallel=true ;               --打开任务并行执行
set hive .exec. parallel . thread .number=16 ;   --同一个sql允许最大并行度,默认为8。
  • 当然,得是在系统资源比较空闲的时候才有优势,否则,没资源,并行也起不来。

  严格模式

Hive提供了一个严格模式,可以防止用户执行那些可能意向不到的不好的影响的查询。

通过设置属性hive.mapred.mode值为默认是非严格模式nonstrict 。开启严格模式需要修改hive.mapred.mode值为strict,开启严格模式可以禁止3种类型的查询。

set hive.mapred.mode  = strict ;   --开启严格模式
set hive.mapred.mode  =  nostrict ;  --开启非严格模式

配置文件修改:hive-site.xml

 <property>  <name>hive.mapred.mode</name>  <value> strict </value>  </property> 

1)对于分区表,在where语句中必须含有分区字段作为过滤条件来限制范围,否则不允许执行。换句话说,就是用户不允许扫描所有分区。进行这个限制的原因是,通常分区表都拥有非常大的数据集,而且数据增加迅速。没有进行分区限制的查询可能会消耗令人不可接受的巨大资源来处理这个表。

2)对于使用了order by语句的查询,要求必须使用limit语句。因为order by为了执行排序过程会将所有的结果数据分发到同一个Reducer中进行处理,强制要求用户增加这个LIMIT语句可以防止Reducer额外执行很长一段时间。

3)限制笛卡尔积的查询。对关系型数据库非常了解的用户可能期望在执行JOIN查询的时候不使用ON语句而是使用where语句,这样关系数据库的执行优化器就可以高效地将WHERE语句转化成那个ON语句。不幸的是,Hive并不会执行这种优化,因此,如果表足够大,那么这个查询就会出现不可控的情况。

  存储方式orc和压缩方式snappy

大数据场景下存储格式压缩格式尤为关键,可以提升计算速度,减少存储空间,降低网络io,磁盘io,所以要选择合适的压缩格式和存储格式,存储方式和压缩方式之前已经讲过,这里不再描述。