1、map reduce过程

回顾一下经典的统计词频WordCount流程,

  • step1 map过程

使用三个Map任务并行读取三行文件中的内容,对读取的单词进行map操作,每个单词都以<key, value>形式生成

spark 提交任务 spark 提交任务动态调整mapreduce参数_spark 提交任务

  • step2 reduce过程

spark 提交任务 spark 提交任务动态调整mapreduce参数_数据_02

可以看出Reduce操作是对Map的结果进行排序、合并等操作最后得出词频。

Reduce-Join和Map-Join

2.1 Reduce-Join 的原理

Reduce Join 包含Map、Shuffle、Reduce阶段,而 join 会在 reduce 阶段完成,故称为 Reduce Join。

  • Map 阶段:读取源表的数据,Map 输出时候以 Join on 条件中的列为 key ,如果 Join 有多个关联键,则以这些关联键的组合作为 key ;Map 输出的 value 为 join 之后所关心的列(即 select 或者 where 中需要用到的);同时在 value 中还会包含表的 Tag 信息,用于标明此 value 对应哪个表;
  • shuffle 阶段:根据 key 的值进行 hash ,并将 key/value 按照 hash 值推送至不同的 reduce 中,这样确保两个表中相同的 key 位于同一个 reduce 中。
  • reduce 阶段:通过 Tag 来判断每一个 value 是来自 table1 还是 table2 ,在内部分成2组,做集合笛卡尔积。

缺点:

  • shuffle 的网络传输和排序性能很低,reduce 端对2个集合做乘积计算,很耗内存,容易导致 OOM。
  • 在进行 shuffle 的时候,必须将各个节点上相同的 key 拉取到某个节点上的一个 task 来进行处理,此时如果某个 key 对应的数据量特别大的话,就会发生数据倾斜

spark 提交任务 spark 提交任务动态调整mapreduce参数_big data_03

2.2 Map-Join 的原理

Map-Join 适用于有一份数据较小的连接情况。做法是把该小份数据直接全部加载到内存当中,按 Join关键字 建立索引。然后大份数据就作为 MapTask 的输入,对 map() 方法的每次输入都去内存当中直接进行匹配连接。然后把连接结果按 key 输出。这种方法要使用 Spark 中的 广播(Broadcast)功能 把小份数据分放到各个计算节点,每个 maptask 执行任务的节点都需要加载该数据到内存。由于 Join 是在 Map 阶段进行的,故称为 Map-Join 。

缺点:

  • 需要将小表建立索引,常用方式是建立Map表,在 Spark 中,可以通过 rdd.collectAsMap() 算子实现。但是 collectAsMap() 在 key 重复时,后面的 value 会覆盖前面的,所以对于存在重复 key 的表,需做其他处理。另外,在 collectAsMap() 过程中,由于需要在 Driver 节点进行 collect ,所以需要保证 Driver 节点内存充足,可在 spark-commit 提交执行任务时,通过设置 driver-memory 调节 Driver 节点大小。
  • Map-Join 同时需要将经过 Map 广播到不同的 executor ,供 Task 获取数据进行连接并输出结果。所以 executor 也需要保证内存充足,可在 spark-commit 提交执行任务时,通过设置 --executor-cores 调节 Driver 节点大小。

spark 提交任务 spark 提交任务动态调整mapreduce参数_java_04


2、GC

首先确定是map阶段gc,还是reduce阶段gc。

reduce阶段gc,则

set mapreduce.reduce.cpu.vcores=当前值*2;
set mapreduce.reduce.memory.mb=当前值*2;
set mapreduce.reduce.java.opts=当前值*2;

map阶段gc,则

mapreduce.map.cpu.vcores=当前值*2;
mapreduce.map.memory.mb=当前值*2;
mapreduce.map.java.opts=当前值*2;

参数含义如下,

  • set mapreduce.map.cpu.vcores = 4;  -- 每个Map Task需要的虚拟CPU个数
  • set mapreduce.reduce.cpu.vcores = 8;  -- 每个Reduce Task需要的虚拟CPU个数
  • set mapreduce.map.memory.mb = 8192; -- 每个Map Task需要的内存量
  • set mapreduce.reduce.memory.mb = 10500; -- 每个Reduce Task需要的内存量
  • set mapreduce.map.java.opts = - Xmx9192m;  --  设置Map任务JVM的堆空间大小,默认-Xmx1024m
  • set mapreduce.reduce.java.opts = - Xmx10000m;   -- 设置reduce任务JVM的堆空间大小,默认-Xmx1024m
  • set spark.sql.hive.mergeFiles=true;   合并小文件
3、数据倾斜

数据倾斜是数据处理作业中的一个非常常见也是非常难以处理的问题。正常情况下,数据通常都会出现数据倾斜的问题,只是轻重不同而已。数据倾斜的症状是大量数据集中到一个或者几个任务里,导致这几个任务会拖慢整个作业的执行速度,严重的甚至会导致整个作业执行失败。通常某个任务处理了绝大多数数据,其他任务执行完成后需要等待此任务执行完成后,作业才算完成。对于这种情况,可以采取以下几种办法处理:

1、用insert into替换union all。

2、列裁剪:where语句先过滤有效信息。(这个一般都已经解决)

3、加大并发参数,调整 mapreduce.job.reduces,例如,(该值只对hive任务有效,spark任务无效)

set mapreduce.job.reduces=15; 指定reducer个数,设置每个job的Reduce个数
  • 提高作业的并行度:这种方式仍然不能从根本上消除数据倾斜,只是尽可能地将数据分散到多个任务中去,这种方案只能提升作业的执行速度,但是不能解决数据倾斜的问题。

这里需要注意,

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

4、mapjoin调整小表的内存。

在Hive中,common join是很慢的,如果我们是一张大表关联多张小表,可以使用mapjoin加快速度。

mapjoin主要有以下参数:

  • hive.auto.convert.join : 是否自动转换为mapjoin
  • hive.mapjoin.smalltable.filesize : 小表的最大文件大小,默认为25000000,即25M
  • hive.auto.convert.join.noconditionaltask : 是否将多个mapjoin合并为一个
  • hive.auto.convert.join.noconditionaltask.size : 多个mapjoin转换为1个时,所有小表的文件大小总和的最大值。

具体操作如下,

set hive.auto.convert.join=true;
set hive.mapjoin.smalltable.filesize=300000000;
set hive.auto.convert.join.noconditionaltask=true;
set hive.auto.convert.join.noconditionaltask.size=300000000;

红色的两个值一般默认为???。

  • 广播变量:可以将小表进行广播,避免了Shuffle的过程,这样就使计算相对均匀地分布在每个Map任务中,但是对于数据倾斜严重的情况,还是会出现作业执行缓慢的问题。

-- /*+mapjoin(t1)*/ 通过将小表加入内存。缓解数据倾斜的问题。

DROP TABLE IF EXISTS table.table_name;
CREATE TABLE IF NOT EXISTS table.table_name AS

SELECT
	/*+mapjoin(t1)*/
	t2.*
FROM
	(
		--3258,9189,约3200w数据
		SELECT sku FROM table.small_table
	)
	t1
JOIN
	(
		--1185,1598,0413, 约1100亿数据
		SELECT * FROM table.big_table
	)
	t2
ON
	t1.sku= t2.sku

5、数据打散

将不均匀的数据进行单独处理:在连接操作的时候,可以先从大表中将集中分布的连接键找出来,与小表单独处理,再与剩余数据连接的结果做合并。处理方法为如果大表的数据存在数据倾斜,而小表不存在这种情况,那么可以将大表中存在倾斜的数据提取出来,并将小表中对应的数据提取出来,这时可以将小表中的数据扩充n倍,而大表中的每条数据则打上一个n以内的随机数作为新键,小表中的数据则根据扩容批次作为新键。

spark 提交任务 spark 提交任务动态调整mapreduce参数_spark_05

 这种方式可以将倾斜的数据打散,从而避免数据倾斜。

6、多段分组

对于那种分组统计的任务,可以通过两阶段聚合的方案来解决数据倾斜的问题,首先将数据打上一个随机的键值,并根据键的散列值进行分发,将数据均匀地分散到多个任务中去,然后在每个任务中按照真实的键值做局部聚合,最后再按照真实的键值分发一次,得到最后的结果。这样,最后一次分发的数据已经是聚合过后的数据,就不会出现数据倾斜的情况。这种方法虽然能够解决数据倾斜的问题但只适合聚合计算的场景。

spark 提交任务 spark 提交任务动态调整mapreduce参数_数据_06





如果作业执行速度缓慢可以通过autoBroadcastJoinThreshold,将值调大。

set spark.sql.autoBroadcastJoinThreshold=535544320;

1 spark-sql的broadcast join需要先判断小表的size是否小于spark.sql.autoBroadcastJoinThreshold设定的值(byte);(所以调大这个值就可以启动broadcast join。存在真实值大于阈值,导致broadcast join不开启。)

2 在spark中size的估算表示为statistics类,仅对hive relation 有效,因为其最初是从hive元数据库中读取所需的统计值的.因此对于jdbc relation等来说,无法触发broadcast join。



参考:

调节 Driver 节点大小。hive-set设置总结 | 卖姑娘的小火柴