Hive性能优化上的一些总结

注意,本文百分之九十来源于此文:Hive性能优化,很感谢作者的细心整理,其中有些部分我做了补充和追加,要是有什么写的不对的地方,请留言赐教,谢谢

前言

今天电话面试突然被涉及到hive上有没有做过什么优化,当时刚睡醒,迷迷糊糊的没把以前实习的中遇到的一些问题阐述清楚,这里顺便转载一篇并来做一下总结


介绍

首先,我们来看看Hadoop的计算框架特性,在此特性下会衍生哪些问题?

  • 数据量大不是问题,数据倾斜是个问题。
  • jobs数比较多的作业运行效率相对比较低,比如即使有几百行的表,如果多次关联多次汇总,产生十几个jobs,耗时很长。原因是map reduce作业初始化的时间是比较长的。
  • sum,count,max,min等UDAF,不怕数据倾斜问题,hadoop在map端的汇总合并优化,使数据倾斜不成问题。
  • count(distinct ),在数据量大的情况下,效率较低,如果是多count(distinct )效率更低,因为count(distinct)是按group by 字段分组,按distinct字段排序,一般这种分布方式是很倾斜的。举个例子:比如男uv,女uv,像淘宝一天30亿的pv,如果按性别分组,分配2个reduce,每个reduce处理15亿数据。

      面对这些问题,我们能有哪些有效的优化手段呢?下面列出一些在工作有效可行的优化手段:

  • 好的模型设计事半功倍。

  • 解决数据倾斜问题。
  • 减少job数。
  • 设置合理的map reduce的task数,能有效提升性能。(比如,10w+级别的计算,用160个reduce,那是相当的浪费,1个足够)。
  • 了解数据分布,自己动手解决数据倾斜问题是个不错的选择。set hive.groupby.skewindata=true;这是通用的算法优化,但算法优化有时不能适应特定业务背景,开发人员了解业务,了解数据,可以通过业务逻辑精确有效的解决数据倾斜问题。
  • 数据量较大的情况下,慎用count(distinct),count(distinct)容易产生倾斜问题。
  • 对小文件进行合并,是行至有效的提高调度效率的方法,假如所有的作业设置合理的文件数,对云梯的整体调度效率也会产生积极的正向影响。
  • 优化时把握整体,单个作业最优不如整体最优。

      而接下来,我们心中应该会有一些疑问,影响性能的根源是什么?


性能低下的根源

hive性能优化时,把HiveQL当做M/R程序来读,即从M/R的运行角度来考虑优化性能,从更底层思考如何优化运算性能,而不仅仅局限于逻辑代码的替换层面。

​ RAC(Real Application Cluster)真正应用集群就像一辆机动灵活的小货车,响应快;Hadoop就像吞吐量巨大的轮船,启动开销大,如果每次只做小数量的输入输出,利用率将会很低。所以用好Hadoop的首要任务是增大每次任务所搭载的数据量。

  Hadoop的核心能力是parition和sort,因而这也是优化的根本。

  观察Hadoop处理数据的过程,有几个显著的特征

  • 数据的大规模并不是负载重点,造成运行压力过大是因为运行数据的倾斜。
  • jobs数比较多的作业运行效率相对比较低,比如即使有几百行的表,如果多次关联对此汇总,产生几十个jobs,将会需要30分钟以上的时间且大部分时间被用于作业分配,初始化和数据输出。M/R作业初始化的时间是比较耗时间资源的一个部分。
  • 在使用SUM,COUNT,MAX,MIN等UDAF函数时,不怕数据倾斜问题,Hadoop在Map端的汇总合并优化过,使数据倾斜不成问题。
  • COUNT(DISTINCT)在数据量大的情况下,效率较低,如果多COUNT(DISTINCT)效率更低,因为COUNT(DISTINCT)是按GROUP BY字段分组,按DISTINCT字段排序,一般这种分布式方式是很倾斜的;比如:男UV,女UV,淘宝一天30亿的PV,如果按性别分组,分配2个reduce,每个reduce处理15亿数据。
  • 数据倾斜是导致效率大幅降低的主要原因,可以采用多一次 Map/Reduce 的方法, 避免倾斜。

      最后得出的结论是:避实就虚,用 job 数的增加,输入量的增加,占用更多存储空间,充分利用空闲 CPU 等各种方法,分解数据倾斜造成的负担。


优化性能

配置角度优化

map阶段优化

Map阶段的优化,主要是确定合适的map数。那么首先要了解map数的计算公式,另外要说明的是,这个优化只是针对Hive 0.9版本。

<span style="color:#000000"><code class="language-shell">num_map_tasks = max[${mapred<span style="color:#009900 !important">.min</span><span style="color:#009900 !important">.split</span><span style="color:#009900 !important">.size</span>},min(${dfs<span style="color:#009900 !important">.block</span><span style="color:#009900 !important">.size</span>},${mapred<span style="color:#009900 !important">.max</span><span style="color:#009900 !important">.split</span><span style="color:#009900 !important">.size</span>})]</code></span>
  • 1
  • mapred.min.split.size: 指的是数据的最小分割单元大小;min的默认值是1B
  • mapred.max.split.size: 指的是数据的最大分割单元大小;max的默认值是256MB
  • dfs.block.size: 指的是HDFS设置的数据块大小。个已经指定好的值,而且这个参数默认情况下hive是识别不到的

通过调整max可以起到调整map数的作用,减小max可以增加map数,增大max可以减少map数。需要提醒的是,直接调整mapred.map.tasks这个参数是没有效果的

reduce阶段优化

这里说的reduce阶段,是指前面流程图中的reduce phase(实际的reduce计算)而非图中整个reduce task。Reduce阶段优化的主要工作也是选择合适的reduce task数量, 与map优化不同的是,reduce优化时,可以直接设置mapred.reduce.tasks参数从而直接指定reduce的个数

<span style="color:#000000"><code class="language-shell">num_reduce_tasks = min[${hive<span style="color:#009900 !important">.exec</span><span style="color:#009900 !important">.reducers</span><span style="color:#009900 !important">.max</span>},(${input<span style="color:#009900 !important">.size</span>}/${hive<span style="color:#009900 !important">.exec</span><span style="color:#009900 !important">.reducers</span><span style="color:#009900 !important">.bytes</span><span style="color:#009900 !important">.per</span><span style="color:#009900 !important">.reducer</span>})]</code></span>
  • 1
  • hive.exec.reducers.max:此参数从Hive 0.2.0开始引入。在Hive 0.14.0版本之前默认值是999;而从Hive 0.14.0开始,默认值变成了1009,这个参数的含义是最多启动的Reduce个数

  • hive.exec.reducers.bytes.per.reducer:此参数从Hive 0.2.0开始引入。在Hive 0.14.0版本之前默认值是1G(1,000,000,000);而从Hive 0.14.0开始,默认值变成了256M(256,000,000),可以参见HIVE-7158和HIVE-7917。这个参数的含义是每个Reduce处理的字节数。比如输入文件的大小是1GB,那么会启动4个Reduce来处理数据。

也就是说,根据输入的数据量大小来决定Reduce的个数,默认Hive.exec.Reducers.bytes.per.Reducer为1G,而且Reduce个数不能超过一个上限参数值,这个参数的默认取值为999。所以我们可以调整Hive.exec.Reducers.bytes.per.Reducer来设置Reduce个数。

需要注意的是:

  1. Reduce的个数对整个作业的运行性能有很大影响。如果Reduce设置的过大,那么将会产生很多小文件,对NameNode会产生一定的影响,而且整个作业的运行时间未必会减少;如果Reduce设置的过小,那么单个Reduce处理的数据将会加大,很可能会引起OOM异常
  2. 如果设置了mapred.reduce.tasks/mapreduce.job.reduces参数,那么Hive会直接使用它的值作为Reduce的个数
  3. 如果mapred.reduce.tasks/mapreduce.job.reduces的值没有设置(也就是-1),那么Hive会根据输入文件的大小估算出Reduce的个数。根据输入文件估算Reduce的个数可能未必很准确,因为Reduce的输入是Map的输出,而Map的输出可能会比输入要小,所以最准确的数根据Map的输出估算Reduce的个数。

列裁剪

Hive 在读数据的时候,可以只读取查询中所需要用到的列,而忽略其它列。 例如,若有以下查询:

<span style="color:#000000"><code><span style="color:#000088 !important">SELECT</span> a,b <span style="color:#000088 !important">FROM</span> q <span style="color:#000088 !important">WHERE</span> e<<span style="color:#006666 !important">10</span>;</code></span>
  • 1

在实施此项查询中,Q 表有 5 列(a,b,c,d,e),Hive 只读取查询逻辑中真实需要 的 3 列 a、b、e,而忽略列 c,d;这样做节省了读取开销,中间表存储开销和数据整合开销。

​ 裁剪所对应的参数项为:hive.optimize.cp=true(默认值为真)

补充:在我实习的操作过程中,也有用到这个道理,也就是多次join的时候,考虑到只需要的指标,而不是为了省事使用select * 作为子查询

分区裁剪

可以在查询的过程中减少不必要的分区。 例如,若有以下查询:

<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span>
(
SELECTT a1,<span style="color:#009900 !important">COUNT</span>(<span style="color:#006666 !important">1</span>)
<span style="color:#000088 !important">FROM</span> T
<span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> a1
)subq    # 建议贴边写,这样容易检查是否是中文括号!
<span style="color:#000088 !important">WHERE</span> subq.prtn=<span style="color:#006666 !important">100</span>; #(多余分区)<span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span>
T1
<span style="color:#000088 !important">JOIN</span>
(<span style="color:#000088 !important">SELECT</span> * <span style="color:#000088 !important">FROM</span> T2
)subq
<span style="color:#000088 !important">ON</span> (T1.a1=subq.a2)
<span style="color:#000088 !important">WHERE</span> subq.prtn=<span style="color:#006666 !important">100</span>;</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

查询语句若将“subq.prtn=100”条件放入子查询中更为高效,可以减少读入的分区 数目。 Hive 自动执行这种裁剪优化。

​ 分区参数为:hive.optimize.pruner=true(默认值为真)

补充:实际集群操作过程中,加分区是重中之重,不加分区的后果非常可能把整个队列资源占满,而导致io读写异常,无法登陆服务器及hive!切记切记分区操作和limit操作

JOIN操作

在编写带有 join 操作的代码语句时,应该将条目少的表/子查询放在 Join 操作符的左边。 因为在 Reduce 阶段,位于 Join 操作符左边的表的内容会被加载进内存,载入条目较少的表 可以有效减少 OOM(out of memory)即内存溢出。所以对于同一个 key 来说,对应的 value 值小的放前,大的放后,这便是“小表放前”原则。 若一条语句中有多个 Join,依据 Join 的条件相同与否,有不同的处理方法。

JOIN原则

在使用写有 Join 操作的查询语句时有一条原则:应该将条目少的表/子查询放在 Join 操作符的左边。原因是在 Join 操作的 Reduce 阶段,位于 Join 操作符左边的表的内容会被加载进内存,将条目少的表放在左边,可以有效减少发生 OOM 错误的几率。对于一条语句中有多个 Join 的情况,如果 Join 的条件相同,一句话就是小表在左边比如查询:

<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">INSERT</span> OVERWRITE <span style="color:#000088 !important">TABLE</span> pv_users
<span style="color:#000088 !important">SELECT</span> pv.pageid,u.age
<span style="color:#000088 !important">FROM</span> page_view p
<span style="color:#000088 !important">JOIN</span> <span style="color:#000088 !important">user</span> u <span style="color:#000088 !important">ON</span> (pv.userid = u.userid)
<span style="color:#000088 !important">JOIN</span> newuser x <span style="color:#000088 !important">ON</span> (u.userid = x.userid);  </code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 如果 Join 的 key 相同,不管有多少个表,都会则会合并为一个 Map-Reduce
  • 一个 Map-Reduce 任务,而不是 ‘n’ 个
  • 在做 OUTER JOIN 的时候也是一样

如果 Join 的条件不相同,比如:

<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">INSERT</span> OVERWRITE <span style="color:#000088 !important">TABLE</span> pv_users
<span style="color:#000088 !important">SELECT</span> pv.pageid, u.age
<span style="color:#000088 !important">FROM</span> page_view p
<span style="color:#000088 !important">JOIN</span> <span style="color:#000088 !important">user</span> u <span style="color:#000088 !important">ON</span> (pv.userid = u.userid)
<span style="color:#000088 !important">JOIN</span> newuser x <span style="color:#000088 !important">on</span> (u.age = x.age);   </code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Map-Reduce 的任务数目和 Join 操作的数目是对应的,上述查询和以下查询是等价的:

<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">INSERT</span> OVERWRITE <span style="color:#000088 !important">TABLE</span> tmptable
<span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span> page_view p
<span style="color:#000088 !important">JOIN</span>
<span style="color:#000088 !important">user</span> u
<span style="color:#000088 !important">ON</span> (pv.userid = u.userid);<span style="color:#000088 !important">INSERT</span> OVERWRITE <span style="color:#000088 !important">TABLE</span> pv_users
<span style="color:#000088 !important">SELECT</span> x.pageid, x.age
<span style="color:#000088 !important">FROM</span> tmptable x
<span style="color:#000088 !important">JOIN</span>
newuser y
<span style="color:#000088 !important">ON</span> (x.age = y.age);    </code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

MAP JOIN操作

如果你有一张表非常非常小,而另一张关联的表非常非常大的时候,你可以使用mapjoin此Join 操作在 Map 阶段完成,不再需要Reduce,也就不需要经过Shuffle过程,从而能在一定程度上节省资源提高JOIN效率前提条件是需要的数据在 Map 的过程中可以访问到。比如查询:

<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">INSERT</span> OVERWRITE <span style="color:#000088 !important">TABLE</span> pv_users <span style="color:#000088 !important">SELECT</span> /*+ MAPJOIN(pv) */ pv.pageid, u.age <span style="color:#000088 !important">FROM</span> page_view pv <span style="color:#000088 !important">JOIN</span> <span style="color:#000088 !important">user</span> u <span style="color:#000088 !important">ON</span> (pv.userid = u.userid);    </code></span>
  • 1
  • 2
  • 3
  • 4

  可以在 Map 阶段完成 Join,如图所示:

  相关的参数为:

  • hive.join.emit.interval = 1000
  • hive.mapjoin.size.key = 10000
  • hive.mapjoin.cache.numrows = 10000

参考于Hive MapJoin:值得注意的是,Hive版本0.11之后,Hive默认启动该优化,也就是不在需要显示的使用MAPJOIN标记,其会在必要的时候触发该优化操作将普通JOIN转换成MapJoin

两个属性来设置该优化的触发时机

<span style="color:#000000"><code>hive<span style="color:#009900 !important">.auto</span><span style="color:#009900 !important">.convert</span><span style="color:#009900 !important">.join</span></code></span>
  • 1

默认值为true,自动开户MAPJOIN优化

<span style="color:#000000"><code>hive<span style="color:#009900 !important">.mapjoin</span><span style="color:#009900 !important">.smalltable</span><span style="color:#009900 !important">.filesize</span></code></span>
  • 1

默认值为2500000(25M),通过配置该属性来确定使用该优化的表的大小,如果表的大小小于此值就会被加载进内存中

GROUP BY操作

进行GROUP BY操作时需要注意一下几点:

  • Map端部分聚合

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

      这里需要修改的参数为:

hive.map.aggr=true(用于设定是否在 map 端进行聚合,默认值为真) hive.groupby.mapaggr.checkinterval=100000(用于设定 map 端进行聚合操作的条目数)

  • 有数据倾斜时进行负载均衡

      此处需要设定 hive.groupby.skewindata,当选项设定为 true 是,生成的查询计划有两个MapReduce 任务。

    1. 在第一个 MapReduce 中,map 的输出结果集合会随机分布到 reduce 中, 每个reduce 做部分聚合操作,并输出结果。这样处理的结果是,相同的 Group By Key 有可能分发到不同的 reduce 中,从而达到负载均衡的目的
    2. 第二个 MapReduce 任务再根据预处 理的数据结果按照 Group By Key 分布到 reduce 中(这个过程可以保证相同的 Group By Key 分布到同一个 reduce 中),最后完成最终的聚合操作。

合并小文件

  我们知道文件数目小,容易在文件存储端造成瓶颈,给 HDFS 带来压力,影响处理效率。对此,可以通过合并Map和Reduce的结果文件来消除这样的影响。

  用于设置合并属性的参数有:

  • 是否合并Map输出文件:hive.merge.mapfiles=true(默认值为真)
  • 是否合并Reduce 端输出文件:hive.merge.mapredfiles=false(默认值为假)
  • 合并文件的大小:hive.merge.size.per.task=256*1000*1000(默认值为 256000000)

补充:实际集群操作过程中,join时候的小表在前的原则是比较先接触到的,这点在查阅一些资料和问过同事之后觉得是最快优化join操作的,而mapjoin则几乎没有用到,可能接触到的小表量级也是比较大的,而且公司的hive貌似是0.12的了,应该是自动优化的把

程序角度优化

熟练使用SQL提高查询

熟练地使用 SQL,能写出高效率的查询语句。

  场景:有一张 user 表,为卖家每天收到表,user_id,ds(日期)为 key,属性有主营类目,指标有交易金额,交易笔数。每天要取前10天的总收入,总笔数,和最近一天的主营类目。   

常用方法

<span style="color:#000000"><code class="language-sql"># 第一步:利用分析函数,取每个 user_id 最近一天的主营类目,存入临时表 t1。
<span style="color:#000088 !important">CREATE</span> <span style="color:#000088 !important">TABLE</span> t1 <span style="color:#000088 !important">AS</span>
<span style="color:#000088 !important">SELECT</span> user_id,substr(<span style="color:#009900 !important">MAX</span>(CONCAT(ds,cat),<span style="color:#006666 !important">9</span>) <span style="color:#000088 !important">AS</span> main_cat)
<span style="color:#000088 !important">FROM</span> users
<span style="color:#000088 !important">WHERE</span> ds=<span style="color:#006666 !important">20120329</span> // <span style="color:#006666 !important">20120329</span> 为日期列的值,实际代码中可以用函数表示出当天日期 <span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> user_id; # 第二步:汇总 10 天的总交易金额,交易笔数,存入临时表 t2
<span style="color:#000088 !important">CREATE</span> <span style="color:#000088 !important">TABLE</span> t2 <span style="color:#000088 !important">AS</span>
<span style="color:#000088 !important">SELECT</span> user_id,<span style="color:#009900 !important">sum</span>(qty) <span style="color:#000088 !important">AS</span> qty,<span style="color:#009900 !important">SUM</span>(amt) <span style="color:#000088 !important">AS</span> amt
<span style="color:#000088 !important">FROM</span> users
<span style="color:#000088 !important">WHERE</span> ds BETWEEN <span style="color:#006666 !important">20120301</span> <span style="color:#000088 !important">AND</span> <span style="color:#006666 !important">20120329</span>
<span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> user_id # 第三步:关联 t1,t2,得到最终的结果。
<span style="color:#000088 !important">SELECT</span> t1.user_id,t1.main_cat,t2.qty,t2.amt
<span style="color:#000088 !important">FROM</span> t1
<span style="color:#000088 !important">JOIN</span> t2 <span style="color:#000088 !important">ON</span> t1.user_id=t2.user_id</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

优化方法 

<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">SELECT</span> user_id,substr(<span style="color:#009900 !important">MAX</span>(CONCAT(ds,cat)),<span style="color:#006666 !important">9</span>) <span style="color:#000088 !important">AS</span> main_cat,<span style="color:#009900 !important">SUM</span>(qty),<span style="color:#009900 !important">SUM</span>(amt)
<span style="color:#000088 !important">FROM</span> users
<span style="color:#000088 !important">WHERE</span> ds BETWEEN <span style="color:#006666 !important">20120301</span> <span style="color:#000088 !important">AND</span> <span style="color:#006666 !important">20120329</span>
<span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> user_id</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在工作中我们总结出:方案 2 的开销等于方案 1 的第二步的开销,性能提升,由原有的 25 分钟完成,缩短为 10 分钟以内完成。节省了两个临时表的读写是一个关键原因,这种方式也适用于 Oracle 中的数据查找工作。 SQL 具有普适性,很多 SQL 通用的优化方案在 Hadoop 分布式计算方式中也可以达到效果。

补充:实际集群操作过程中,第一种普通操作是要被同事嘲笑的,一般写join的复合类的操作,我们尽量将把它写在同一段代码中,所以可能会出现一段hive有七八个join,只有当需要产出中间表或者业务逻辑有点混乱的时候,我们才存储中间表然后再重新写下,一般而言,我们抽取数据的时候使用核心表去left join其他表,这样就保证了核心表中的字段都会在,即使匹配不到也会存在Null而不是数据的丢失,这对于我们之后计算指标来说是比较重要的

无效ID在关联时的数据倾斜问题

问题:日志中常会出现信息丢失,比如每日约为 20 亿的全网日志,其中的 user_id 为主 键,在日志收集过程中会丢失,出现主键为 null 的情况,如果取其中的 user_id 和 bmw_users 关联,就会碰到数据倾斜的问题。原因是 Hive 中,主键为 null 值的项会被当做相同的 Key 而分配进同一个计算 Map。

  • 解决方法 1:user_id 为空的不参与关联,子查询过滤 null
<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span> log a
<span style="color:#000088 !important">JOIN</span>
bmw_users b
<span style="color:#000088 !important">ON</span> a.user_id <span style="color:#000088 !important">IS</span> <span style="color:#000088 !important">NOT</span> <span style="color:#000088 !important">NULL</span> <span style="color:#000088 !important">AND</span> a.user_id=b.user_id
<span style="color:#000088 !important">UNION</span> <span style="color:#000088 !important">All</span>
<span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span> log a
<span style="color:#000088 !important">WHERE</span> a.user_id <span style="color:#000088 !important">IS</span> <span style="color:#000088 !important">NULL</span></code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 解决方法 2 如下所示:函数过滤 null
<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span> log a
<span style="color:#000088 !important">LEFT</span> <span style="color:#000088 !important">OUTER</span> <span style="color:#000088 !important">JOIN</span>
bmw_users b
<span style="color:#000088 !important">ON</span>
<span style="color:#000088 !important">CASE</span> <span style="color:#000088 !important">WHEN</span> a.user_id <span style="color:#000088 !important">IS</span> <span style="color:#000088 !important">NULL</span> <span style="color:#000088 !important">THEN</span> CONCAT(<span style="color:#009900 !important">'dp_hive'</span>,RAND()) <span style="color:#000088 !important">ELSE</span> a.user_id <span style="color:#000088 !important">END</span> =b.user_id;  // 这句话写的好骚气啊,还有这种操作,我没有试过</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

调优结果:原先由于数据倾斜导致运行时长超过 1 小时,解决方法 1 运行每日平均时长 25 分钟,解决方法 2 运行的每日平均时长在 20 分钟左右。优化效果很明显。

  我们在工作中总结出:解决方法2比解决方法1效果更好,不但IO少了,而且作业数也少了。解决方法1中log读取两次,job 数为2。解决方法2中 job 数是1。这个优化适合无效 id(比如-99、 ‘’,null 等)产生的倾斜问题。把空值的 key 变成一个字符串加上随机数,就能把倾斜的 数据分到不同的Reduce上,从而解决数据倾斜问题。因为空值不参与关联,即使分到不同 的 Reduce 上,也不会影响最终的结果。附上 Hadoop 通用关联的实现方法是:关联通过二次排序实现的,关联的列为 partion key,关联的列和表的 tag 组成排序的 group key,根据 pariton key分配Reduce。同一Reduce内根据group key排序。

不同数据类型关联产生的倾斜问题

问题:不同数据类型 id 的关联会产生数据倾斜问题。

​ 一张表 s8 的日志,每个商品一条记录,要和商品表关联。但关联却碰到倾斜的问题。 s8 的日志中有 32 为字符串商品 id,也有数值商品 id,日志中类型是 string 的,但商品中的 数值 id 是 bigint 的。猜想问题的原因是把 s8 的商品 id 转成数值 id 做 hash 来分配 Reduce, 所以字符串 id 的 s8 日志,都到一个 Reduce 上了,解决的方法验证了这个猜测。

  • 解决方法:把数据类型转换成字符串类型
<span style="color:#000000"><code class="language-Sql"><span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span> s8_log a
<span style="color:#000088 !important">LEFT</span> <span style="color:#000088 !important">OUTER</span> <span style="color:#000088 !important">JOIN</span>
r_auction_auctions b
<span style="color:#000088 !important">ON</span> a.auction_id=<span style="color:#000088 !important">CASE</span>(b.auction_id <span style="color:#000088 !important">AS</span> STRING) </code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

调优结果显示:数据表处理由 1 小时 30 分钟经代码调整后可以在 20 分钟内完成。

补充:话说hive不是自带隐式转换么,需要将bigint类型强制转换成string类型?

利用Hive对UNION ALL优化的特性

问题:比如推广效果表要和商品表关联,效果表中的 auction_id 列既有 32 为字符串商 品 id,也有数字 id,和商品表关联得到商品的信息。

  • 解决方法:union all
<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span> effect a
<span style="color:#000088 !important">JOIN</span>
(<span style="color:#000088 !important">SELECT</span> auction_id <span style="color:#000088 !important">AS</span> auction_id <span style="color:#000088 !important">FROM</span> auctions <span style="color:#000088 !important">UNION</span> <span style="color:#000088 !important">All</span> <span style="color:#000088 !important">SELECT</span> auction_string_id <span style="color:#000088 !important">AS</span> auction_id <span style="color:#000088 !important">FROM</span> auctions
)b
<span style="color:#000088 !important">ON</span> a.auction_id=b.auction_id </code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

多表 union all 会优化成一个 job。比分别过滤数字 id,字符串 id 然后分别和商品表关联性能要好。

这样写的好处:1 个 MapReduce 作业,商品表只读一次,推广效果表只读取一次。把 这个 SQL 换成 Map/Reduce 代码的话,Map 的时候,把 a 表的记录打上标签 a,商品表记录 每读取一条,打上标签 b,变成两个

解决Hive对UNION ALL优化的短板

Hive 对 union all 的优化的特性:对 union all 优化只局限于非嵌套查询

消灭子查询内的 group by

  • 示例 1:子查询内有 group by
<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span>
(<span style="color:#000088 !important">SELECT</span> * <span style="color:#000088 !important">FROM</span> t1 <span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> c1,c2,c3
<span style="color:#000088 !important">UNION</span> <span style="color:#000088 !important">ALL</span> <span style="color:#000088 !important">SELECT</span>* <span style="color:#000088 !important">FROM</span> t2 <span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> c1,c2,c3
)t3
<span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> c1,c2,c3 </code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  从业务逻辑上说,子查询内的 GROUP BY 怎么都看显得多余(功能上的多余,除非有 COUNT(DISTINCT)),如果不是因为 Hive Bug 或者性能上的考量(曾经出现如果不执行子查询 GROUP BY,数据得不到正确的结果的 Hive Bug)。所以这个 Hive 按经验转换成如下所示:

<span style="color:#000000"><code><span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span>
(<span style="color:#000088 !important">SELECT</span> * <span style="color:#000088 !important">FROM</span> t1 <span style="color:#000088 !important">UNION</span> <span style="color:#000088 !important">ALL</span> <span style="color:#000088 !important">SELECT</span> * <span style="color:#000088 !important">FROM</span> t2
)t3
<span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> c1,c2,c3 </code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  调优结果:经过测试,并未出现 union all 的 Hive Bug,数据是一致的。MapReduce 的 作业数由 3 减少到 1。

​ t1 相当于一个目录,t2 相当于一个目录,对 Map/Reduce 程序来说,t1,t2 可以作为 Map/Reduce 作业的 mutli inputs。这可以通过一个 Map/Reduce 来解决这个问题。Hadoop 的 计算框架,不怕数据多,就怕作业数多。

  但如果换成是其他计算平台如 Oracle,那就不一定了,因为把大的输入拆成两个输入, 分别排序汇总后 merge(假如两个子排序是并行的话),是有可能性能更优的(比如希尔排 序比冒泡排序的性能更优)。

消灭子查询内的 COUNT(DISTINCT),MAX,MIN。

<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span>
(<span style="color:#000088 !important">SELECT</span> * <span style="color:#000088 !important">FROM</span> t1 <span style="color:#000088 !important">UNION</span> <span style="color:#000088 !important">ALL</span> <span style="color:#000088 !important">SELECT</span> c1,c2,c3,<span style="color:#009900 !important">COUNT</span>(<span style="color:#000088 !important">DISTINCT</span> c4) <span style="color:#000088 !important">FROM</span> t2 <span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> c1,c2,c3
)t3
<span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> c1,c2,c3; </code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

  由于子查询里头有 COUNT(DISTINCT)操作,直接去 GROUP BY 将达不到业务目标。这时采用临时表消灭 COUNT(DISTINCT)作业不但能解决倾斜问题,还能有效减少 jobs。

<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">INSERT</span> t4 <span style="color:#000088 !important">SELECT</span> c1,c2,c3,c4 <span style="color:#000088 !important">FROM</span> t2 <span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> c1,c2,c3; <span style="color:#000088 !important">SELECT</span>
c1,
c2,
c3,
<span style="color:#009900 !important">SUM</span>(income),
<span style="color:#009900 !important">SUM</span>(uv)
<span style="color:#000088 !important">FROM</span>
(<span style="color:#000088 !important">SELECT</span> c1,c2,c3,income,<span style="color:#006666 !important">0</span> <span style="color:#000088 !important">AS</span> uv <span style="color:#000088 !important">FROM</span> t1 <span style="color:#000088 !important">UNION</span> <span style="color:#000088 !important">ALL</span> <span style="color:#000088 !important">SELECT</span> c1,c2,c3,<span style="color:#006666 !important">0</span> <span style="color:#000088 !important">AS</span> income,<span style="color:#006666 !important">1</span> <span style="color:#000088 !important">AS</span> uv <span style="color:#000088 !important">FROM</span> t2
)t3
<span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> c1,c2,c3;</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

  job 数是 2,减少一半,而且两次 Map/Reduce 比 COUNT(DISTINCT)效率更高。

​ 调优结果:千万级别的类目表,member 表,与 10 亿级得商品表关联。原先 1963s 的任务经过调整,1152s 即完成。

消灭子查询内的 JOIN

<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">SELECT</span>
*
<span style="color:#000088 !important">FROM</span>
(<span style="color:#000088 !important">SELECT</span> * <span style="color:#000088 !important">FROM</span> t1 <span style="color:#000088 !important">UNION</span> <span style="color:#000088 !important">ALL</span> <span style="color:#000088 !important">SELECT</span> * <span style="color:#000088 !important">FROM</span> t4 <span style="color:#000088 !important">UNION</span> <span style="color:#000088 !important">ALL</span> <span style="color:#000088 !important">SELECT</span> * <span style="color:#000088 !important">FROM</span> t2 <span style="color:#000088 !important">JOIN</span> t3 <span style="color:#000088 !important">ON</span> t2.id=t3.id
)x
<span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> c1,c2; </code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

上面代码运行会有 5 个 jobs。加入先 JOIN 生存临时表的话 t5,然后 UNION ALL,会变成 2 个 jobs。

<span style="color:#000000"><code class="language-sql"><span style="color:#000088 !important">INSERT</span> OVERWRITE <span style="color:#000088 !important">TABLE</span> t5
<span style="color:#000088 !important">SELECT</span> * <span style="color:#000088 !important">FROM</span> t2 <span style="color:#000088 !important">JOIN</span> t3 <span style="color:#000088 !important">ON</span> t2.id=t3.id; <span style="color:#000088 !important">SELECT</span> * <span style="color:#000088 !important">FROM</span> (t1 <span style="color:#000088 !important">UNION</span> <span style="color:#000088 !important">ALL</span> t4 <span style="color:#000088 !important">UNION</span> <span style="color:#000088 !important">ALL</span> t5); </code></span>
  • 1
  • 2
  • 3
  • 4

  调优结果显示:针对千万级别的广告位表,由原先 5 个 Job 共 15 分钟,分解为 2 个 job 一个 8-10 分钟,一个3分钟。

补充:第二个消灭子查询内的 JOIN,我觉得意义不是很大,第一需要建立临时表,这个内表还需要定时清理,第二没有从原理上进行优化,只是把步骤分开了而已,所以意义并非那么大,如果说从减少job的意义上来说,的确有提升,但是如果是业务代码块,感觉分开写意义不是很大,这是我个人的理解

GROUP BY替代COUNT(DISTINCT)达到优化效果

  计算 uv 的时候,经常会用到 COUNT(DISTINCT),但在数据比较倾斜的时候 COUNT(DISTINCT) 会比较慢。这时可以尝试用 GROUP BY 改写代码计算 uv。

  • 原有代码
<span style="color:#000000"><code class="language-SQL"><span style="color:#000088 !important">ALTER</span>  <span style="color:#000088 !important">TABLE</span> s_dw_tanx_adzone_uv <span style="color:#000088 !important">ADD</span> PARTITION (ds=<span style="color:#006666 !important">20120329</span>)
<span style="color:#000088 !important">SELECT</span> <span style="color:#006666 !important">20120329</span> <span style="color:#000088 !important">AS</span> thedate,adzoneid,<span style="color:#009900 !important">COUNT</span>(<span style="color:#000088 !important">DISTINCT</span> acookie) <span style="color:#000088 !important">AS</span> uv
<span style="color:#000088 !important">FROM</span> s_ods_log_tanx_pv t
<span style="color:#000088 !important">WHERE</span> t.ds=<span style="color:#006666 !important">20120329</span>
<span style="color:#000088 !important">GROUP</span> <span style="color:#000088 !important">BY</span> adzoneid</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

关于COUNT(DISTINCT)的数据倾斜问题不能一概而论,要依情况而定,下面是我测试的一组数据:

测试数据:169857条

<span style="color:#000000"><code class="language-SQL">#统计每日IP
<span style="color:#000088 !important">CREATE</span> <span style="color:#000088 !important">TABLE</span> ip_2014_12_29 <span style="color:#000088 !important">AS</span>
<span style="color:#000088 !important">SELECT</span>
<span style="color:#009900 !important">COUNT</span>(<span style="color:#000088 !important">DISTINCT</span> ip) <span style="color:#000088 !important">AS</span> IP
<span style="color:#000088 !important">FROM</span> logdfs
<span style="color:#000088 !important">WHERE</span> logdate=<span style="color:#009900 !important">'2014_12_29'</span>;
耗时:24.805 seconds #统计每日IP(改造)
<span style="color:#000088 !important">CREATE</span> <span style="color:#000088 !important">TABLE</span> ip_2014_12_29 <span style="color:#000088 !important">AS</span>
<span style="color:#000088 !important">SELECT</span>
<span style="color:#009900 !important">COUNT</span>(<span style="color:#006666 !important">1</span>) <span style="color:#000088 !important">AS</span> IP
<span style="color:#000088 !important">FROM</span>
(
<span style="color:#000088 !important">SELECT</span>
<span style="color:#000088 !important">DISTINCT</span> ip
<span style="color:#000088 !important">from</span> logdfs
<span style="color:#000088 !important">WHERE</span> logdate=<span style="color:#009900 !important">'2014_12_29'</span>
)tmp;
耗时:46.833 seconds</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  测试结果表名:明显改造后的语句比之前耗时,这是因为改造后的语句有2个SELECT,多了一个job,这样在数据量小的时候,数据不会存在倾斜问题。

补充:相当于多做子查询操作,那肯定是变慢的


优化总结

  优化时,把hive sql当做mapreduce程序来读,会有意想不到的惊喜。理解hadoop的核心能力,是hive优化的根本。这是这一年来,项目组所有成员宝贵的经验总结。

长期观察hadoop处理数据的过程,有几个显著的特征:

  1. 不怕数据多,就怕数据倾斜。
  2. 对jobs数比较多的作业运行效率相对比较低,比如即使有几百行的表,如果多次关联多次汇总,产生十几个jobs,没半小时是跑不完的。map reduce作业初始化的时间是比较长的。
  3. 对sum,count来说,不存在数据倾斜问题。
  4. 对count(distinct ),效率较低,数据量一多,准出问题,如果是多count(distinct )效率更低。

优化可以从几个方面着手:

  1. 好的模型设计事半功倍。
  2. 解决数据倾斜问题。
  3. 减少job数。
  4. 设置合理的map reduce的task数,能有效提升性能。(比如,10w+级别的计算,用160个reduce,那是相当的浪费,1个足够)。
  5. 自己动手写sql解决数据倾斜问题是个不错的选择。set hive.groupby.skewindata=true;这是通用的算法优化,但算法优化总是漠视业务,习惯性提供通用的解决方法。 Etl开发人员更了解业务,更了解数据,所以通过业务逻辑解决倾斜的方法往往更精确,更有效。
  6. 对count(distinct)采取漠视的方法,尤其数据大的时候很容易产生倾斜问题,不抱侥幸心理。自己动手,丰衣足食。
  7. 对小文件进行合并,是行至有效的提高调度效率的方法,假如我们的作业设置合理的文件数,对云梯的整体调度效率也会产生积极的影响。

优化时把握整体,单个作业最优不如整体最优。


更新

这是一个积累的过程,所以以后必然会有更新

  • 2017.07.29 第一次更新
  • 2017.08.04 第二次更新

致谢

  • Hive性能优化

  • Hive之insert 和insert overwrite

  • split和block的区别以及maptask和reducetask个数设定

  • hadoop中,combine、partition、shuffle作用分别是什么?

  • Hive的HQL语句及数据倾斜解决方案

  • Hive - hive.groupby.skewindata环境变量与负载均衡

  • 深入浅出数据仓库中SQL性能优化之Hive篇

总结:Hive性能优化上的一些总结相关推荐

  1. hive性能优化指南

    1.概述 继续<hive性能优化指南--初级篇>一文中的剩余部分,本篇博客赘述了在工作中总结Hive的常用优化手段和在工作中使用Hive出现的问题.下面开始本篇文章的优化介绍. 2.介绍 ...

  2. Hive性能优化(全面)

    1.介绍 首先,我们来看看Hadoop的计算框架特性,在此特性下会衍生哪些问题? 数据量大不是问题,数据倾斜是个问题. jobs数比较多的作业运行效率相对比较低,比如即使有几百行的表,如果多次关联多次 ...

  3. Hive 性能优化(全面)解决数据倾斜等问题

    Hive性能优化(全面) 1.介绍 首先,我们来看看Hadoop的计算框架特性,在此特性下会衍生哪些问题? 数据量大不是问题,数据倾斜是个问题. jobs数比较多的作业运行效率相对比较低,比如即使有几 ...

  4. Hive性能优化(全面)解决数据倾斜等问题

    Hive性能优化(全面) 1.介绍 首先,我们来看看Hadoop的计算框架特性,在此特性下会衍生哪些问题? 数据量大不是问题,数据倾斜是个问题. jobs数比较多的作业运行效率相对比较低,比如即使有几 ...

  5. 7.Hive性能优化及Hive3新特性

    1.Hive表设计优化 分区表优化查询速度 分桶表优化join速度 索引优化(在Hive3后移除,了解即可) 2.Hive表数据优化 2.1 文件格式 概述 Hive数据存储的本质市HDFS,所有数据 ...

  6. 多层科目任意组合汇总报表的性能优化 (上)

    一 问题背景 我们先来看一张资产负债表: 这是一个典型的中国式复杂报表格式,其复杂并不在于布局,而在于其中"期末余额"的每个单元格都是一个需要独立计算的指标,互相之间几乎没有关系, ...

  7. h5如何上传文件二进制流_Hadoop如何将TB级大文件的上传性能优化上百倍?

    这篇文章,我们来看看,Hadoop的HDFS分布式文件系统的文件上传的性能优化. 首先,我们还是通过一张图来回顾一下文件上传的大概的原理. 由上图所示,文件上传的原理,其实说出来也简单. 比如有个TB ...

  8. 代码覆盖率在性能优化上的一种可行应用

    You can't manage what you can't measure. 一件事如果你无法衡量它,你就无法管理它.--管理大师 彼得·德鲁克 前言 JavaScript 是前端应用主要语言,相 ...

  9. 阿里三面 Android 研发岗,竟然挂在了性能优化上……

    作为一个程序员,性能优化是无法避开的事情,并且性能优化也是软件系统中最有挑战的工作之一,更是每个工程师都需要掌握的核心技能. 性能问题和Bug不同,后者的分析和解决思路更清晰,很多时候从应用日志即可直 ...

最新文章

  1. lucene源码分析(2)读取过程实例
  2. python把桢写入txt_ffmpeg 常用参数一览表及python 使用示例
  3. 用计算机答题答案提交后能否改错,南京晓庄计算机操作系统习题库含答案全1-5章...
  4. python hsv inrange 范围_仅20行代码,用python给证件照换底色
  5. 计算机网络交换机组网及虚拟局域网实验报告,计算机网络实验报告材料(虚拟局域网).doc...
  6. vb登录ftp服务器并打开文档,VB.Net实现登陆Ftp的方法
  7. electron sqlite3_Electron+React+Antd工程搭建
  8. 部分iPhone13 系统有bug
  9. OpenSSL 1.0.0生成p12、jks、crt等格式证书的命令个过程(转)
  10. MTK OTG 功能总结(UVC)
  11. 用户画像业务数据调研及ETL(二)持续更新中...
  12. 概率论与数理统计基础概念与重要定义汇总
  13. 滁州学院元旦晚会计算机,滁州学院机械学院2017元旦晚会精彩上演
  14. 全靠这套面试题,历经一年学弟从家里到了阿里,只要有梦想总会实现的
  15. java web 邮件_Javaweb_邮件发送
  16. 【论文解析】Anchor-Free Person Search
  17. 2022-2028年中国高速铁路行业投资分析及前景预测报告
  18. 简易华为人工智能服务器,华为发布昇腾AI全栈软件平台:极致性能、简单易用...
  19. 在xubuntu-destory中怎么恢复到ubuntu原来的主题和登陆界面
  20. Java游戏西游前传,《西游前传》西游记之前发生的故事

热门文章

  1. 手机app测试分析方法 -- 逻辑分析法(数据测试方法)
  2. 致----想要转行做Java的朋友们一封信
  3. 书摘 - 重新定义公司:谷歌是如何运营的
  4. Oracle 备份、恢复表空间数据步骤
  5. 【vue动态加载js文件】
  6. java和python哪个运行速度快_Python与Java-你首选哪个?
  7. 最新深度学习入门高赞教材,只需高中数学基础 | 资源
  8. Tomcat 面试题汇总
  9. 测试控件页面 html,Web页面测试总结—控件类
  10. css中如何实现帧布局_如何在游戏中实时显示硬件占用率和帧数