暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

数仓面试,HQL之优化、行列转化问题(一)

媛数据 2021-04-08
2050
上一节Hadoop体系中,我们有讲过三个重要组件的原理,这一节,我们来讲Hadoop体系中关于HQL常见的问题第一个系列,HQL优化及其UDF\UDTF函数,行转列、列转行问题


01
                  关于HQL优化

1. HQL为什么要优化?
当HQL运行时间过长,或者出现数据倾斜的时候,必须要考虑优化。一般HQL优化主要是针对于数据倾斜。

2. HQL优化常见的方法:
优化主要从日志入手,hive执行转为MapReduce,主要是看日志,还有查看yarn资源分配情况。
1MapJoin
如果不指定MapJoin或者不符合MapJoin的条件,那么Hive解析器会将Join操作转换成Common Join,即:在Reduce阶段完成join。容易发生数据倾斜。可以MapJoin把小表全部加载到内存在map端进行join,避免reducer处理
2)行列过滤
列处理:在SELECT中,只拿需要的列,如果有,尽量使用分区过滤,少用SELECT *
行处理:在分区剪裁中,当使用外关联时,如果将副表的过滤条件写在Where后面,那么就会先全表关联,之后再过滤 
3)列式存储
4)采用分区技术
5)合理设置Map数
(1)通常情况下,作业会通过input的目录产生一个或者多个map任务。
主要的决定因素有:input的文件总个数,input的文件大小,集群设置的文件块大小(2)是不是map数越多越好?
答案是否定的。如果一个任务有很多小文件(远远小于块大小128m),则每个小文件也会被当做一个块,用一个map任务来完成,而一个map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费。而且,同时可执行的map数是受限的。
(3)是不是保证每个map处理接近128m的文件块,就高枕无忧了?
答案也是不一定。比如有一个127m的文件,正常会用一个map去完成,但这个文件只有一个或者两个小字段,却有几千万的记录,如果map处理的逻辑比较复杂,用一个map任务去做,肯定也比较耗时。
针对上面的问题23,我们需要采取两种方式来解决:即减少map数和增加map数;
6)小文件进行合并
Map执行前合并小文件,减少Map数:CombineHiveInputFormat具有对小文件进行合并的功能(系统默认的格式)。HiveInputFormat没有对小文件合并功能。
7)合理设置Reduce数
Reduce个数并不是越多越好
1)过多的启动和初始化Reduce也会消耗时间和资源;
2)另外,有多少个Reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;
在设置Reduce个数的时候也需要考虑这两个原则:处理大数据量利用合适的Reduce数;使单个Reduce任务处理数据量大小要合适;
8)常用参数
// 输出合并小文件
SET hive.merge.mapfiles = true; -- 默认true,在map-only任务结束时合并小文件
SET hive.merge.mapredfiles = true; -- 默认false,在map-reduce任务结束时合并小文件
SET hive.merge.size.per.task = 268435456; -- 默认256M
SET hive.merge.smallfiles.avgsize = 16777216; -- 当输出文件的平均大小小于16m该值时,启动一个独立的map-reduce任务进行文件merge
9)开启map端combiner(不影响最终业务逻辑)
set hive.map.aggr=true
10)压缩(选择快的)
设置map端输出、中间结果压缩。(不完全是解决数据倾斜的问题,但是减少了IO读写和网络传输,能提高很多效率)
11)开启JVM重用

3. 一个优化HQL的具体案例:
优化过程:
优化一:stage-3 2020-10-15发现的问题和优化
测试HQL运行时间,发现运行有2个小时46分决定优化  ---> 查看SQL写法(a 表 left join 11 个表后,得到数据后再join一个表) ---> 查看相关运行的日志--->最后是join一个小表,耗时1小时--->mapjoin开启小表加载内存-->

优化二:stage-3 2020-10-21日再次发现的问题和优化
日志上显示已经数据倾斜(map 100%完成了很久,reduce99%一直未完成) --->发现是stage-3造成整体数据倾斜--->explain输出sql语法树--->文本搜索找到stage-3--->定位确定的出问题的语句---定位关键词user_id--->查看源表数据情况--->大量0值造成倾斜--->语句修改,处理成随机数

优化三:设置map数和reduce数
查看map、reduce设置参数,发现是用的128M切分--->找到任务在CDH平台yarn上查看map启用个数---->发现启用了快100多个--->反复测试,将map切分参数增大100倍(set mapred.参数系列)---->最后缩短到50分钟

具体过程如下:
因为涉及公司隐私,本文所有的sql被精简化,加上脱敏处理。
第一版的sql:
主表a 和11个从表 left join后,得到数据后,再和一个表join。数据插入终表
一共运行2个小时46分

第一次优化:开启mapjoin

本次sql运行时长在2小时46分,肯定是一个待优化的sql,所以首先从hive执行日志入手
.........

     整个SQL在执行后,一共分为了3个阶段,最后一个join表,这是最后一步的日志。从上面的sql可以看到,最后一步的sql只有短短的一个join。join的也是一个小表。但是sql足足运行了一个多小时,这里是很需要去优化的。
      所以这里就需要吧这个小表写进内存里面,在不改变sql语句的情况下。只需要加上一个参数。在版本Hive 0.11之前是需要自己加mapjoin, 现在大部分企业都在hive 0.11版本以上了。
hive0.11 版本以前 ---要在写的SQL里面加mapjoin
SELECT关键字后面添加/*+ MAPJOIN(tablelist) */提示优化器转化为map join
select /* +mapjoin(a) */ a.id aid, name, age from a join b on a.id = b.id;
select /* +mapjoin(movies) */ a.title, b.rating from movies a join ratings b on a.movieid =
b.movieid;
hive0.11 版本以后 ---开启参数即可。

set hive.auto.convert.join=true; 设置 MapJoin 优化自动开启
set hive.mapjoin.smalltable.filesize=25000000
       这个参数是开启mapjoin。hive会自动将小表加入内存里面读取。
优化完后运行时间直接从2小时46分降到了2小时。

备注:如何查看当前hive版本号

方法一

查看jar包版本:

  1. whereis hive 获取 hive位置
  2. 查看hive的jar包版本

方法二

进入hive客户端 通过log查看当前hive的版本。

第二次优化: 寻找数据倾斜

       继续去看日志,很容易就能看到这么一段,这一看就是数据倾斜了。
所以继续去CDH平台,yarn的历史日志去确认是否数据倾斜。

stage3的时候,数据倾斜。

所以,只要找到stage是哪个语句就能找到数据倾斜的地方。

通过explain。将sql输出语法树。

这个语法树会非常的长。但是只要找到stage-3是哪个语句就好了。

语法树的日志就不详细贴出。

通过文本搜索’Stage-3’。

在语法树的下面能看到 alias:e 的标签。

所以,上面的sql语句是e表的join出现了数据倾斜。

回顾join e表的语句。

     left outer join dm_bbb_broker_detail_normal_daily e on a.user_id=e.user_id and e.cal_dt = ${dealDate} and coalesce(e.user_id,0)>0


     这个语句的数据倾斜一共就两个地方:1.主表的user_id倾斜了。2.从表的user_id倾斜了,这是属于特殊数据造成的倾斜。
通过查找。可以发现是主表的user_id倾斜了。里面user_id为0的条数有几十万条。定位找到了数据倾斜的地方。优化就变得简单了。
现在直接将主表的user_id打散就好了。

 left outer join dm_bbb_broker_detail_normal_daily e on (case when a.user_id = 0 then cast(ceiling(rand() * -65535) as bigint) else a.user_id end)=e.user_id and e.cal_dt = ${dealDate} and coalesce(e.user_id,0)>0

这里用了一个随机数,将user_id为0的字段全部分散。
这里的优化效果很明显。
原来2小时的运行时间。现在一小时就能结束了。

第三次优化:调整map数量与reduce数量

set mapred.max.split.size=134217728;

set mapred.min.split.size.per.node=134217728;

set mapred.min.split.size.per.rack=134217728;

set hive.exec.reducers.bytes.per.reducer=1073741824;

从上面的参数可以看到。这里sql设置的map切分是128兆。随便挑一个任务去yarn看一下详细情况。

能看到起了100多个map。
但是每一个map的时间都很短

这里就会造成了yarn会启动大量的map,造成了资源浪费。所以减少map数量迫在眉睫。经过调试确定下列参数。

set mapred.max.split.size=1024000000;

set mapred.min.split.size.per.node=1024000000;

set mapred.min.split.size.per.rack=1024000000;

set mapred.reduce.tasks=150;

将map切分数量过大了近10倍,能明显减少map数量
这里需要经过多次的调试,确定一个最优的值。

最后将直接缩短到了50分钟

前后更改SQL对比:

map、reduce参数更改:


表关联关联键特殊数据处理随机数:


开启mapjoin,小表先加载内存:


02
                              HQL中的UDF函数,行转列,列转行函数
UDF函数:
为什么要自定义函数?
a.内置函数有的没有满足所有的功能,所以需要重新自定义
b.方便检查数据出错、异常处
c.方便调试

自定义函数开发UDF的步骤:
1. 使用maven创建一个java项目
2. 继承UDF类;
3. 重写evaluate方法。
4.打jar包
5.上传jar包
案例如下:
  • 创建一个HelloUdf类继承UDF,并且重写evaluate方法




  • 打jar包


  • 上传jar包

面试题:

     在项目中是否自定义过UDF、UDTF函数,以及用他们处理了什么问题,及自定义步骤?

1)自定义过。

2)用UDF函数解析公共字段;用UDTF函数解析事件字段。


      自定义UDF:继承UDF,重写evaluate方法

      自定义UDTF:继承自GenericUDTF,重写3个方法:initialize(自定义输出的列名和类型),process(将结果返回forward(result)),close


      为什么要自定义UDF/UDTF,因为自定义函数,可以自己埋点Log打印日志,出错或者数据异常,方便调试.


列转行、行转列函数
  • 列转行,行转列问题

      列转行

     其实也就是一行中的一个字段的数据,拆成好几行的数据,其中经常用到的函       数列举如下:

        split()函数

        hsql中split(item,'分隔符')


       lateral view:

      这个和explode要结合起来一起用

     explode将复杂结构一行拆成多行,实现行转列,如

    id     name

    1      hell,hao,de

    select  explode(split(name,','))  from tablename;

    id   name

   1      hello

   1       hao

   1      he 

   Lateral view

  lateral view用于和split、explode等UDTF一起使用的,lateral view把一行拆分    成一行或者多行,再把结果组合,产生一个支持别名表的虚拟表。 

  select id,name2 from tablename lateral view explode(split(name,’,’))   c as    name2; 

  案例:

  XDC123   计划买车,已买房,已买车

  DFG456   计划买房,计划买房,已买房,已买车

  CXY789   计划买车,计划买房,已买房,已买车

  ...

  分析:数据拆出来,每个人生阶段这种事件会有很多重复,明细是要分组;

  步骤:

  1. 按',' 拆分数据;

  2. explode一行拆分成多行

  3. laterval view组合2.命名成虚拟表

  4. laterval view explode  ---整条命名成 as 新字段

   select  stage_someone, count(distinct UID)  from  LifeStage  lateral      view  explode(split(stage,','))  LifeStage_tmp  as stage_someone       group  by  stage_someone;


     行转列:

       XDC123   计划买车

       XDC123   已买车

       XDC123   已买房

.....

     数据变换:

     XDC123   已买房,已买车, 计划买车

     所有数据拼合成一行

    select UID,concat_ws(',',collect_set(stage)) as stages from LifeStage group by UID;


                              
总结:这一节,我们来讲Hadoop体系中关于HQL常见的问题第一个系列,HQL优化及其UDF\UDTF函数,行转列、列转行问题。
下一节,我们继续补充HQL中常见的问题,关于开窗函数以及剩下的一些常见问题。


#我是媛姐,一枚有多年大数据经验的程序媛,早期做过电商也有外贸,打过螺丝搬过砖,关注数仓,关注分析。愿你我走得更远!
文章转载自媛数据,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论