Spark编程指南之一:transformation和action等RDD基本操作

基本概念

Spark应用依靠driver程序在集群上运行,RDD(Resilient distributed dataset) 是Spark的核心数据结构抽象,是一个在集群上分区从而可以并行操作的元素集合。另一个抽象是共享变量(shared variables),当Spark在不同节点上并行运行时,会复制每个变量给到每个任务。共享变量包括广播变量(broadcast variables)和累加器(accumulators)。

开发环境

本文使用Java1.8和Spark 2.3.1 为例,使用IDEA开发程序。
使用maven管理Java依赖包,在maven中添加

   <dependencies>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.11</artifactId>
        <version>2.3.1</version>
    </dependency>
    </dependencies>

如果要运行在HDFS上,还要在dependencies>里添加hadoop-client依赖

   <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>2.7.3</version>
    </dependency>

Java程序需要引入SparkAPI,也可在写程序时根据编辑器的提示自动引入。
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.SparkConf;

编程实战

初始化SparkContext

SparkConf conf = new SparkConf().setAppName(appName).setMaster(master);
JavaSparkContext sc = new JavaSparkContext(conf);

RDD的生成

RDD的生成有两种方式,一种是从另一个RDD转换而来,另一种是从外部系统数据生成。(There are two ways to create RDDs: parallelizing an existing collection in your driver program, or referencing a dataset in an external storage system, such as a shared filesystem, HDFS, HBase, or any data source offering a Hadoop InputFormat.)
第一种:生成a parallelized collection如下,一旦生成,distData就可以进行并行运算。Spark会对每个分区产生一个task,通常Spark会根据集群自行设置分区数量,也可以在生成时多传一个参数,例如通过parallelize(data,10)指定分区数为10。

List<Integer> data = Arrays.asList(1, 2, 3, 4, 5);
JavaRDD<Integer> distData = sc.parallelize(data);

第二种:从外部系统生成:
Spark supports text files, SequenceFiles, and any other Hadoop InputFormat.

JavaRDD<String> distFile = sc.textFile("data.txt");

RDD基本操作

RDD支持两种操作:转换操作(Transformations)和行动操作(Actions)。转换操作是从一个RDD转换为另一个RDD,而行动操作返回的是值(return a value to the driver program)。

Key-Value Pairs

要对RDD进行操作,首先要了解有些RDD操作只能对key-value形式的RDD进行。在Java中的key-value pairs是通过引入Scala.Tuple2类库实现的。key-value pairs可以从JavaRDD生成:

JavaRDD<String> lines = sc.textFile("data.txt");
JavaPairRDD<String, Integer> pairs = lines.mapToPair(s -> new Tuple2(s, 1));
JavaPairRDD<String, Integer> counts = pairs.reduceByKey((a, b) -> a + b);

通过mapToPair操作将普通的JavaRDD转为key-value形式的JavaPairRDD,其中key为String格式,value为Integer格式。通过new Tuple2(s, 1)函数,将原来RDD中的每个String,映射为String和整数1的键值对,这样就可以对String进行计数。通过
reduceByKey((a, b) -> a + b) 对相同键的1值进行累加,即为每个String的数量,此操作返回的JavaPairRDD的键值对的数量通常会小于操作前的pairs的键值对的数量,因为相同键值的数据被合并统计了。

Transformations

以下操作以读取Spark安装包里的README.md文件为例。
JavaRDD textFileRDD = sc.textFile(“README.md”);

filter

传入函数,保留函数返回值为true的数据,返回新的RDD。

JavaRDD<String> filterRDD = textFileRDD.filter(new Function<String, Boolean>() {
            @Override
            public Boolean call(String s) throws Exception {
                return s.contains("spark"); //返回带有"spark"内容的行。
            }
        });

此操作是对每个分区内的数据进行过滤,使用后需要重分区防止分区内为空。

map

传入函数,每条数据被函数处理后,返回新的RDD。

JavaRDD<Integer> mapRDD = textFileRDD.map(new Function<String, Integer>() {
            @Override
            public Integer call(String s) throws Exception {
                return s.length();
            }
        });

flatmap

与map不同的是,每条数据处理后,可以被map为0条或多条数据, 所以返回的是一个Seq(So func should return a Seq rather than a single item)。
最常见的例子就是单词计数:原始的RDD是每行数据,用flatmap将每行的数据用空格分割称为单词。这样一行20个单词的数据,将返回20个单词,即为1对多的map。

JavaRDD<String> words = textFileRDD.flatMap(new FlatMapFunction<String, String>() {

            public Iterator<String> call(String s) throws Exception {
                return Arrays.asList(s.split(" ")).iterator();
            }
        });

mapPartitions

与map不同的是在每个分区的迭代器上进行操作,函数应为Iterator<T> => Iterator<U> 形式。此处的Iterator是同一分区内的所有数据组成的Iterator。之前的map是对每条数据调用一次函数,这样当函数内部存在数据库连接、文件打开或关闭时,要对每条数据创建一次连接或句柄,会导致性能的下降。

JavaRDD<Integer> mapPartRDD = textFileRDD.mapPartitions(new FlatMapFunction<Iterator<String>, Integer>() {
            @Override
            public Iterator<Integer> call(Iterator<String> stringIterator) throws Exception {
                List<Integer> l = new LinkedList<>();
                while(stringIterator.hasNext()){
                    l.add(stringIterator.next().length());
                }
                return l.iterator();
            }
        });

注意:同一分区内的数据量过大会产生OOM

union

返回两个RDD的并集:
RDD3 = RDD1.union(RDD2);

intersection

返回两个RDD的交集:
RDD3 = RDD1.intersection(RDD2);

reduceByKey

只能对PairRDD进行操作,因为普通RDD没有key, 要通过mapToPair转换成PairRDD才能调用reduceByKey。
如对单词进行计数,先将普通RDD转为PairRDD,再调用reduceByKey,对相同key的数据进行累加,返回新的RDD。

JavaPairRDD<String, Integer> PairRDD = words.mapToPair(new PairFunction<String, String, Integer>() {
            public Tuple2<String, Integer> call(String s) throws Exception {
                return new Tuple2<String, Integer>(s, 1);
            }
        });
JavaPairRDD<String, Integer> countPairRDD = PairRDD.reduceByKey(new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer integer, Integer integer2) throws Exception {
                return integer + integer2;
            }
        });

Actions

前面的transformation操作Spark不会真正执行计算任务,直到actions操作才会开始计算。

count

返回RDD中的元素数量:

long count = textFileRDD.count();
System.out.println("count:"+count);

take

取出RDD中指定数量的值,但并不有序:

List<String> take = textFileRDD.take(5);

top

取出前五个:

List<Integer> toplist = mapRDD.top(5);
System.out.println(toplist);

countByValue

作用在普通RDD上可以计数,对每条数据出现的次数进行计数,可以做单词统计:

Map<String, Long> stringLongMap = words.countByValue();
        System.out.println(stringLongMap);

collect

将整个数据集返回给driver端,通常做过filter或其他处理后再使用,这样数据集可以足够小,防止内存溢出。

List<String> collect = words.collect();

foreach

传入函数,作用于每条数据。如果要对RDD中的每条数据进行操作,但不希望返回给driver端时使用。

foreachPartition

参考链接

http://spark.apache.org/docs/2.3.1/rdd-programming-guide.html#transformations

猜你喜欢

转载自blog.csdn.net/weixin_42628594/article/details/84933676