下班后我都学了什么1 | Hive

下班后我都学了什么1 | Hive

我是19年校招,20年入职,即将工作满一年的数据分析师~ 今年开始用空余时间给自己充充电🔋,然后把笔记整理在博客~~ 第一篇从Hive开始啦。

在这里插入图片描述



一、学习资料和方法 📝

1、学习途径 ❓

我有买过O’Reilly系列的《Hive编程指南》看,但是没坚持下去。因为我的时间太碎片化,没法集中精力地去钻研一本书。有试过在地铁上拿着书看,但是看完了仿佛什么都记不得。所以,后来改成每天在地铁上用手机看视频来学习

众所周知,b站是个学习网站🤔

2、学习资料 📖

  • 视频来源:b站上搜 “hive”,然后播放量最高的那个视频就对啦。
  • 学习资料评论区前排就有分享课件!!! 非常良心,都是免费的

(不上链接了,免得广告嫌疑。虽然是免费的课程资源,但我觉得质量还是很好的,符合我的需要)

3、学习方法 💦

  • 每天上下班通勤时间,1.5倍速看课程视频。有sql基础的,学起来不太费力。先整体过一遍。
    –> 耗时:1个月 (2021.3.4 - 2021.2021.4.1)
  • 打印课件,把课件过一遍(相当于第二次学习),自己勾勾画画,把不会的圈出来。
    –> 耗时:3天 (2021年4月的第一周清明假期)
  • 盲点、难点对照课程视频,又再看一遍,把不会的搞懂,查漏补缺。
    –> 耗时:2天 (2021年4月第二周周末)
  • 整理笔记在博客
    –> 耗时:2天 (2021年4月第三、四周周末)


二、笔记 📒

这是我个人的笔记啦,挑我自己的重难点、按照个人理解记录的啦。不是面面俱到,我的理解也可能不够严谨。

1、什么是Hive

  • Hive 是做什么的

Hive 是基于Hadoop的一个数据仓库工具,可以将海量的结构化数据映射为一张表,并且提供类SQL查询功能,能将SQL语句转变成MapReduce任务来执行。

结合工作中的体会,我的理解是,
(1)埋点采集回来的原始数据都是json格式,json是半结构化数据格式,按照key-value,还不方便数据分析师使用。
(2)数仓的同事,按照key-value把数据取出来,经过一系列加工处理(数据提取、转化、加载),整理成一张张最终供数据分析师使用的表,存储起来。
(3)而数据分析师,就是用HQL语句来查询。虽然我们写的是HQL语句,但Hive框架会转换成MapReduce的方法来实现。所以提交代码后控制台会看到Map、Reduce等字眼。

  • 总结

Hive的本质:将HQL转化成MapReduce程序
在这里插入图片描述

  • 数据存储在 HDFS
  • 分析数据底层的实现是MapReduce
  • 执行程序运行在Yarn上

2、Hive的优缺点/ Hive VS 数据库

我觉得这两个问题类似,其实都是总结Hive的特点、适用场景。

Hive是为了数据仓库而设计的,是为了支持大规模数据而产生的。理解它的这个出发点和特性,就比较好理解它同时所存在的优点和缺点。

以下是我自己整理和理解的:

  • 优点
    (1) HQL是类SQL语法,不用重新学一种新的语言,简单。
    (2)本身可以把HQL转换成MapReduce,避免了去写MapReduce,简单。
    (3)Hive的优势在于处理大数据,当数据规模大到超过数据库的处理能力的时候,Hive可以利用MapReduce进行并行计算。
    (4)支持自定义函数

  • 缺点
    (1)Hive执行延迟性高,因此常用于对实时要求不高的场合(数据分析)。
    -> 延迟性高的原因有2个:一,Hive在查询数据的时候,由于没有索引,要扫描整个表,所以延迟较高;二,MapReduce本身具有较高延迟性。
    (2)Hive不支持update和delete。
    -> 我的理解是这样的:由于Hive是针对数据仓库设计的,数据仓库的内容是读多写少,所以Hive不建议对数据改写,数据在加载的时候就已经确定好了。所以不支持update。delete是删除表数据,也算是改写数据,那也不允许。Hive是可以drop table的,这个是删除整个表。而数据库是可以update和delete数据的。
    (3)Hive无法表达迭代式算法,不擅长机器学习、数据挖掘等
    -> 最近开始学spark了,spark就擅长迭代式算法,所以对比Hive,对这一条就有比较深刻的理解。
    -> 迭代式算法,是指第一个MapReduce的结果作为第二个MapReduce的输入。Hive不擅长,就是指MapReduce不擅长。那MapReduce为什么不擅长呢?因为MapReduce的结果存储在磁盘,等到作为第二个MapReduce的输入的时候,又要去磁盘读取,影响性能和时间。而比如Spark,就是基于内存进行数据的多次迭代。

3、Hive构架和运行机制

  • Hive 构架
    在这里插入图片描述

把Hive架构分为4块,分别用不同颜色表示:
(1)红色:用户接口Client,提供了一些列交互接口比如CLI、JDBC。
(2)蓝色:这部分是Hive主要的。Driver是驱动,包括解析器、编译器、优化器、执行器。执行顺序是:解析器 -> 编译器 -> 优化器 -> 执行器。

  • 解析器:比如检查HQL语法,表是否存在,字段是否存在等
  • 编译器:将HQL 转化成MapReduce
  • 优化器:优化
  • 执行器:执行

(3)黄色:Meta store 元数据,用来存储映射关系的,将HQL中的表与HDFS路径做映射。因为Hive中的数据是存储在HDFS的,相当于元数据记录了每个表存在HDFS哪里,先通过表找到它的地址,再根据地址去HDFS里取数据。
(4)绿色:Hadoop相关的,HDFS进行存储,MapReduce进行计算。

  • Hive的运行机制

Hive的运行机制
(1)Hive通过给用户提供的一些列交互接口,接收到用户的HQL;
(2)使用Driver(解析器、编译器、优化器、执行器),结合元数据(Mata Store),看HQL写的对不对,表存储在HDFS哪里,把HQL翻译成MapReduce,优化然后提交到Hadoop中执行。
(3)最后将执行结果输出到用户交互接口


4、Hive的数据类型

  • 基本数据类型
    在这里插入图片描述
    我想说的是,Hive中的字符是string,而数据库中是varchar。

  • 集合数据类型
    在这里插入图片描述
    第一次看到集合数据类型,array很好理解,就是数组。但是会疑惑,struct和map有什么区别? 因为看上去都是反应一种映射关系。
    在这里插入图片描述
    通过这个例子就可以理解到map和stuct的区别。
    map类型的children,它里面都是相同结构的数据,即名字和年龄;而struct类型的address,里面允许更复杂的内容,可以是街道、城市,并不要求每一行的数据都要是同样内容。

  • 类型转换
    (1)隐式转换
    比如使用INT类型,TINYINT会自动转换成INT类型。但是HIVE不会进行反向转换,比如使用TINYINT类型,INT不会自动转换为TINYINT类型。
    (2)CAST强制转换
    例如 CAST(‘1’ AS INT)将把字符串’1’ 转换成整数 1;如果强制类型转换失败,如执行 CAST(‘X’ AS INT),表达式返回空值 NULL。

5、内部表 VS 外部表

我们知道HIVE中有 元数据 🆚 原数据,元数据存储的是指向hdfs实际数据的路径,原数据是hdfs里真实存储的数据。

(1)敲黑板!!内部表和外部表的区别就在于这里啦。

  • 内部表(也称为管理表):
    删除内部表,元数据和原数据同时删除。 所以不适合和其他工具共享数据。
  • 外部表:
    删除外部表,则删除元数据,原数据不会被删掉。 外部表就是Hive并非认为其完全拥有这份数据,所以真是的原数据不会被删掉。

(2)创建外部表:create external table ……

create external table if not exists dept( deptno int,
dname string, loc int
)
row format delimited fields terminated by '\t';

(3)内部表与外部表的互相转换:

alter table student2 set tblproperties('EXTERNAL'='TRUE'); 

6、排序 order by VS sort by VS cluster by

(1) order by 全局排序
只有1个Reducer。
-> 比如对所有员工按照工资降序排序。
select * from emp order by sal desc;


(2)sort by 对每个Reducer内部排序,与distribute by结合使用
distribute by类似partition by 进行分区,可以控制某个指定行应该到哪个reducer。分区规则根据分区字段的hash码与reduce的个数进行模除后,余数相同的分到一个区。
-> 比如按照部门编号deptno分区,再对每个分区内员工的工资排序
select * from emp distribute by deptno sort by empno desc;


(3)当distribute by 和 sort by字段相同时,可以用 cluster by。
cluster by同时具有distribute by和 sort by的功能。但排序只能升序,不能指定asc或desc
-> 以下两种写法等价
select * from emp cluster by deptno;
select * from emp distribute by deptno sort by deptno;


7、分区表 VS 分桶表

(1)分区表

  • Hive中的分区就是分目录。分区表的每个分区,对应HDFS文件系统上一个独立的文件夹, 该文件夹下是该分区所有的数据文件。
  • 查询时通过where来指定分区,提高查询效率。

-> 比如创建分区表,按照日期分区。那么在hdfs上存储数据的时候,数据按照日期分别存放在不同的文件夹中,不同的文件夹就是不一样的存储路径。这样在查询的时候,比如限定where day = ‘20210418’,那么就会去20210418的文件夹下取数据,而不会去20210417的文件夹。

-- 创建分区表
create table dept_partition
( deptno int
, dname string
, loc string
)
partitioned by (day string)
row format delimited fields terminated by '\t';

(2)分桶表

  • 对于一张表或者分区,Hive可以进一步组织成桶。分桶将数据集分解成更容易管理的若干部分。
  • Hive的分桶采用对分桶字段的值进行哈希,然后除以桶的个数求余的方式决定该条记录存放在哪个桶当中。
  • 分桶表可以用来做抽样。 对表进行抽样,也就是对表分成的桶抽样。语法:TABLESAMPLE(BUCKET x OUT of y);其中,x的值必须小于y

-> 比如在 20210418文件夹下,存储着这一天的所有数据,但是数据集太庞大了,我想进一步把数据集分割成一份一份的更小的数据集,也就是分成一个一个的桶,比如按照id分成4个桶。

-- 创建分桶表,分成4个桶
create table stu_buck(id int, name string) clustered by(id)
into 4 buckets
row format delimited fields terminated by '\t';

-> ,因为数据集太庞大了,现在我想对20210418这一天的数据抽样。那我可以相当于对刚刚分好的4个桶中,选1个桶的数据出来就好了。

select * from stu_buck tablesample(bucket 1 out of 4 on id);

(3)分区表 🆚 分桶表

  • 分区针对的是数据的存储路径;
  • 分桶针对的是数据文件。

我的理解:

  • 分区是在hdfs中,按照分区字段把数据存在不同的文件夹,即不同目录下,也就是存储在不同的地方,不同的地方就是不同的存储路径。
  • 分桶是针对某一个数据文件,把它分成几份更小的数据文件。

8、常用内置函数

8.1 空字段赋值 —— NUL()

NVL:给值为 NULL 的数据赋值,它的格式是 NVL( value,default_value)。它的功能是如 果 value 为 NULL,则 NVL 函数返回 default_value 的值,否则返回 value 的值,如果两个参数 都为 NULL ,则返回 NULL。

-- 如果comm为空,则用-1代替
select comm,nvl(comm, -1) from emp; 

8.2 case when then else end

  • Example
    在这里插入图片描述
select dept_id,
sum(case sex when '男' then 1 else 0 end) male_count,
sum(case sex when '女' then 1 else 0 end) female_count from emp_sex
group by dept_id;

8.3 concat() & concat_ws() & collect_set()

  • CONCAT(string A/col, string B/col…):返回输入字符串连接后的结果,支持任意个输入字 符串;
  • CONCAT_WS(separator, str1, str2,…):它是一个特殊形式的 CONCAT()。第一个参数剩余参 数间的分隔符。分隔符可以是与剩余参数一样的字符串。如果分隔符是 NULL,返回值也将 为 NULL。这个函数会跳过分隔符参数后的任何 NULL 和空字符串。分隔符将被加到被连接 的字符串之间;
    注意: CONCAT_WS must be "string or array
  • COLLECT_SET(col):函数只接受基本数据类型,它的主要作用是将某字段的值进行去重 汇总,产生 Array 类型字段。
  • Example
    在这里插入图片描述
-- 创建hive表并导入数据
create table person_info
( name string, 
constellation string, 
blood_type string)
row format delimited fields terminated by "\t";

load data local inpath "/opt/module/hive/data/person_info.txt" into table
-- (1)先把星座和血型用CONCAT_WS连接,连接后的字段命名 c_b
-- (2)按照c_b分组group by,同时对name字段用collect_set去重汇总成array,再将array里的元素用CONCAT_WS以‘|’连接。
SELECT
t1.c_b, CONCAT_WS("|",collect_set(t1.name))
FROM (
SELECT
NAME,
CONCAT_WS(',',constellation,blood_type) c_b FROM person_info
)t1
GROUP BY t1.c_b

8.4 explode() 、LATERAL VIEW

  • EXPLODE(col):将 hive 一列中复杂的 Array 或者 Map 结构拆分成多行。
  • LATERAL VIEW
    用法:LATERAL VIEW udtf(expression) tableAlias AS columnAlias
    解释:用于和 split, explode 等 UDTF 一起使用,它能够将一列数据拆成多行数据,在此 基础上可以对拆分后的数据进行聚合。

我的理解,

  • EXPLODE就是炸开,把一行的多个元素给拆开成到多行,比如本来a,b,c在同一行,现在炸开成a一行,b一行,c一样。

  • lateral view是侧写,用explode炸开之后,本来a,b,c对应 id=001,现在炸开成a、b、c三行之后,a、b、c分别也应该要对应一个id,即第一行a对应 id=001,第二行b也对应id=001,第三行c也对应id=001。也就是用lateral view把炸开后的数据(即a、b、c),与原来的数据(即id=001)给聚合关联起来。

  • Example
    在这里插入图片描述

-- 建hive表并导入数据
create table movie_info( movie string, category string)
row format delimited fields terminated by "\t";

load data local inpath "/opt/module/data/movie.txt" into table 
SELECT
movie, category_name
FROM
movie_info lateral VIEW
explode(split(category,",")) movie_info_tmp AS category_name;

8.5 窗口函数(开窗函数)

  • OVER():指定分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变而变化。
  • CURRENT ROW: 当前行
  • n PRECEDING: 往前 n 行数据 n FOLLOWING:往后 n 行数据
  • UNBOUNDED:起点,
    – UNBOUNDED PRECEDING 表示从前面的起点,
    – UNBOUNDED FOLLOWING 表示到后面的终点
  • LAG(col,n,default_val):往前第 n 行数据
  • LEAD(col,n, default_val):往后第 n 行数据
  • NTILE(n):把有序窗口的行分发到指定数据的组中,各个组有编号,编号从 1 开始,对 于每一行,NTILE 返回此行所属的组的编号。注意:n 必须为 int 类型。
  • Example在这里插入图片描述
--(1)查询在 2017 年 4 月份购买过的顾客及总人数
select name,count(*) over () 
from business
where substring(orderdate,1,7) = '2017-04' 
group by name;
--(2)查询顾客的购买明细及月购买总额
select name
	,orderdate
	,cost
	,sum(cost) over(partition by month(orderdate)) 
from business;
--(3)将每个顾客的 cost 按照日期进行累加
select name,orderdate,cost,
sum(cost) over() as sample1,--所有行相加
sum(cost) over(partition by name) as sample2,--按 name 分组,组内数据相加 sum(cost) over(partition by name order by orderdate) as sample3,--按 name 分组,组内数据累加
sum(cost) over(partition by name order by orderdate rows between UNBOUNDED PRECEDING and current row ) as sample4 ,--和 sample3 一样,由起点到 当前行的聚合
sum(cost) over(partition by name order by orderdate rows between 1 PRECEDING and current row) as sample5, --当前行和前面一行做聚合 
sum(cost) over(partition by name order by orderdate rows between 1 PRECEDING AND 1 FOLLOWING ) as sample6,--当前行和前边一行及后面一行
sum(cost) over(partition by name order by orderdate rows between current row and UNBOUNDED FOLLOWING ) as sample7 --当前行及后面所有行
from business;
--(4)查看顾客上次的购买时间
select name
	,orderdate
	,cost
	,lag(orderdate,1,'1900-01-01') over(partition by name order by orderdate ) as time1
	, lag(orderdate,2) over (partition by name order by orderdate) as time2
from business;
--(5)查询前 20%时间的订单信息
select * 
from (
	select name
		,orderdate
		,cost
		, ntile(5) over(order by orderdate) sorted 
	from business
) t
where sorted = 1;

8.6 rank() VS dense_rank() VS row_number

  • RANK() 排序相同时会重复,总数不会变。比如4个人的排名分别为1,1,3,4;
    DENSE_RANK() 排序相同时会重复,总数会减少。比如4个人的排名分别为1,1,2,3;
  • ROW_NUMBER() 会根据顺序计算。比如4个人的排序分别为1,2,3,4。
  • Example
    在这里插入图片描述
-- 创建hive表并导入数据
create table score
( name string
, subject string,
 score int)
row format delimited fields terminated by "\t";

load data local inpath '/opt/module/data/score.txt' into table score;
select name
	,subject
	, score
	,rank() over(partition by subject order by score desc) rp
	,dense_rank() over(partition by subject order by score desc) drp
	,row_number() over(partition by subject order by score desc) rmp
from score;

9、压缩和存储

9.1 压缩

  • MapReduce支持的压缩编码
    在这里插入图片描述
  • 压缩参数配置
    在这里插入图片描述

9.2 存储

9.2.1 列式存储和行式存储

在这里插入图片描述

  • 行存储的特点
    查询满足条件的一整行数据的时候,列存储则需要去每个聚集的字段找到对应的每个列的值,行存储只需要找到其中一个值,其余的值都在相邻地方,所以此时行存储查询的速度更快。
  • 列存储的特点
    因为每个字段的数据聚集存储,在查询只需要少数几个字段的时候,能大大减少读取的数据量;每个字段的数据类型一定是相同的,列式存储可以针对性的设计更好的设计压缩算法。

Hive 支持的存储数据的格式主要有:TEXTFILE 、SEQUENCEFILE、ORC、PARQUET。

  • TEXTFILE 和 SEQUENCEFILE 的存储格式都是基于行存储的;
  • ORC 和 PARQUET 是基于列式存储的。

9.2.2 TextFile 格式

默认格式,数据不做压缩,磁盘开销大,数据解析开销大。
可结合 Gzip、Bzip2 使用, 但使用 Gzip 这种方式,hive 不会对数据进行切分,从而无法对数据进行并行操作。


9.2.3 Orc格式

Orc (Optimized Row Columnar)是 Hive 0.11 版里引入的新的存储格式。
如下图所示可以看到每个 Orc 文件由 1 个或多个 stripe 组成,每个 stripe 一般为 HDFS的块大小,每一个 stripe 包含多条记录,这些记录按照列进行独立存储, 对应到 Parquet 中的 row group 的概念。每个 Stripe 里有三部分组成,分别是 Index Data,Row Data,Stripe Footer

在这里插入图片描述

  • (1)Index Data: 一个轻量级的 index,默认是每隔 1W 行做一个索引。这里做的索引应该只是记录某行的各字段在 Row Data 中的 offset。
  • (2)Row Data:存的是具体的数据,先取部分行,然后对这些行按列进行存储。对每个列进行了编码,分成多个 Stream 来存储。
    (3)Stripe Footer:存的是各个 Stream 的类型,长度等信息。
  • 每个文件有一个 File Footer,这里面存的是每个 Stripe 的行数,每个 Column 的数据类型信息等;
    每个文件的尾部是一个 PostScript,这里面记录了整个文件的压缩类型以及 FileFooter 的长度信息等。
    在读取文件时,会 seek 到文件尾部读 PostScript,从里面解析到 File Footer 长度,再读 FileFooter,从里面解析到各个 Stripe 信息,再读各个 Stripe,即从后 往前读。

9.2.4 Parquet 格式

Parquet 文件是以二进制方式存储的,所以是不可以直接读取的,文件中包括该文件的 数据和元数据,因此 Parquet 格式文件是自解析的。

  • (1)行组(Row Group):每一个行组包含一定的行数,在一个 HDFS 文件中至少存储一 个行组,类似于 orc 的 stripe 的概念。
  • (2)列块(Column Chunk):在一个行组中每一列保存在一个列块中,行组中的所有列连续的存储在这个行组文件中。一个列块中的值都是相同类型的,不同的列块可能使用不同的算法进行压缩。
  • (3)页(Page):每一个列块划分为多个页,一个页是最小的编码的单位,在同一个列块的不同页可能使用不同的编码方式。

通常情况下,在存储 Parquet 数据的时候会按照 Block 大小设置行组的大小,由于一般情况下每一个 Mapper 任务处理数据的最小单位是一个 Block,这样可以把每一个行组由一个 Mapper 任务处理,增大任务执行并行度。Parquet 文件的格式。

在这里插入图片描述

上图展示了一个 Parquet 文件的内容,一个文件中可以存储多个行组,文件的首位都是 该文件的 Magic Code,用于校验它是否是一个 Parquet 文件,Footer length 记录了文件元数据的大小,通过该值和文件长度可以计算出元数据的偏移量,文件的元数据中包括每一个行 组的元数据信息和该文件存储数据的 Schema 信息。除了文件中每一个行组的元数据,每一 页的开始都会存储该页的元数据,在 Parquet 中,有三种类型的页:数据页、字典页和索引页。 数据页用于存储当前行组中该列的值,字典页存储该列值的编码字典,每一个列块中最 多包含一个字典页,索引页用来存储当前行组下该列的索引,目前 Parquet 中还不支持索引页。


9.2.5 主流文件存储格式对比

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

  • 存储文件的对比总结:ORC > Parquet > textFile
  • 存储文件的查询速度总结:查询速度相近。
  • 存储方式和压缩总结:在实际的项目开发当中,hive 表的数据存储格式一般选择:orc 或 parquet。压缩方式一 般选择 snappy,lzo。

10、调优

10.1 fetch抓取

  • Fetch 抓取是指,Hive 中对某些情况的查询可以不必使用 MapReduce 计算。例如:SELECT * FROM employees; 在这种情况下,Hive 可以简单地读取 employee 对应的存储目录下的文件, 然后输出查询结果到控制台。
  • 在 hive-default.xml.template 文件中 hive.fetch.task.conversion 默认是 more,老版本 hive 默认是 minimal,该属性修改为 more 以后,在全局查找、字段查找、limit 查找等都不走 mapreduce。
-- 把 hive.fetch.task.conversion 设置成 more,然后执行查询语句,如下查询方式都不 会执行 mapreduce 程序。
hive (default)> set hive.fetch.task.conversion=more; hive (default)> select * from emp;
hive (default)> select ename from emp;
hive (default)> select ename from emp limit 3;

10.2 表的优化

10.2.1 小表大表 Join(MapJOIN)
  • 将 key 相对分散,并且数据量小的表放在 join 的左边,可以使用 map join 让小的维度表先进内存。 在 map 端完成 join。
  • 实际测试发现:新版的 hive 已经对小表 JOIN 大表和大表 JOIN 小表进行了优化。小表放在左边和右边已经没有区别。
-- 设置自动选择 Mapjoin
set hive.auto.convert.join = true; 默认为 true 

-- 大表小表的阈值设置(默认 25M 以下认为是小表)
set hive.mapjoin.smalltable.filesize = 25000000; 

10.2.2 大表 Join 大表

(1) 空 KEY 过滤

  • 数据倾斜:
    有时 join 超时是因为某些 key 对应的数据太多,而相同 key 对应的数据都会发送到相同的reducer上,从而导致某些reducer的资源消耗远高于其他reducer。结果导致“一个人累死,其他人闲死的局面”。违背了并行的初衷。一方面,该节点承受巨大的压力;另一方面,其他节点计算完毕后要一直等该忙碌的节点,拖累了整体计算时间。
  • 此时我们应该仔细分析这些异常的 key,很多情况下, 这些 key 对应的数据是异常数据,我们需要在 SQL语句中进行过滤。

(2)空 key 转换

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

-- 用nvl(n.id,rand()),对为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的 reducer上。
insert overwrite table jointable
select n.* 
from nullidtable n 
full join bigtable o 
on nvl(n.id,rand()) = o.id;

10.3 group by

  • 默认情况下,Map 阶段同一 Key 数据分发给一个 reduce,当一个 key 数据过大时就倾斜了。
  • 并不是所有的聚合操作都需要在 Reduce 端完成,很多聚合操作都可以先在 Map 端进行部分聚合,最后在 Reduce 端得出最终结果。
--(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 中),最后完成最终的聚合操作。

10.4 Count(Distinct) 去重统计

  • 数据量小的时候无所谓,数据量大的情况下,由于 COUNT DISTINCT 操作需要用一个 Reduce Task 来完成,这一个 Reduce 需要处理的数据量太大,就会导致整个 Job 很难完成。
  • 一般 COUNT DISTINCT 使用先 GROUP BY 再 COUNT 的方式替换,但是需要注意 group by 造成的数据倾斜问题。

10.5 笛卡尔积

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


10.6 where过滤

  • 列处理:
    在 SELECT 中,只拿需要的列,如果有分区,尽量使用分区过滤,少用 SELECT *。
  • 行处理:
    在分区剪裁中,当使用外关联时,把where条件写在自查询里边,再做关联。
select b.id 
from bigtable b
join 
(select id from bigtable where id <= 10) o 
on b.id = o.id;

10.7 合理设置 Map 及 Reduce 数

10.7.1 Map数
  • (1)通常情况下,作业会通过 input 的目录产生一个或者多个 map 任务。 主要的决定因素有:input 的文件总个数,input 的文件大小,集群设置的文件块大小。
  • (2)是不是 map 数越多越好? 答案是否定的。
    如果一个任务有很多小文件(远远小于块大小 128m),则每个小文件也会被当做一个块,用一个 map 任务来完成,而一个 map 任务启动和初始化的时间远远大于逻辑处理的时间, 就会造成很大的资源浪费。而且,同时可执行的 map 数是受限的。
  • (3)是不是保证每个 map 处理接近 128m 的文件块,就高枕无忧了?
    答案也是不一定。比如有一个 127m 的文件,正常会用一个 map 去完成,但这个文件只有一个或者两个小字段,却有几千万的记录,如果 map 处理的逻辑比较复杂,用一个 map 任务去做,肯定也比较耗时。

针对上面的问题 2 和 3,我们需要采取两种方式来解决:即减少 map 数和增加 map 数;

(1)复杂文件增加 Map 数

当 input 的文件都很大,任务逻辑复杂,map 执行非常慢的时候,可以考虑增加 Map 数, 来使得每个 map 处理的数据量减少,从而提高任务的执行效率。

  • 增加 map 的方法为:根据
    computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M 公式, 调整 maxSize 最大值。让 maxSize 最大值低于 blocksize 就可以增加 map 的个数。
-- 设置最大切片值为 100 个字节
set mapreduce.input.fileinputformat.split.maxsize=100; 

(2)小文件进行合并

在 map 执行前合并小文件,减少 map 数:CombineHiveInputFormat 具有对小文件进行合并的功能(系统默认的格式)。HiveInputFormat 没有对小文件合并功能。

set hive.input.format= org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
-- 在 Map-Reduce 的任务结束时合并小文件的设置: 

-- 在 map-only 任务结束时合并小文件,默认 true
SET hive.merge.mapfiles = true; 	

-- 在 map-reduce 任务结束时合并小文件,默认 false
SET hive.merge.mapredfiles = true; 	

-- 合并文件的大小,默认 256M
SET hive.merge.size.per.task = 268435456; 
	 
-- 当输出文件的平均大小小于该值时,启动一个独立的 map-reduce 任务进行文件 merge  
SET hive.merge.smallfiles.avgsize = 16777216; 	

10.7.2 Reduce数

(1)调整 reduce 个数方法一

-- 1 每个 Reduce 处理的数据量默认是 256MB
hive.exec.reducers.bytes.per.reducer=256000000 	

-- 2 每个任务最大的 reduce数,默认为 1009
hive.exec.reducers.max=1009 	

--3 计算 reducer 数的公式
-- N=min(参数 2,总输入数据量/参数 1) 

(2)调整 reduce 个数方法二

-- 在 hadoop 的 mapred-default.xml 文件中修改设置每个 job 的 Reduce个数。
set mapreduce.job.reduces = 15; 

(3)reduce 个数并不是越多越好

  • 过多的启动和初始化 reduce 也会消耗时间和资源;
  • 另外,有多少个 reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;
  • 在设置 reduce 个数的时候也需要考虑这两个原则:处理大数据量利用合适的 reduce 数; 使单个 reduce 任务处理数据量大小要合适;

10.8 并行执行

  • Hive 会将一个查询转化成一个或者多个阶段。这样的阶段可以是 MapReduce 阶段、抽 样阶段、合并阶段、limit 阶段。或者Hive 执行过程中可能需要的其他阶段。默认情况下, Hive 一次只会执行一个阶段。
  • 不过,某个特定的 job可能包含众多的阶段,而这些阶段可能并非完全互相依赖的,也就是说有些阶段是可以并行执行的,这样可能使得整个 job 的执行时间缩短。 不过,如果有更多的阶段可以并行执行,那么 job 可能就越快完成。
  • 通过设置参数 hive.exec.parallel 值为true,就可以开启并发执行。
  • 不过,在共享集群中, 需要注意下,如果 job 中并行阶段增多,那么集群利用率就会增加。
-- 打开任务并行执行
set hive.exec.parallel=true;

-- 同一个 sql 允许最大并行度,默认为8。
set hive.exec.parallel.thread.number=16;

10.9 严格模式

Hive 可以通过设置防止一些危险操作:

10.9.1 防止分区表不使用分区过滤
  • hive.strict.checks.no.partition.filter 设置为 true 时,对于分区表,除非 where 语句中含有分区字段过滤条件来限制范围,否则不允许执行。 换句话说,就是用户不允许扫描所有分区。
  • 进行这个限制的原因是,通常分区表都拥有非常大的数据集,而且数据增加迅速。没有进行分区限制的查询可能会消耗令人不可接受的巨大资源来处理这个表。

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

10.9.3 防止笛卡尔积
  • 将 hive.strict.checks.cartesian.product 设置为 true 时,会限制笛卡尔积的查询。
  • 对关系型数 据库非常了解的用户可能期望在 行 JOIN 查询的时候不使用 ON 语句而是使用 where 语句,这样关系数据库的执行优化器就可以高效地将 WHERE 语句转化成那个 ON 语句。不幸的是,Hive 并不会执行这种优化,因此,如果表足够大,那么这个查询就会出现不可控的情况。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值