spark 分区 java_如何通过分区来提高spark的性能(java代码)

RDD是Spark上最重要的概念。可以将RDD理解为是包含很多对象的集合,这些对象实质上是存储在不同的分区上的。当在RDD上执行计算时,这些分区能够并行执行。

通过修改分区,我们能够有效的提高spark job的性能。下面通过一个简单的例子来说明下。

举例:找素数

假如我们想找出200万以内的所有素数。简单的方法是,我们可以先找出所有的非素数。然后对全部数字,执行清除所有这些非素数。剩下的就是素数了。

具体方法如下:

1)对于2-200万内的所有数,找到它们每个的小于200万的所有乘数;

2)200万内所有数清除第1步获得的全部数字,得到的就是素数。

代码如下:

import java.util.LinkedList;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaPairRDD;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.FlatMapFunction;

import org.apache.spark.api.java.function.Function;

import org.apache.tools.ant.taskdefs.Java;

import scala.Tuple2;

import scala.collection.generic.BitOperations.Int;

//spark-submit --queue datateam_spark --master yarn --class com.ssj.test.Test recommender-0.0.1-SNAPSHOT-jar-with-dependencies.jar

public class Test {

public static void main(String args[]) throws Exception{

SparkConf conf = new SparkConf().setAppName("test");

JavaSparkContext jsc = new JavaSparkContext(conf);

LinkedList nums = new LinkedList<>();

for(int i = 2;i<2000000 ;i++){

nums.add(i);

}

JavaRDD>> rdd1 = jsc.parallelize(nums,8).map(new Function>>() {

@Override

public Tuple2> call(Integer x){

LinkedList result = new LinkedList<>();

for(int i = 2;i<2000000/x;i++){

result.add(i);

}

return new Tuple2>(x, result);

}

});

JavaRDD rdd2 = rdd1.flatMap(new FlatMapFunction>, Integer>() {

@Override

public Iterable call(Tuple2> kv){

LinkedList result = new LinkedList<>();

for(Integer val:kv._2){

result.add(kv._1*val);

}

return result;

}

});

JavaRDD result = jsc.parallelize(nums,8).subtract(rdd2);

System.out.println(result.glom().collect());

}

}

(原博客中采用的是Scala实现)

答案看上去似乎合情合理。但让我们来看看程序的性能。进到Spark UI界面。在Stages阶段我们可以看到改程序执行分为3个stage。下面是DAG图:

0818b9ca8b590ca3270a3433284dd417.png

当job需要在分区间通信(也就是“shuffle”)的时候,就会开始一个新的stage。stage对于每个分区都会有对应的一个task,这些task会将一个RDD的一个分区转移到另一个RDD的一个分区。让我们先来看下Stage 0的任务:

0818b9ca8b590ca3270a3433284dd417.png

“Duration”和“Shuffle Write Size / Records”是我们感兴趣的列。。sc.parallelize(2 to n, 8)操作创建了1999999,这些记录被平均分配到了8个分区上。每个任务执行了相同的时间。这个看上去很好。

Stage 1是最有意思的。因为运行了map和flatMap transformation。让我们看下执行情况:

0818b9ca8b590ca3270a3433284dd417.png

看上去并不好。为什么? 首先看Duration.最长的分区执行了14s。而其他的分区执行的最久的也只有1s。也就是说有7个空闲的分区要等待13s。资源利用率太低了。

然后我们再看Shuffle write size/records 。可以看到,任务分配非常不均。第一个分区大概分配了93%的任务,而有4个分区点任务都没有分配。

为什么会发生这样的现象?

When we ran sc.parallelize(2 to n, 8), Spark used a partitioning scheme that nicely divided the data into 8 even groups.

执行sc.parallelize(2 to n,8)时,spark会使用一种分区机制很好的将数据分到8个平均组中。很可能使用的是range partitioner——2-250000数据分到第一个分区,250001-500000分到第二个分区,以此类推。但是,当我们执行map,将其转换成键值对的时候(见rdd1),每个键对应的值得大小就差别很大了。比如,对于大于1000000的那一半数,它们的值是为空的。此外,值数量最大的是2,为2-1000000的全部数据。其中前者是造成后面4个分区没有数据的原因。而后者造成了第一个分区的执行时间为最长的。

怎么处理才能防止这样的现象出现?

在map执行完后,我们再对RDD执行重新分区:调用.repartition(numPartitions),就可以平均将数据分到各分区:

在rdd1后面增加.repartition(8)即可。

修改后的DAG图如下:

0818b9ca8b590ca3270a3433284dd417.png

比之前更加复杂的原因是因为分区增加了其他的shuffle。

我们看第二个stage:

0818b9ca8b590ca3270a3433284dd417.png

这比旧的第一个stage显然要好的多.虽然数据量还是一样,但时间控制在5s,每个分区处理的数据差不多,资源得到了充分的利用.

当然,实际求素数肯定能有更加有效的算法。举这个例子只是为了说明spark分区的一个方面的作用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值