大数据学习之路,不定时修改和增加内容,欢迎指正
第5章 Hive Job优化
5.1 Hive Map优化
5.1.1 复杂文件增加Map数
当input的文件都很大,任务逻辑复杂,map执行非常慢的时候,可以考虑增加Map数,来使得每个map处理的数据量减少,从而提高任务的执行效率。
增加map的方法为:根据computeSlite(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M公式,调整maxSize最大值,让maxSize最大值低于blocksize就可以增加map的个数。
案例实操:
1)执行查询
hive (default)> select count(*) from emp;
Hadoop job information for Stage-1: number of mappers: 1; number of reducers: 1
2)设置最大切片值为100个字节
hive (default)> set mapreduce.input.fileinputformat.split.maxsize=100;
hive (default)> select count(*) from emp;
Hadoop job information for Stage-1: number of mappers: 6; number of reducers: 1
5.1.2 小文件进行合并
1)在map执行前合并小文件,减少map数:CombineHiveInputFormat具有对小文件进行合并的功能(系统默认的格式)。HiveInputFormat没有对小文件合并的功能。
set hive.input.format= org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
2)在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-reudce任务进行文件merge
set hive.merge.smallfiles.avgsize = 16777216;
5.1.3 Map端聚合
set hive.map.aggr=true;#相当于 map 端执行 combiner
5.1.4 推测执行
根据一定的法则推测出“拖后腿”的任务,并为这样的任务启动一个备份任务,让该任务与原始任务同时处理同一份数据,并最终选用最先成功运行完成任务的计算结果作为最终结果。
set mapred.map.tasks.speculative.execution = true #默认是 true
5.2 Hive Reduce优化
5.2.1 合理设置Reduce数
1)调整reduce个数方法一
(1)每个Reduce处理的数据量默认是256MB
set hive.exec.reducers.bytes.per.reducer = 256000000;
(2)每逢任务最大的reduce数,默认1009
set hive.exec.reducers.max = 1009;
(3)计算reduce数的公式
N=min(参数 2,总输入数据量/参数 1)(参数 2 指的是上面的 1009,参数 1 值得是 256M)
2)调整reduce个数方法二
在hadoop的mapred-default.xml文件中修改
设置每个job的Reduce个数
set mapreduce.job.reduces = 15;
3)reduce个数并不是越多越好
(1)过多的启动和初始化reduce也会消耗时间和资源;
(2)另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,而且这些小文件作为下一个任务的输入,则会出现小文件过多的问题;
在设置reduce个数的时候也需要考虑这两个原则:处理大数据量利用合适的reduce数,使单个reduce任务处理数据量大小要合适。
5.3.2 推测执行
mapred.reduce.tasks.speculative.execution (hadoop 里面的)
hive.mapred.reduce.tasks.speculative.execution(hive 里面相同的参数,效果和
hadoop 里面的一样两个随便哪个都行)
5.3 Hive任务整体优化
5.3.1 Fetch抓取
Fetch抓取是指,Hive中对某些情况的查询可以不必使用MapReduce计算。例如:select * from emp;在这种情况下,Hive可以简单地读取emp对应的存储目录下的文件,然后输出查询结果到控制台。
在hive-default.xml.template文件中hive.fetch.task.conversion默认是more,老版本hive默认是minimal,该属性默认修改为more以后,在全局查找、字段查找、limit查找等都不走mapreduce。
<property>
<name>hive.fetch.task.conversion</name>
<value>more</value>
<description>
Expects one of [none, minimal, more].
Some select queries can be converted to single FETCH task minimizing
latency.
Currently the query should be single sourced not having any subquery
and should not have any aggregations or distincts (which incurs RS),
lateral views and joins.
0. none : disable hive.fetch.task.conversion
1. minimal : SELECT STAR, FILTER on partition columns, LIMIT only
2. more : SELECT, FILTER, LIMIT only (support TABLESAMPLE and
virtual columns)
</description>
</property>
1)案例实操
(1)把hive.fetch.task.conversion设置成none,然后执行查询语句,都会执行mapreduce程序。
hive (default)> set hive.fetch.task.conversion=none;
hive (default)> select * from emp;
hive (default)> select ename from emp;
hive (default)> select ename from emp limit 3;
(2)把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;
5.3.2 本地模式
大多数的Hadoop Job是需要Hadoop提供的完整的可扩展性来处理大数据集的。不过,有时Hive的输入数据量是非常小的。在这种情况下,为查询触发执行任务消耗的时间可能会比实际job的执行时间要多得多。对于大多数这种情况,Hive可以通过本地模式在单台机器上处理所有的任务。对于小数据集,执行时间可以明显被缩短。
用户可以通过设置hive.exec.mode.local.auto的值为true,来让Hive在适当的时候自动启动这个优化。
set hive.exec.mode.local.auto=true; //开启本地 mr
//设置 local mr 的最大输入数据量,当输入数据量小于这个值时采用 local mr 的方式,默认为 134217728,即 128M
set hive.exec.mode.local.auto.inputbytes.max=50000000;
//设置 local mr 的最大输入文件个数,当输入文件个数小于这个值时采用 local mr 的方式,默认为 4
set hive.exec.mode.local.auto.input.files.max=10;
1)案例实操
(1)开启本地模式,并执行查询语句
hive (default)> set hive.exec.mode.local.auto=true;
hive (default)> select * from emp cluster by deptno;
Time taken: 1.328 seconds, Fetched: 14 row(s)
(2)关闭本地模式,并执行查询语句
hive (default)> set hive.exec.mode.local.auto=false;
hive (default)> select * from emp cluster by deptno;
Time taken: 20.09 seconds, Fetched: 14 row(s)
5.3.3 并行执行
Hive会将一个查询转化成一个或者多个阶段。这样的阶段可以实MapReduce阶段、抽样阶段、合并阶段、limit阶段。或者Hive执行过程中可能需要的其他阶段。默认情况下,Hive一次只会执行一个阶段。不过,某个特定的job可能包含众多的阶段,而这些阶段可能并非完全相互依赖,也就是说有些阶段是可以并行执行的,这样可能使得整个job的执行时间缩短。不过,如果有更多的阶段可以并行执行,那么job可能就越快完成。
通过设置参数hive.exec.parallel的值为true,就可以开启并行执行。不过,在共享集群中,需要注意下,如果job中并行阶段增多,那么集群利用率就会增加。
set hive.exec.parallel=true; //打开任务并行执行,默认为 false
set hive.exec.parallel.thread.number=16; //同一个 sql 允许最大并行度,默认为 8
当让,得是在系统资源比较空闲的时候才用优势,否则没有资源,并行也起不来
(建议在数据量大,sql很长的时候使用,数据量小,sql比较短,开启可能还不如之前快)。
5.3.4 严格模式
Hive可以通过设置防止一些危险操作
1)分区表不使用分区过滤
将hive.strict.checks.no.partition.filter设置为true时,对于分区表,除非where语句中含有分区字段过滤条件来限制范围,否则不允许执行。换句话说,就是用户不允许扫描所有分区。进行这个限制的原因是,通常分区表都拥有非常大的数据集,而且数据量增加迅速。如果没有进行分区限制的查询可能消耗令人不可接受的巨大资源来处理这个表。
2)使用order by没有limit过滤
将hive.strict.checks.orderby.no.limit设置为true时,对于使用了order by语句的查询,要求必须使用limit语句。因为order by为了执行排序过程会将所有的结果分发到同一个Reducer中进行处理,强制要求用户增加这个limit语句可以防止Reducer额外执行很长一段时间(开启了limit可以在数据进入到reduce之前就减少一部分数据)。
3)笛卡尔积
将hive.strict.checks.cartesian.product设置为true时,会限制笛卡尔积的查询。对关系型数据库非常了解的用户可能期望在执行Join查询的时候不使用on语句而是使用where语句,这样关系数据库的执行优化器就可以高效地将where语句转化成on语句。不幸的是,Hive并不会执行这种优化,因此,如果表足够大,那么这个查询就会出现不可控的情况。
5.3.5 JVM重用
小文件过多的时候使用。
第6章 Hive On Spark
6.1 Executor参数
以单台服务器128G内存,32线程为例。
6.1.1 spark.executor.cores
该参数表示每个Executor可利用的CPU核心数。其值不宜设定过大,因为Hive的底层以HDFS存储,而HDFS有时对高并发写入处理不太好,容易造成race condition。根据经验实践,设定在3~6之间比较合理。
假设我们使用的服务器单节点有32个CPU核心可供使用。考虑到系统基础服务和HDFS等组件的余量,一般会将YARN NodeManager的yarn.nodemanager.resource.cpu-vcores参数设为28,也就是YARN能够利用其中的28核,此时将spark.executor.cores设为4最合适,最多可以正好分配给7分Executor而不造成浪费。又假设yarn.nodemanager.resource.cpu-vcores为26,那么将spark.executor设为5最合适,只会剩余1个核。
由于一个Executor需要一个YARN Container来运行,所以还需要保证spark.executor.cores的值不能大于单个Container能申请到的最大核心数,即yarn.scheduler.maximum-allocation-vcores的值。
6.1.2 spark.executor.memory/spark.yarn.executor.memoryOverhead
这两个参数分别表示每个Executor可利用的堆内内存量核堆外内存量。堆内内存越大,Executor就能缓存更多的数据,在做诸如map join之类的操作时就会更快,但同时也会使得GC变得更麻烦。spark.yarn.executor.memoryOverhead的默认值时executorMemory * 0.10,最小值为384M(每个Executor)
Hive官方提供了一个计算Executor总内存量的经验公式,如下:yarn.nodemanager.resource.memory-mb * (spark.executor.cores/yarn.nodemanager.resource.cpu-vcores)
其实就是按核心数的比例分配。在计算出来的总内存量中80%~85%划分给堆内内存,剩余的划分给堆外内存。
假设集群中单节点有128G物理内存,yarn.nodemanager.resource.memory-mb(即单个NodeManager能够利用的主机内存量)设为100G,那么每个Executor大概就是100*(4/28)=约14G。
再按8:2比例划分的话,最终spark.executor.memory设为约11.2G,spark.yarn.executor.memoryOverhead设为约2.8G。
通过这些配置,每个主机一次可以运行多达7个executor。每个executor最多可以运行4个task(每个核一个)。因此task平均有3.5GB内存。在executor中运行的所有task共享相同的堆空间。
set spark.executor.memory=11.2g;
set spark.yarn.executor.memoryOverhead=2.8g;
同理,这两个内存参数相加的总量也不能超过单个Container最多能申请到的内存量,即yarn.scheduler.maximum-allocation-mb配置的值。
6.1.3 spark.executor.instance
该参数表示执行查询时一共启动多少个Executor实例,这取决于每个节点的资源分配情况以及集群的节点数。若我们一共有10台32C/128G的节点,并按照上述配置(即每个节点承载7个Executor),那么理论上讲我们可以将spark.executor.instance设为70,以使集群资源最大化利用,但是实际上一般都会适当设小一些(推荐是理论值的一半左右,比如40),因为Driver也要占用资源,并且一个YARN集群往往还要承载除了Hive on Spark之外的其他业务。
6.1.4 spark.dynamicAllocation.enabled
上面所说的固定分配Executor数量的方式可能不太灵活,尤其是在Hive集群面向很多用户提供分析服务的情况下。所以更推荐将spark.dynamicAllocation,enabled参数设为true,以启用Executor动态分配。
6.1.5 参数配置样例参考
set hive.execution.engine=spark;
set spark.executor.memory=11.2g;
set spark.yarn.executor.memoryOverhead=2.8g;
set spark.executor.cores=4;
set spark.executor.instances=40;
set spark.dynamicAllocation.enabled=true;
set spark.serializer=org.apache.spark.serializer.KryoSerializer;
6.2 Driver参数
6.2.1 spark.driver.cores
该参数表示每个Driver可利用的CPU核心数。绝大多数情况下设为1都够用。
6.2.2 spark.driver.memory/spark.driver.memoryOverhead
这两个参数分别表示每个Driver可利用的堆内内存量核堆外内存量。根据资源富裕程度核作业的大小,一般是将总量控制在512MB~4GB之间,并且沿用Executor内存的“二八分配方式”。例如,spark.driver.memory可以设为约819MB,spark.driver.memoryOverhead设为约205MB,加起来正好1G。