Spark RDD算子、分区与Shuffle

RDD

Spark的主要抽象是分布式的元素集合(distributed collection of items),称为RDD(Resilient Distributed Dataset,弹性分布式数据集)

它可被分发到集群各个节点上,进行并行操作。RDD可以通过Hadoop InputFormats创建,如 HDFS,或者从其他RDD转化而来。

RDD的操作分为两种,一种是转化(transformation)操作,一种是执行(action)操作,类似于SQL中的聚合函数。

Spark Transformation

Transformation

Spark Action

Action

RDD创建

RDD的创建2种方式:

  1. 从已经存在的集合创建RDD
  2. 从外部存储系统创建RDD(文件系统、hdfs、hbase、MySQL等)

从集合中创建RDD

主要使用parallelize和makeRDD函数

sc.parallelize(List(1,2,3))
sc.makeRDD(List(1,2,3))
sc.parallelize(Array(1 to 10))

从外部存储创建RDD

sc.textFile("hdfs://127.0.0.1:9000/tmp/in.txt")
sc.textFile("file://G:/tmp/in.txt")

分区(partition)-重要

前面的内容算是一个简单的复习,这里我们介绍Spark中另一个非常重要的的知识,partition,可以翻译为分区或者分片,在很多涉及分布式的应用中都有这个概念。

本质就是将大的数据拆分为小的数据集合,比如我们从hdfs目录创建一个RDD,这个目录中的数据可能有几百个G,显然放在一个一起对于处理是不利的。

算法中有一个重要的思想就是分而治之,Spark的partition就利用了这种思想,将一个RDD拆分到不同的partition,这样我们的算法就可以利用多机器、多核并行来处理这些数据了。

下文中sc没有特殊说明,都是:

SparkConf sparkConf = new SparkConf();
sparkConf.setAppName("create JavaSparkContext");
sparkConf.setMaster("local[*]");
JavaSparkContext sc = new JavaSparkContext(sparkConf);

partition方式

HashPartitioner
sc.parallelize(List((1,'a'),(1,'aa'),(2,'b'),(2,'bb'),(3,'c')), 3)
.partitionBy(new HashPartitioner(3))

HashPartitioner确定分区的方式:partition = key.hashCode () % numPartitions

RangePartitioner
sc.parallelize(List((1,'a'),(1,'aa'),(2,'b'),(2,'bb'),(3,'c')), 3)
.partitionBy(new RangePartitioner(3,counts))

RangePartitioner会对key值进行排序,然后将key值被划分成3份key值集合。

自定义

继承org.apache.spark.Partitioner,重写getPartition方法。

sc.parallelize(List((1,'a'),(1,'aa'),(2,'b'),(2,'bb'),(3,'c')), 3).partitionBy(new CustomPartitioner(3))

默认分区

sc.defaultParallelism sc.defaultMinPartitions

sc.defaultParallelism的值可以通过spark-defaults.conf配置文件配置:

spark.default.parallelism=20 

还会受代码设置影响:

sparkConf.setMaster("local[*]");

local[*]和local,分区数就是运行机器cpu的核数,local[n],分区数就是n的值

还和提交时下面两个参数相关: --num-executors --executor-cores

从HDFS读入文件的分区数默认等于HDFS文件的blocks,例如,我们有一个目录的数据量是100G, HDFS默认的块容量大小128MB,那么hdfs的块数为: 100G/128M = 800 那么Spark读取SparkContext.textFile()读取该目录,默认分区数为800。

合理设置分区数

分区数太多意味着任务数太多,每次调度任务也是很耗时的,所以分区数太多会导致总体耗时增多。

分区数太少的话,会导致一些结点没有分配到任务;另一方面,分区数少则每个分区要处理的数据量就会增大,从而对每个结点的内存要求就会提高;还有分区数不合理,会导致数据倾斜问题。

根据任务是IO型还是CPU型,分区数设置为executor-cores * num-executor的2到3倍。

shuffle

首先我们来梳理一下Spark的执行,我们使用Java编写Spark应用,会有一个包含main方法的Class,这个一般被称作是Driver program。

我们通过集群运行Spark应用,会把应用和应用依赖的jar包拷贝到所有的节点,当然为了避免拷贝可以指定一个网络磁盘来存放依赖jar包。

Spark本质是处理数据,当Spark读取数据的时候,就会把这些数据拆分为n个partition,然后封装为不同的Task发给不同的机器去执行。

对于很多操作是有效的,例如map、foreach等,但是对于另一些操作就需要额外的处理,例如reduceByKey,因为相同的key可能分布在不同的partition中,甚至不同的机器节点上,所以必须读取所有partition中的数据到一个节点,然后执行夸partition的计算来获取最终结果,这个过程就称之为shuffle。

能够造成shuffle的操作:

repartition、coalesce、repartitionAndSortWithinPartitions、groupByKey、reduceByKey、sortByKey、aggregateByKey、groupByKey、cogroup、join

实例

bykey

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.Function2;
import org.junit.Before;
import org.junit.Test;
import scala.Tuple2;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class ByKeyAccumulatorTest implements Serializable {

    private transient JavaSparkContext sc;

    @Before
    public void setUp(){
        SparkConf conf = new SparkConf().setAppName("test").setMaster("local");
        sc = new JavaSparkContext(conf);
    }

    @Test
    public void reduceByKey(){
        List<Tuple2<String, Integer>> datas = Arrays.asList(
                new Tuple2<>("A", 10),
                new Tuple2<>("B", 20),
                new Tuple2<>("A", 30),
                new Tuple2<>("A", 60),
                new Tuple2<>("C", 30),
                new Tuple2<>("B", 40)
        );
        JavaRDD<Tuple2<String, Integer>> RDDOne = sc.parallelize(datas);

        JavaPairRDD<String, Integer> pairRDDOne = JavaPairRDD.fromJavaRDD(RDDOne);
        JavaPairRDD<String, Integer> rdd = pairRDDOne.reduceByKey(new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer v1, Integer v2) throws Exception {
                System.out.println("v1:" + v1);
                System.out.println("v2:" + v2);
                return v1 + v2;
            }
        });
        Map<String, Integer> stringIntegerMap = rdd.collectAsMap();
        System.out.println(stringIntegerMap);
        rdd.saveAsTextFile("G:\\tmp\\spark");
    }

    @Test
    public void aggregateByKey() {
        List<Tuple2<String, Integer>> datas = Arrays.asList(
                new Tuple2<>("A", 10),
                new Tuple2<>("B", 20),
                new Tuple2<>("A", 30),
                new Tuple2<>("A", 60),
                new Tuple2<>("C", 30),
                new Tuple2<>("B", 40)
        );
        JavaRDD<Tuple2<String, Integer>> RDDOne = sc.parallelize(datas);

        JavaPairRDD<String, Integer> pairRDDOne = JavaPairRDD.fromJavaRDD(RDDOne);

        JavaPairRDD<String, Integer> rdd = pairRDDOne.aggregateByKey(0, new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer v1, Integer v2) throws Exception {
                return v1 > v2 ? v1 : v2;
            }
        }, new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer v1, Integer v2) throws Exception {
                return v1 + v2;
            }
        });

        Map<String, Integer> stringIntegerMap = rdd.collectAsMap();
        System.out.println(stringIntegerMap);
    }

    @Test
    public void countByKey(){
        List<Tuple2<String, Integer>> datas = Arrays.asList(
                new Tuple2<>("A", 10),
                new Tuple2<>("B", 20),
                new Tuple2<>("A", 30),
                new Tuple2<>("A", 60),
                new Tuple2<>("C", 30),
                new Tuple2<>("B", 40)
        );
        JavaRDD<Tuple2<String, Integer>> RDDOne = sc.parallelize(datas);

        JavaPairRDD<String, Integer> pairRDDOne = JavaPairRDD.fromJavaRDD(RDDOne);
        Map<String, Long> stringLongMap = pairRDDOne.countByKey();
        System.out.println(stringLongMap);
    }
}

foreach\filter

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.VoidFunction;
import org.junit.Before;
import org.junit.Test;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

public class ForeachAccumulatorTest implements Serializable {

    private transient JavaSparkContext sc;

    private transient List<String> datas;

    @Before
    public void setUp(){
        SparkConf conf = new SparkConf().setAppName("test").setMaster("local");
        sc = new JavaSparkContext(conf);
        datas = Arrays.asList("hello", "spark", "hadoop", "瑞雪兆丰年","Java");
    }

    @Test
    public void foreach(){
        JavaRDD<String> dataRDD = sc.parallelize(datas);
        dataRDD.foreach(new VoidFunction<String>() {
            @Override
            public void call(String s) throws Exception {
                System.out.println(s.length());
            }
        });
    }

    @Test
    public void filter(){
        JavaRDD<String> dataRDD = sc.parallelize(datas);
        JavaRDD<String> resultRDD = dataRDD.filter(new Function<String, Boolean>() {
            @Override
            public Boolean call(String v1) throws Exception {
                return v1.contains("h");
            }
        });
        List<String> collect = resultRDD.collect();
        collect.forEach(System.out::println);
    }
}

map

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.DoubleFlatMapFunction;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.PairFlatMapFunction;
import org.junit.Before;
import org.junit.Test;
import scala.Tuple2;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class MapAccumulatorTest implements Serializable {

    private transient JavaSparkContext sc;

    private transient List<String> datas;

    @Before
    public void setUp(){
        SparkConf sparkConf = new SparkConf();
        sparkConf.setAppName("create JavaSparkContext");
        sparkConf.setMaster("local[*]");
        sc = new JavaSparkContext(sparkConf);
        //datas = sc.textFile("file:///G:/tmp/in.txt");
        datas = Arrays.asList("hello", "spark", "hadoop", "瑞雪兆丰年","Java");
    }

    @Test
    public void map(){
        JavaRDD<String> toDealRDD = sc.parallelize(datas, 3);
        JavaRDD<Integer> map = toDealRDD.map(s -> s.length());
        //收集所以数据到驱动节点
        List<Integer> results = map.collect();
        results.forEach(System.out::println);
    }

    @Test
    public void mapPartitions(){
        JavaRDD<String> toDealRDD = sc.parallelize(datas, 3);
        toDealRDD.mapPartitions(new FlatMapFunction<Iterator<String>, Integer>() {
            @Override
            public Iterator<Integer> call(Iterator<String> stringIterator) throws Exception {
                LinkedList<Integer> lengths = new LinkedList<>();
                //处理外部资源等公共耗时操作
                while (stringIterator.hasNext()) {
                    String content = stringIterator.next();
                    lengths.add(content.length());
                }
                //返回的是iterator
                return lengths.iterator();
            }
        });
    }

    @Test
    public void mapPartitionsToParis(){
        JavaRDD<String> toDealRDD = sc.parallelize(datas, 3);
        toDealRDD.mapPartitionsToPair(new PairFlatMapFunction<Iterator<String>, String, Integer>() {
            @Override
            public Iterator<Tuple2<String, Integer>> call(Iterator<String> stringIterator) throws Exception {
                List<Tuple2<String,Integer>> result = new LinkedList<>();
                //处理外部资源等公共耗时操作
                while (stringIterator.hasNext()) {
                    String content = stringIterator.next();
                    Tuple2<String, Integer> tuple = new Tuple2<>(content, content.length());
                    result.add(tuple);
                }
                return result.iterator();
            }
        });
    }

    @Test
    public void mapPartitionsToDouble(){
        JavaRDD<String> toDealRDD = sc.parallelize(datas, 3);
        toDealRDD.mapPartitionsToDouble(new DoubleFlatMapFunction<Iterator<String>>() {
            @Override
            public Iterator<Double> call(Iterator<String> stringIterator) throws Exception {
                LinkedList<Double> result = new LinkedList<>();
                while (stringIterator.hasNext()) {
                    String content = stringIterator.next();
                    result.add(Double.valueOf(content.length()));
                }
                return result.iterator();
            }
        });
    }
    
    @Test
    public void flatMap(){
        List<String> listOne = Arrays.asList("hello", "spark", "hadoop", "瑞雪兆丰年", "Java");
        List<String> listTwo = Arrays.asList("语言", "C", "C++", "Java", "Python");
        List<List<String>> datas = Arrays.asList(listOne, listTwo);
//        JavaRDD<List<String>> toDealRDD = sc.parallelize(datas, 3);
        JavaRDD<List<String>> toDealRDD = sc.parallelize(datas);
        JavaRDD<Integer> mapResult = toDealRDD.map(new Function<List<String>, Integer>() {
            @Override
            public Integer call(List<String> content) throws Exception {
                return content.size();
            }
        });

        List<Integer> mapList = mapResult.collect();
        mapList.forEach(System.out::println);
        System.out.println("------------------");
        JavaRDD<Integer> flatMapRDD = toDealRDD.flatMap(new FlatMapFunction<List<String>, Integer>() {
            @Override
            public Iterator<Integer> call(List<String> strings) throws Exception {
                LinkedList<Integer> result = new LinkedList<>();
                strings.forEach(content -> result.add(content.length()));
                return result.iterator();
            }
        });
        List<Integer> flatMapResult = flatMapRDD.collect();
        flatMapResult.forEach(System.out::println);
    }

}

save

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.mapred.TextOutputFormat;
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.junit.Before;
import org.junit.Test;
import scala.Tuple2;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class SaveAccumulatorTest implements Serializable {

    private transient JavaSparkContext sc;

    private transient List<Tuple2<String, Integer>> datas;

    @Before
    public void setUp(){
        SparkConf conf = new SparkConf().setAppName("test").setMaster("local");
        sc = new JavaSparkContext(conf);
        datas = new ArrayList<>();
        datas.add(new Tuple2<>("A", 10));
        datas.add(new Tuple2<>("B", 1));
        datas.add(new Tuple2<>("A", 6));
        datas.add(new Tuple2<>("C", 5));
        datas.add(new Tuple2<>("B", 3));
    }

    @Test
    public void saveAsTextFile(){
        JavaRDD<Tuple2<String, Integer>> dataRDD = sc.parallelize(datas);
        String path = "G:\\tmp\\spark\\saveAsTextFile\\" + System.currentTimeMillis();
        dataRDD.saveAsTextFile(path);
    }

    @Test
    public void saveAsSequenceFile(){
        List<Tuple2<String,Integer>> datas = new ArrayList<>();
        datas.add(new Tuple2<>("A", 10));
        datas.add(new Tuple2<>("B", 1));
        datas.add(new Tuple2<>("A", 6));
        datas.add(new Tuple2<>("C", 5));
        datas.add(new Tuple2<>("B", 3));
        JavaPairRDD<String, Integer> dataRDD = sc.parallelizePairs(datas);
        String path = "G:\\tmp\\spark\\saveAsHadoopFile\\" + System.currentTimeMillis();
        dataRDD.saveAsHadoopFile(path, String.class, IntWritable.class, TextOutputFormat.class);
    }

    @Test
    public void saveAsObjectFile(){
        JavaRDD<Tuple2<String, Integer>> dataRDD = sc.parallelize(datas);
        String path = "G:\\tmp\\spark\\saveAsObjectFile\\" + System.currentTimeMillis();
        dataRDD.saveAsObjectFile(path);
    }
}

map、filter、foreach

map、filter、foreach都会遍历元素,但是他们还是有一些差别的。

map是把dataset中的元素映射为另一个元素,甚至是另一种类型。

filter接收的函数返回值是boolean类型,主要是过滤元素用,减少dataset中的数据量。

foreach没有返回值。

coalesce、repartition、repartitionAndSortWithinPartitions

coalesce、repartition、repartitionAndSortWithinPartitions这三个算子都会重新分区,但是还是有明显的区别。

coalesce主要是用于减少分区数量,在filter算子之后数据量大量减少之后,减少分区数量可以提高效率。

repartition可以用于增加或者减少分区数量,总是会shuffle所有的数据

repartitionAndSortWithinPartitions,如果分区之后要排序,那么使用repartitionAndSortWithinPartitions,而不是先使用repartition,然后使用sortBy。

有partition的算子与无partition算子的区别

  1. 无partition的算子,是对RDD的每一个元素都调用一次处理函数
  2. 有partition的算子,是对RDD中的每一个partition调用一次处理函数

有什么区别呢?

假如处理函数内部存在数据库链接、文件等的创建及关闭,无partition的算子会导致处理每个元素时创建一次链接或者句柄,导致性能问题。

所以在处理函数中需要访问外部资源的时候,我们应该尽量选择有partition的算子。这样函数传入的参数是整个分区数据的迭代器,可以一次处理一个partition,可以减少外部资源处理时间。

Spark RDD

转载于:https://my.oschina.net/u/2474629/blog/3069328

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值