聊聊Spark的分区、并行度 —— 前奏篇

通过之前的文章【Spark RDD详解】,大家应该了解到Spark会通过DAG将一个Spark job中用到的所有RDD划分为不同的stage,每个stage内部都会有很多子任务处理数据,而每个stage的任务数是决定性能优劣的关键指标。      

首先来了解一下Spark中分区的概念,其实就是将要处理的数据集根据一定的规则划分为不同的子集,每个子集都算做一个单独的分区,由集群中不同的机器或者是同一台机器不同的core进行分区并行处理。
      

Spark对接不同的数据源,在第一次得到的分区数是不一样的,但都有一个共性:对于map类算子或者通过map算子产生的彼此之间具有窄依赖关系的RDD的分区数,子RDD分区与父RDD分区是一致的。而对于通过shuffle差生的子RDD则由分区器决定,当然默认分区器是HashPartitioner,我们完全可以根据实际业务场景进行自定义分区器,只需继承Parttioner组件,主要重写几个方法即可


以加载hdfs文件为例,Spark在读取hdfs文件还没有调用其他算子进行业务处理前,得到的RDD分区数由什么决定呢?关键在于文件是否可切分!

对于可切分文件,如text文件,那么通过加载文件得到的RDD的分区数默认与该文件的block数量保持一致;

对于不可切分文件,它只有一个block块,那么得到的RDD的分区数默认也就是1。

当然,我们可以通过调用一些算子对RDD进行重分区,如repartition。

这里必须要强调一点,很多小伙伴不理解,RDD既然不存储数据,那么加载过来的文件都跑哪里去了呢?这里先给大家提个引子——blockmanager,Spark自己实现的存储管理器。RDD的存储概念其实block,至于block的大小可以根据不同的数据源进行调整,blockmanager的数据存储、传输都是以block进行的。至于block内部传输的时候,它的大小也是可以通过参数控制的,比如广播变量、shuffle传输时block的大小等。

下面再通过大家熟知的一个参数spark.default.parallelism为引,聊一聊Spark并行度都由哪些因素决定?

 

上图是spark官网关于spark.default.parallelism参数说明:

  1. 对于reduceByKey和join这些分布式shuffle算子操作,取决于它的父RDD中分区数的最大值

  2. 对于没有父RDD的的算子,比如parallelize,依赖于集群管理器:

    1. 本地模式:取决于本地机器的核数

    2. 如果集群管理器是Mesos,则为8

    3. 其他的:对比所有executor上总核数与2比较,哪个大是哪个

当然上面这些都是默认值,如果我们自己设置了分区数,情况就会有所变化,直接看源码【查看org.apache.spark.Partitioner源码defaultPartitioner方法】

你会发现,如果你使用reducebykey、groupByKey等这些带shuffle的算子,建议不要通过上述方法让程序内部去推测。完全可以通过传入一个确定的分区数或者自己实现一个分区器来做处理。当然这个确定的分区数也不是贸贸然设定的,需要结合你的业务场景根据实际情况来确定多少合适。比如shuffle时流经的数据量,这个就要结合分区数和shuffle总数据量来做适当调整,处理不好的结果极有可能导致数据倾斜等问题…

 

笔者再次建议,学习Spark一定要多看Spark官网//spark.apache.org/,并且多看源码


 

 

关注微信公众号:大数据学习与分享,获取更对技术干货