HIve调优

1、Fetch抓取机制

​ 我们在刚开始学习hive的时候,都知道hive可以降低程序员的学习成本和开发成本,具体表现就在于可以将SQL语句转换成MapReduce程序运行。但是Hive中对某些情况的查询可以不必使用MapReduce计算。例如:SELECT * FROM employees,在这种情况下,Hive可以简单地读取employee对应的存储目录下的文件,然后输出查询结果到控制台。

​ 在hive-default.xml文件中hive.fetch.task.conversion默认是more,老版本hive默认是minimal,该属性修改为more以后,在全局查找字段查找limit查找等都不走mapreduce。

​ 把hive.fetch.task.conversion设置成none,所有的程序都走mapreduce程序会耗费一定的时间。但就算设置成more,也只有部分sql语句会不走mapreduce程序,那有没有什么办法可以优化这个问题呢?

  • 功能:在执行sql的时候,能不走MapReduce程序处理就尽量不走MapReduce程序处理

  • 尽量直接去操作数据文件。

  • 在下述3种情况下 sql不走mr程序

    • --全局查找 select * from student;
    • --字段查找 select num,name from student;
    • --limit 查找 select num,name from student limit 2;

2、mapreduce本地模式

​ Hive 在集群上查询时,默认是在集群上 N 台机器上运行, 需要多个机器进行协调运行,这个方式很好地解决了大数据量的查询问题。但是当 Hive 查询处理的数据量比较小时,其实没有必要启动分布式模式去执行,因为以分布式方式执行就涉及到跨网络传输、多节点协调 等,并且消耗资源。mapreduce程序除了可以提交到yarn执行之外,还可以使用本地模拟环境运行,此时就不是分布式执行的程序,对于小数据集,使用本地模式执行时间可以明显被缩短。

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

  • 功能:如果非要执行MapReduce程序,能够本地执行的,尽量不提交yarn上执行。

  • 默认是关闭的。意味着只要走MapReduce就提交yarn执行。

    mapreduce.framework.name = local 本地模式
    mapreduce.framework.name = yarn 集群模式 
    
  • Hive提供了一个参数,自动切换MapReduce程序为本地模式,如果不满足条件,就执行yarn模式。

    set hive.exec.mode.local.auto = true;
     
    --3个条件必须都满足 自动切换本地模式
    The total input size of the job is lower than: hive.exec.mode.local.auto.inputbytes.max (128MB by default)  --数据量小于128M
    
    The total number of map-tasks is less than: hive.exec.mode.local.auto.tasks.max (4 by default)  --maptask个数少于4个
    
    The total number of reduce tasks required is 1 or 0.  --reducetask个数是0 或者 1
    

3、表的优化

(1)join优化

底层还是MapReduce的join优化

  • Map 端 join

    适合于小表join大表或者小表Join小表

    ​ Map 端 join是针对以下场景进行的优化:两个待连接表中,有一个表非常大,而另一个表非常小,以至于小表可以直接存放到内存中。这样,我们可以将小表复制多份,让每个map task内存中存在一份(比如存放到hash table中),然后只扫描大表:对于大表中的每一条记录key/value,在hash table中查找是否有相同的key的记录,如果有,则连接后输出即可

    ​ 如果不指定MapJoin或者不符合MapJoin的条件,那么Hive解析器会将Join操作转换成Common Join,即:在Reduce阶段完成join。容易发生数据倾斜。可以用MapJoin把小表全部加载到内存在map端进行join,避免reducer处理。

    • map端join的参数设置:

      开启mapjoin参数设置:

      (1)设置自动选择map join

      set hive.auto.convert.join = true; 默认为true

      (2)大表小表的阈值设置:

      set hive.mapjoin.smalltable.filesize= 25000000;

      ​ 小表的输入文件大小的阈值(以字节为单位);如果文件大小 小于此阈值,它将尝试将common join转换为map join。

      ​ 因此在实际使用中,只要根据业务把握住小表的阈值标准即可,hive会自动帮我们完成mapjoin,提高执行的效率。

  • reduce 端 join

    适合于大表Join大表

    reduce 端 join是一种最简单的join方式,其主要思想如下:

    ​ 在map阶段,map函数同时读取两个文件File1和File2,为了区分两种来源的key/value数据对,对每条数据打一个标签(tag),比如:tag=0表示来自文件File1,tag=2表示来自文件File2。即:map阶段的主要任务是对不同文件中的数据打标签。

    ​ 在reduce阶段,reduce函数获取key相同的来自File1和File2文件的value list, 然后对于同一个key,对File1和File2中的数据进行join(笛卡尔乘积)。即:reduce阶段进行实际的连接操作。

    ​ Reduce 端 join是非常低效的,因为shuffle阶段要进行大量的数据传输

    • (1)、空值过滤

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

      • 对空值进行过滤

        SELECT a.* FROM (SELECT * FROM tableA WHERE A.id IS NOT NULL ) a JOIN tableB b ON a.id = b.id;
        
    • (2)、空值转换

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

      • 如果不给 null赋随机的值会怎样?

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

      • 如何给null 赋一个随机的值? Hive中的 rand()函数

        SELECT a.*
        FROM tableA a
        LEFT JOIN 
        tableB b ON CASE WHEN a.id IS NULL 
        THEN concat('null', rand()) ELSE a.id END = b.id;
        
        
  • Q:如果两张表都是大表,想走Map join 怎么办?怎么办?

    • 把大表变小

      • 在join之前where过滤无关数据

      • 还可以分桶 join (bucket join)

        • 分桶:表分为多个文件,相同的字段分到同一个桶里

        • Hive采用对列值哈希,然后除以桶的个数求余的方式决定该条记录存放在哪个桶当中。

        • 分桶字段=join 字段

        • 分桶之后还可以进行排序 Sort Merge Bucket Join

        • set hive.optimize.buketmapjoin=true 默认

  • bucket join

    适合于大表Join大表

    • 方式1:Bucktet Map Join

      语法: clustered by colName(参与join的字段)
      参数: set hive.optimize.bucketmapjoin = true
      要求: 分桶字段 = Join字段 ,分桶的个数相等或者成倍数,必须是在map join中
      
    • 方式2:Sort Merge Bucket Join(SMB)

      基于有序的数据Join
      语法:clustered by colName sorted by (colName)
      参数
      	set hive.optimize.bucketmapjoin = true;
      	set hive.auto.convert.sortmerge.join=true;
          set hive.optimize.bucketmapjoin.sortedmerge = true;
          set hive.auto.convert.sortmerge.join.noconditionaltask=true;
          
      要求: 分桶字段 = Join字段 = 排序字段,分桶的个数相等或者成倍数
      

4、数据倾斜优化

  • group by数据倾斜

    ​ 默认情况下,当进行group by的时候,Map阶段同一Key数据分发给一个reduce,当一个key数据过大时就倾斜了。但并不是所有的聚合操作都需要在Reduce端完成,很多聚合操作都可以先在Map端进行部分聚合,最后在Reduce端得出最终结果。

    • 方案一:开启Map端聚合

      • 是否在Hive Group By 查询中使用map端聚合。 hive.map.aggr=true;
        这个设置可以将顶层的部分聚合操作放在Map阶段执行,从而减轻清洗阶段数据传输和Reduce阶段的执行时间,提升总体性能。但是指标不治本。
    • 方案二:实现随机分区

      实现随机分区
      select * from table distribute by rand();
      
    • 方案三:数据倾斜时自动负载均衡

      hive.groupby.skewindata=true;
      
      #开启该参数以后,当前程序会自动通过两个MapReduce来运行
      
      #第一个MapReduce自动进行随机分布到Reducer中,每个Reducer做部分聚合操作,输出结果
      
      #第二个MapReduce将上一步聚合的结果再按照业务(group by key)进行处理,保证相同的分布到一起,最终聚合得到结果
      
  • join数据倾斜

    • 方案一:提前过滤,将大数据变成小数据,实现Map Join

    • 方案二:使用Bucket Join

    • 方案三:使用Skew Join

      #将Map Join和Reduce Join进行合并,如果某个值出现了数据倾斜,就会将产生数据倾斜的数据单独使用Map Join来实现
      
      #其他没有产生数据倾斜的数据由Reduce Join来实现,这样就避免了Reduce Join中产生数据倾斜的问题
      
      #最终将Map Join的结果和Reduce Join的结果进行Union合并
      
      #开启运行过程中skewjoin
      set hive.optimize.skewjoin=true;
      #如果这个key的出现的次数超过这个范围
      set hive.skewjoin.key=100000;
      #在编译时判断是否会产生数据倾斜
      set hive.optimize.skewjoin.compiletime=true;
      set hive.optimize.union.remove=true;
      #如果Hive的底层走的是MapReduce,必须开启这个属性,才能实现不合并
      set mapreduce.input.fileinputformat.input.dir.recursive=true;
      
      

5、 MapReduce引擎并行度调整

  • maptask个数

    ​ 通常情况下,作业会通过input的目录产生一个或者多个map任务。主要的决定因素有:input的文件总个数,input的文件大小,集群设置的文件块大小。

    • 如果是在MapReduce中 maptask个数是通过逻辑切片机制决定的。

    • 但是在hive中,影响的因素很多。比如逻辑切片机制,文件是否压缩、压缩之后是否支持切割。

    • 因此在Hive中,调整MapTask的个数,直接去HDFS调整文件的大小和个数,效率较高。

      • 如果小文件多,就进行小文件的合并 ,合并的大小最好=block size

      • 如果大文件多,就调整block size

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

        ​ 增加map的方法为:

        1、

        computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M调整maxSize最大值。让maxSize最大值低于blocksize就可以增加map的个数。

        2、如果表a只有一个文件,大小为120M,但包含几千万的记录,如果用1个map去完成这个任务,肯定是比较耗时的,这种情况下,我们要考虑将这一个文件合理的拆分成多个,这样就可以用多个map任务去完成。

        set mapreduce.job.reduces =10;       
        create table a_1 as   
        select * from a   
        distribute by rand(1222);   
        

        ​ 这样会将a表的记录,随机的分散到包含10个文件的a_1表中,再用a_1代替上面sql中的a表,则会用10个map任务去完成。每个map任务处理大于12M(几百万记录)的数据,效率肯定会好很多。

  • reducetask个数

    reduce个数并不是越多越好

    1)过多的启动和初始化reduce也会消耗时间和资源;

    2)另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;

    ​ 在设置reduce个数的时候也需要考虑这两个原则:处理大数据量利用合适的reduce数;使单个reduce任务处理数据量大小要合适;

    • 如果在MapReduce中,通过代码可以直接指定 job.setNumReduceTasks(N)

    • 在Hive中,reducetask个数受以下几个条件控制的

      (1)每个 Reduce 处理的数据量默认是 256MB
      hive.exec.reducers.bytes.per.reducer=256000000
      (2)每个任务最大的 reduce 数,默认为 1009
      hive.exec.reducsers.max=1009
      (3)mapreduce.job.reduces
      该值默认为-1,由 hive 自己根据任务情况进行判断。
      
      
      --如果用户用户不设置 hive将会根据数据量或者sql需求自己评估reducetask个数。
      --用户可以自己通过参数设置reducetask的个数
        set mapreduce.job.reduces = N
      --用户设置的不一定生效,如果用户设置的和sql执行逻辑有冲突,比如order by,在sql编译期间,hive又会将reducetask设置为合理的个数。  
      
      Number of reduce tasks determined at compile time: 1
      

6、并行执行机制

​ Hive会将一个查询转化成一个或者多个阶段。这样的阶段可以是MapReduce阶段、抽样阶段、合并阶段、limit阶段。或者Hive执行过程中可能需要的其他阶段。

​ 默认情况下,Hive一次只会执行一个阶段。不过,某个特定的job可能包含众多的阶段,而这些阶段可能并非完全互相依赖的,也就是说有些阶段是可以并行执行的,这样可能使得整个job的执行时间缩短。不过,如果有更多的阶段可以并行执行,那么job可能就越快完成。

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

  • 前提是stage之间没有依赖 并行的弊端是瞬时服务器压力变大。

  • 参数

    set hive.exec.parallel=true; --是否并行执行作业。适用于可以并行运行的 MapReduce 作业,例如在多次插入期间移动文件以插入目标
    set hive.exec.parallel.thread.number=16; --最多可以并行执行多少个作业。默认为8。
    

7、推测执行机制

  • 推测执行机制

    ​ 在分布式集群环境下,因为程序Bug(包括Hadoop本身的bug),负载不均衡或者资源分布不均等原因,会造成同一个作业的多个任务之间运行速度不一致,有些任务的运行速度可能明显慢于其他任务(比如一个作业的某个任务进度只有50%,而其他所有任务已经运行完毕),则这些任务会拖慢作业的整体执行进度。为了避免这种情况发生,Hadoop采用了推测执行(Speculative Execution)机制,它根据一定的法则推测出“拖后腿”的任务,并为这样的任务启动一个备份任务,让该任务与原始任务同时处理同一份数据,并最终选用最先成功运行完成任务的计算结果作为最终结果。

    hadoop中默认两个阶段都开启了推测执行机制。

    • MapReduce中task的一个机制。
    • 功能:
      • 一个job底层可能有多个task执行,如果某些拖后腿的task执行慢,可能会导致最终job失败。
      • 所谓的推测执行机制就是通过算法找出拖后腿的task,为其启动备份的task
      • 两个task同时处理一份数据,谁先处理完,谁的结果作为最终结果。
    • 推测执行机制默认是开启的,但是在企业生产环境中建议关闭。

    ​ 关于调优推测执行机制,还很难给一个具体的建议。如果用户对于运行时的偏差非常敏感的话,那么可以将这些功能关闭掉。如果用户因为输入数据量很大而需要执行长时间的map或者Reduce task的话,那么启动推测执行造成的浪费是非常巨大。

8、严格模式

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

  • 注意。不要和动态分区的严格模式搞混淆。

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

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

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

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

9、笛卡尔积

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

10、使用分区剪裁、列剪裁

  • 在SELECT中,只拿需要的列,如果有,尽量使用分区过滤,少用SELECT * 。

  • 在分区剪裁中,当使用外关联时,如果将副表的过滤条件写在Where后面,那么就会先全表关联,之后再过滤。

    • 先关联再Where:

      • select a.id from tableA left join tableB b on a.id=b.id where b.id<=10
    • 正确的写法是:先Where再关联

      • select a.id from tableA left join tableB b on (b.id <= 10 and a.id=b.id)

11、动态分区调整

​ 关系型数据库中,对分区表Insert数据时候,数据库自动会根据分区字段的值,将数据插入到相应的分区中,Hive中也提供了类似的机制,即动态分区(Dynamic Partition),只不过,使用Hive的动态分区,需要进行相应的配置。

1)开启动态分区参数设置

  • 开启动态分区功能(默认true,开启)

    hive.exec.dynamic.partition=true

  • 设置为非严格模式(动态分区的模式,默认strict,表示必须指定至少一个分区为静态分区,nonstrict模式表示允许所有的分区字段都可以使用动态分区。)

    hive.exec.dynamic.partition.mode=nonstrict

  • (3)在所有执行MR的节点上,最大一共可以创建多少个动态分区。

    hive.exec.max.dynamic.partitions=1000

  • (4)在每个执行MR的节点上,最大可以创建多少个动态分区。该参数需要根据实际的数据来设定。比如:源数据中包含了一年的数据,即day字段有365个值,那么该参数就需要设置成大于365,如果使用默认值100,则会报错。

    hive.exec.max.dynamic.partitions.pernode=100

  • (5)整个MR Job中,最大可以创建多少个HDFS文件。

    hive.exec.max.created.files=100000

  • (6)当有空分区生成时,是否抛出异常。一般不需要设置。

    hive.error.on.empty.partition=false