spark算子类型

在 Apache Spark 中,算子(Operator) 是对分布式数据集(RDD)进行操作的函数或方法。根据功能和特性,Spark 的算子主要分为三大类:转换算子(Transformation)、行动算子(Action) 和 持久化算子(Persistence)。Transformation算子用于从一个 RDD 转换生成另一个 RDD,具有惰性特性;Action算子用于触发实际计算,将结果返回给Driver或写入外部存储;持久化算子用于将 RDD 的数据缓存或持久化,以提高重复计算的效率。

1. Transformation-转换算子

Transformation算子对现有的RDD进行操作,生成新的 RDD,这些操作是惰性的(延迟执行),只有在遇到Action算子时才会被执行。下面对常用的Transformation类算子进行介绍。

1.1 map

将一个RDD中的每个数据项,通过map中的函数映射变为一个新的元素,特点:输入一条,输出一条。

1.2 flatMap

先map后flat。与map类似,每个输入项可以映射为0到多个输出项。

1.3 reduceByKey

有map端预聚合,会产生shuffle,对K,V格式RDD按照key进行分组

只能作用在K,V格式的RDD上,使用指定的函数对相同Key的Value进行聚合,返回K,V格式的RDD。

1.4 groupBy

会产生shuffle,按照用户指定的逻辑对数据进行分组

groupBy算子可以对RDD中数据按照指定的规则进行分组。

如下示例将RDD数据按照奇数和偶数进行分组。GroupBy返回Tuple<K,Iterable<V>>,K表示分组的key,V表示该组中的数据。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("CustomPartitionerTest");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

JavaPairRDD<Integer, Iterable<Integer>> result = rdd.groupBy(new Function<Integer, Integer>() {
    @Override
    public Integer call(Integer one) throws Exception {
        return one % 2;
    }
});

result.foreach(new VoidFunction<Tuple2<Integer, Iterable<Integer>>>() {
    @Override
    public void call(Tuple2<Integer, Iterable<Integer>> integerIterableTuple2) throws Exception {
        System.out.println(integerIterableTuple2);
    }
});

sc.stop()

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("filter")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
val result: RDD[(Int, Iterable[Int])] = rdd.groupBy(one => one % 2)
result.foreach(println)
sc.stop()

1.5 groupByKey

会产生shuffle,作用到K,V格式RDD,按照K对数据进行分组。在聚合的场景中建议使用reduceByKey代替GroupByKey

作用在K,V格式的RDD上,根据Key进行分组,返回(K,Iterable <V>)。对于需要对相同key进行聚合的场景使用reduceByKey更高效,因为reduceByKey会在各个分区中预先进行本地聚合,减少数据传输数量。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("GroupByKeyTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaPairRDD<String, Integer> pairRDD = sc.parallelizePairs(Arrays.asList(
        new Tuple2<>("a", 1),
        new Tuple2<>("b", 2),
        new Tuple2<>("c", 3),
        new Tuple2<>("a", 4),
        new Tuple2<>("b", 5),
        new Tuple2<>("c", 6),
        new Tuple2<>("a", 7),
        new Tuple2<>("b", 8),
        new Tuple2<>("c", 9)

));

//groupByKey:将数据源中的数据,按照相同的key对value进行分组,形成一个新的可迭代的value
JavaPairRDD<String, Iterable<Integer>> result = pairRDD.groupByKey();
result.foreach(new VoidFunction<Tuple2<String, Iterable<Integer>>>() {
    @Override
    public void call(Tuple2<String, Iterable<Integer>> tp) throws Exception {
        String key = tp._1;
        Iterable<Integer> values = tp._2;
        int sum = 0;
        for (Integer value : values) {
            sum += value;
        }
        System.out.println(key+":"+sum);
    }
});

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("GroupByKeyTest")
val sc = new SparkContext(conf)

//groupByKey: 将RDD中的元素按照key进行分组
val result: RDD[(String, Iterable[Int])] = sc.parallelize(List(("a", 1), ("b", 2), ("c", 3), ("d", 4), ("a", 5), ("b", 6), ("c", 7), ("d", 8)))
  .groupByKey()

result.foreach(tp=>{
  val key: String = tp._1
  val values: Iterable[Int] = tp._2.toList
  var sum = 0
  for (value <- values) {
    sum += value
  }
  println(s"key:${key},sum:${sum}")
})

sc.stop()

1.6 filter

过滤符合条件的记录,根据传入的逻辑返回true的数据保留,返回false的数据过滤掉。

案例:过滤数据中长度大于5的字符串。

Java代码:

复制代码
SparkConf conf = new SparkConf();
conf.setMaster("local");
conf.setAppName("filter");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> rdd1 = sc.parallelize(Arrays.asList("zhangsan", "lisi", "wangwu", "maliu"));
// filter:过滤长度大于5的字符串
JavaRDD<String> rdd2 = rdd1.filter(new Function<String, Boolean>() {
    @Override
    public Boolean call(String s) throws Exception {
        return s.length() > 5;
    }
});
rdd2.foreach(s -> System.out.println(s));
sc.stop();

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("filter")
val sc = new SparkContext(conf)

//filter:过滤长度大于5的字符串
val rdd: RDD[String] = sc.parallelize(Array("zhangsan", "lisi", "wangwu", "maliu"))
rdd.filter(str=>{str.length > 5})
  .foreach(println)
sc.stop()

1.7 sample

随机抽样算子,根据传进去的小数按比例进行有放回或者无放回的抽样,常用于数据预览、测试或处理大规模数据时的抽样分析。sample算子函数签名如下:

复制代码
def sample(
    withReplacement: Boolean,
    fraction: Double,
    seed: Long = Utils.random.nextLong
): RDD[T]
  • withReplacement:布尔值,表示抽样时是否采用有放回的方式。true 表示有放回抽样,即同一个元素可能被多次抽取;false 表示无放回抽样,每个元素最多被抽取一次。
  • fraction:表示抽样比例。对于无放回抽样,fraction 是期望抽取的样本占原始数据集的比例,取值范围为 [0, 1];对于有放回抽样,fraction 表示每个元素被抽取的期望次数,取值应大于等于 0。
  • seed:可选参数,表示随机数生成器的种子,指定种子可以确保每次抽样结果一致,便于调试和测试。

需求:对数据进行有放回抽样。

Java代码

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("sample");
JavaSparkContext sc = new JavaSparkContext(conf);

//sample(withReplacement, fraction, seed), withReplacement表示是否放回,fraction表示采样比例,seed表示随机种子
sc.parallelize(Arrays.asList(1,2,3,4,5,6,7,8,9,10))
        .sample(false,0.5,10)
        .foreach(s -> System.out.println(s));
sc.stop();

Scala代码

复制代码
val conf = new SparkConf()
  .setMaster("local")
  .setAppName("SampleTest")
val sc = new SparkContext(conf)
//sample(withReplacement, fraction, seed),
//withReplacement表示是否放回,fraction表示抽样的比例,seed表示随机种子
sc.parallelize(1 to 10)
  .sample(false, 0.5,10)
  .foreach(println)
sc.stop()

1.8 sortBy

会产生shuffle,可以按照指定的规则排序

sortBy 对任意类型的RDD 中的元素按照指定的键进行排序,需要一个函数来提取排序键,并可以指定升序或降序,以及分区数。函数签名如下:

复制代码
def sortBy[K](
    f: (T) => K,
    ascending: Boolean = true,
    numPartitions: Int = this.partitions.length
)(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]
  • f:从 RDD 的元素中提取排序键的函数。
  • ascending:布尔值,表示是否按升序排序,默认为 true。
  • numPartitions:排序后 RDD 的分区数,默认为与原始 RDD 相同。

特别注意:Java API中K,V格式RDD没有sortBy操作

案例:按照字符串长度降序排序。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("SortByTest");
JavaSparkContext sc = new JavaSparkContext(conf);
sc.parallelize(Arrays.asList("zhangsan", "lisi", "wangwu", "maliu"))
        //sortBy:按照字符串长度进行排序
        .sortBy(new Function<String, Integer>() {
            @Override
            public Integer call(String s) throws Exception {
                return s.length();
            }
        },true,1)
        .foreach(s -> System.out.println(s ));
sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("SortByTest")
val sc = new SparkContext(conf)

//sortBy(f, ascending, numPartitions)
//f表示排序的依据,ascending表示是否升序,numPartitions表示分区数
sc.parallelize(List("zhangsan","lisi","wangwu","maliu"))
  .sortBy(_.length)
  .foreach(println)

sc.stop()

1.9 sortByKey

会产生shuffle,对K,V格式的RDD按照key进行排序,默认升序排序

sortByKey 专用于对键值对 RDD 的键进行排序,可以指定升序或降序,以及分区数。

案例:按照K,V数据中的key大小降序排序。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("SortByKeyTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaPairRDD<Integer, String> rdd = sc.parallelizePairs(Arrays.asList(
        new Tuple2<Integer,String>(10, "zhangsan"),
        new Tuple2<Integer,String>(20, "lisi"),
        new Tuple2<Integer,String>(30, "wangwu"),
        new Tuple2<Integer,String>(40, "maliu")
));

//sortBy:按照字符串长度进行排序
//按照key进行排序,false降序排序
rdd.sortByKey(false).foreach(new VoidFunction<Tuple2<Integer,String>>() {
    @Override
    public void call(Tuple2<Integer, String> integerStringTuple2) throws Exception {
        System.out.println(integerStringTuple2);
    }
});

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("SortByKeyTest")
val sc = new SparkContext(conf)

sc.parallelize(List(("a",1),("b",2),("c",3),("d",4)))
  //sortByKey(ascending, numPartitions)
  //ascending表示是否升序,numPartitions表示分区数
  .sortByKey(false)
  .foreach(println)

sc.stop()

1.10 distinct

产生shuffle,去重算子,底层就是map+reduceByKey+map

用于对RDD数据进行去重,返回一个新的RDD,返回RDD包含原始RDD中所有唯一元素。实际底层distinct的实现为map+reduceByKey+map实现。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("DistinctTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.parallelize(Arrays.asList("a", "b", "c", "a", "b", "c"));

//如果手动实现去重:mapToPair+reduceByKey+map
JavaPairRDD<String, Integer> rdd1 = rdd.mapToPair(new PairFunction<String, String, Integer>() {
    @Override
    public Tuple2<String, Integer> call(String s) throws Exception {
        return new Tuple2<>(s, 1);
    }
});
JavaPairRDD<String, Integer> rdd2 = rdd1.reduceByKey(new Function2<Integer, Integer, Integer>() {
    @Override
    public Integer call(Integer v1, Integer v2) throws Exception {
        return v1 + v2;
    }
});

rdd2.map(new Function<Tuple2<String,Integer>, String>() {
    @Override
    public String call(Tuple2<String, Integer> v1) throws Exception {
        return v1._1;
    }
}).foreach(s->System.out.println(s));


//使用distinct算子去重,对源RDD去重后返回一个新的RDD
JavaRDD<String> distinct = rdd.distinct();
distinct.foreach(s->System.out.println(s));
sc.stop();

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("DistinctTest")
val sc = new SparkContext(conf)

val rdd: RDD[String] = sc.parallelize(Array("a","b","c","a","b","c"))
//手动去重,map+reduceByKey+map
rdd.map((_,1))
 .reduceByKey(_+_)
 .map(t=>t._1)
 .foreach(println)

//使用distinct去重
rdd.distinct().foreach(println)

sc.stop()

1.11 mapValues

该函数对K,V格式RDD中的Value按照传入的函数做转换,返回是K,V格式的RDD,Value为新转换后的Value。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("MapValuesTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaPairRDD<String, Integer> pairRDD = sc.parallelizePairs(Arrays.asList(
        new Tuple2<String, Integer>("a", 1),
        new Tuple2<String, Integer>("b", 2),
        new Tuple2<String, Integer>("c", 3)
));

//mapValues算子,只对value进行操作,key保持不变
JavaPairRDD<String, Integer> result = pairRDD.mapValues(new Function<Integer, Integer>() {
    @Override
    public Integer call(Integer integer) throws Exception {
        return integer * 2;
    }
});

result.foreach(tp->System.out.println(tp));

sc.stop();

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("MapValuesTest")
val sc = new SparkContext(conf)

val rdd: RDD[(String, Int)] = sc.parallelize(List(("a", 1), ("b", 2), ("c", 3)))
//mapValues:对rdd中value进行操作,key不变
rdd.mapValues(_ * 10)
  .foreach(println)

sc.stop()

此外,还有个flatMapValues算子,其与mapValues类似,只是mapValues一对一返回数据,而flatMapValues一对多返回数据

1.12 join/leftOuterJoin/rightOuterJoin/fullOuterJoin

join、leftOuterJoin、rightOuterJoin 和 fullOuterJoin 是用于对两个K,V格式 RDD 进行连接操作的转换算子,这些算子根据键(key)将两个RDD 进行合并,从而实现类似于关系型数据库中的连接操作。

注意:产生shuffle,所有Join操作join后生成RDD的分区数与父RDD分区数多的那一个相同。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("JoinTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaPairRDD<String, Integer> personRDD = sc.<String, Integer>parallelizePairs(Arrays.asList(
        new Tuple2<String, Integer>("zhangsan", 18),
        new Tuple2<String, Integer>("lisi", 19),
        new Tuple2<String, Integer>("wangwu", 20),
        new Tuple2<String, Integer>("maliu", 21)
),3);

JavaPairRDD<String, Integer> scoreRDD = sc.<String, Integer>parallelizePairs(Arrays.asList(
        new Tuple2<String, Integer>("zhangsan", 90),
        new Tuple2<String, Integer>("lisi", 80),
        new Tuple2<String, Integer>("wangwu", 70),
        new Tuple2<String, Integer>("tianqi", 60)
),4);

//join算子:对两个RDD进行join操作,返回一个新的RDD,
// 新RDD中的元素是元组,元组的第一个元素是key,第二个元素是一个元组,元组的第一个元素是第一个RDD中的value,第二个元素是第二个RDD中的value
JavaPairRDD<String, Tuple2<Integer, Integer>> joinRDD = personRDD.join(scoreRDD);
System.out.println("JoinRDD 分区数:" + joinRDD.getNumPartitions());
joinRDD.foreach(new VoidFunction<Tuple2<String, Tuple2<Integer, Integer>>>() {
    @Override
    public void call(Tuple2<String, Tuple2<Integer, Integer>> tp) throws Exception {
        System.out.println(tp);
    }
});
/**
 * 结果:
 * (zhangsan,(18,90))
 * (lisi,(19,80))
 * (wangwu,(20,70))
 */

//leftOuterJoin算子:对两个RDD进行leftOuterJoin操作,返回一个新的RDD,
// 新RDD中的元素是元组,元组的第一个元素是key,第二个元素是一个元组,
// 元组的第一个元素是第一个RDD中的value,第二个元素是第二个RDD中的value
JavaPairRDD<String, Tuple2<Integer, Optional<Integer>>> leftOuterJoinRDD = personRDD.leftOuterJoin(scoreRDD);
System.out.println("leftOuterJoinRDD 分区数:" + leftOuterJoinRDD.getNumPartitions());
leftOuterJoinRDD.foreach(new VoidFunction<Tuple2<String, Tuple2<Integer, Optional<Integer>>>>() {
    @Override
    public void call(Tuple2<String, Tuple2<Integer, Optional<Integer>>> tp) throws Exception {
        String name = tp._1;
        Integer age = tp._2._1;
        Optional<Integer> optionalScore = tp._2._2;
        if(optionalScore.isPresent()){//判断是否有值
            Integer score = optionalScore.get();
            System.out.println(name + "," + age + "," + score);
        } else {
            System.out.println(name + "," + age + "," + "null");
        }
////如果第二个RDD中没有对应的value,则返回默认值0
//Integer score = tp._2._2.orElse(0);
//System.out.println(name + "," + age + "," + score);
    }
});
/**
 * 结果:
 * zhangsan,18,90
 * wangwu,20,70
 * maliu,21,0
 * lisi,19,80
 */

//rightOuterJoin算子:对两个RDD进行rightOuterJoin操作,返回一个新的RDD,
// 新RDD中的元素是元组,元组的第一个元素是key,第二个元素是一个元组,
// 元组的第一个元素是第一个RDD中的value,第二个元素是第二个RDD中的value
JavaPairRDD<String, Tuple2<Optional<Integer>, Integer>> rightOuterJoinRDD = personRDD.rightOuterJoin(scoreRDD);
System.out.println("rightOuterJoinRDD 分区数:" + rightOuterJoinRDD.getNumPartitions());
rightOuterJoinRDD.foreach(new VoidFunction<Tuple2<String, Tuple2<Optional<Integer>, Integer>>>() {
    @Override
    public void call(Tuple2<String, Tuple2<Optional<Integer>, Integer>> tp) throws Exception {
        String name = tp._1;
        //如果第一个RDD中没有对应的value,则返回默认值0
        Integer age = tp._2._1.orElse(0);
        Integer score = tp._2._2;
        System.out.println(name + "," + age + "," + score);
    }
});

/**
 * 结果:
 * zhangsan,18,90
 * wangwu,20,70
 * lisi,19,80
 * tianqi,0,60
 */

//fullOuterJoin算子:对两个RDD进行fullOuterJoin操作,返回一个新的RDD,
// 新RDD中的元素是元组,元组的第一个元素是key,第二个元素是一个元组,
// 元组的第一个元素是第一个RDD中的value,第二个元素是第二个RDD中的value
JavaPairRDD<String, Tuple2<Optional<Integer>, Optional<Integer>>> fullOuterJoinRDD = personRDD.fullOuterJoin(scoreRDD);
System.out.println("fullOuterJoinRDD 分区数:" + fullOuterJoinRDD.getNumPartitions());
fullOuterJoinRDD.foreach(new VoidFunction<Tuple2<String, Tuple2<Optional<Integer>, Optional<Integer>>>>() {
    @Override
    public void call(Tuple2<String, Tuple2<Optional<Integer>, Optional<Integer>>> tp) throws Exception {
        String name = tp._1;
        //如果第一个RDD中没有对应的value,则返回默认值0
        Integer age = tp._2._1.orElse(0);
        //如果第二个RDD中没有对应的value,则返回默认值0
        Integer score = tp._2._2.orElse(0);
        System.out.println(name + "," + age + "," + score);

    }
});

/**
 * 结果:
 * zhangsan,18,90
 * wangwu,20,70
 * maliu,21,0
 * lisi,19,80
 * tianqi,0,60
 */

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("JoinTest")
val sc = new SparkContext(conf)

val personRDD: RDD[(String, Int)] = sc.parallelize(List(("zhangsan", 18), ("lisi", 19), ("wangwu", 20), ("maliu", 21)),3)
val scoreRDD: RDD[(String, Int)] = sc.parallelize(List(("zhangsan", 90), ("lisi", 80), ("wangwu", 70), ("tianqi", 60)),2)

//join: 两个RDD中key相同的元素进行连接
val joinRDD: RDD[(String, (Int, Int))] = personRDD.join(scoreRDD)
println(s"joinRDD 分区数:${joinRDD.getNumPartitions}")
joinRDD.foreach(println)

//leftOuterJoin: 左外连接,以左边的RDD为主,右边的RDD中key相同的元素进行连接,左边RDD中key没有的元素,右边RDD中key对应的value为None
val leftOuterJoinRDD: RDD[(String, (Int, Option[Int]))] = personRDD.leftOuterJoin(scoreRDD)
println(s"leftOuterJoinRDD 分区数:${leftOuterJoinRDD.getNumPartitions}")
leftOuterJoinRDD.foreach(println)

//rightOuterJoin: 右外连接,以右边的RDD为主,左边的RDD中key相同的元素进行连接,右边RDD中key没有的元素,左边RDD中key对应的value为None
val rightOuterJoinRDD: RDD[(String, (Option[Int], Int))] = personRDD.rightOuterJoin(scoreRDD)
println(s"rightOuterJoinRDD 分区数:${rightOuterJoinRDD.getNumPartitions}")
rightOuterJoinRDD.foreach(println)

//fullOuterJoin: 全外连接,以两个RDD中的key进行连接,两个RDD中key没有的元素,对应的value为None
val fullOuterJoinRDD: RDD[(String, (Option[Int], Option[Int]))] = personRDD.fullOuterJoin(scoreRDD)
println(s"fullOuterJoinRDD 分区数:${fullOuterJoinRDD.getNumPartitions}")
fullOuterJoinRDD.foreach(println)

sc.stop()

1.13 union

合并两个RDD数据,两个RDD数据集的类型要一致,不会对数据进行去重。

注意:返回新的RDD的分区数是两个合并RDD分区数的总和。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("UnionTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<Integer> rdd1 = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5), 3);
JavaRDD<Integer> rdd2 = sc.parallelize(Arrays.asList(6, 7, 8, 9, 10), 4);

//union算子:对两个RDD进行union操作,返回一个新的RDD,RDD的分区数是两个RDD的分区数的总和
JavaRDD<Integer> rdd3 = rdd1.union(rdd2);
System.out.println("rdd3 分区数:" + rdd3.getNumPartitions());

rdd3.foreach(x-> System.out.println(x));
sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("UnionTest")
val sc = new SparkContext(conf)

val rdd1: RDD[Int] = sc.parallelize(List(1,2,3,4,5),4)
val rdd2: RDD[Int] = sc.parallelize(List(6, 7, 8, 9, 10), 3)

//union: 两个RDD中的元素进行合并,合并后的RDD的分区数为两个RDD的分区数之和
val rdd3: RDD[Int] = rdd1.union(rdd2)
println(s"rdd3 分区数:${rdd3.getNumPartitions}")
rdd3.foreach(println)

sc.stop()

1.14 intersection

取两个RDD数据集的交集。

注意:产生shuffle,返回新的RDD分区数与父RDD分区多的一致。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("IntersectionTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd1 = sc.parallelize(Arrays.asList("a","b","c","d"), 3);
JavaRDD<String> rdd2 = sc.parallelize(Arrays.asList("c","d","e","f"), 4);

//intersection算子:对两个RDD进行intersection操作,返回一个新的RDD,RDD的分区数与父RDD分区数多的保持一致。
JavaRDD<String> rdd3 = rdd1.intersection(rdd2);
System.out.println("rdd3 分区数:" + rdd3.getNumPartitions());

rdd3.foreach(x-> System.out.println(x));

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("IntersectionTest")
val sc = new SparkContext(conf)
val rdd1: RDD[String] = sc.parallelize(List("a", "b", "c", "d"), 4)
val rdd2: RDD[String] = sc.parallelize(List("c", "d", "e", "f"), 3)

//intersection算子:对两个RDD进行intersection操作,返回一个新的RDD,RDD的分区数与父RDD分区数多的保持一致。
val rdd3: RDD[String] = rdd1.intersection(rdd2)
println(s"rdd3 分区数:${rdd3.getNumPartitions}")

rdd3.foreach(println)

sc.stop()

1.15 subtract

取两个RDD数据集的差集,rdd1.subtract(rdd2):返回rdd1中有但rdd2中没有的元素。

注意:产生shuffle,生成RDD的分区数与subtract前面的RDD的分区数一致。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("SubtractTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd1 = sc.parallelize(Arrays.asList("a","b","c","d"), 3);
JavaRDD<String> rdd2 = sc.parallelize(Arrays.asList("c","d","e","f"), 4);

//subtract算子:对两个RDD进行取差集操作,返回一个新的RDD,RDD的分区数与父RDD分区数多的保持一致。
JavaRDD<String> rdd3 = rdd1.subtract(rdd2);
System.out.println("rdd3 分区数:" + rdd3.getNumPartitions());
rdd3.foreach(x-> System.out.println(x));
sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("SubtractTest")
val sc = new SparkContext(conf)

val rdd1: RDD[String] = sc.parallelize(List("a", "b", "c", "d"), 3)
val rdd2: RDD[String] = sc.parallelize(List("c", "d", "e", "f"), 4)

//subtract算子:对两个RDD进行取差集操作,返回一个新的RDD,生成RDD的分区数与subtract前面的RDD的分区数一致。
val rdd3: RDD[String] = rdd1.subtract(rdd2)
println(s"rdd3 分区数:${rdd3.getNumPartitions}")

rdd3.foreach(println)
sc.stop()

1.16 cogroup

作用到K,V格式的两个RDD上,如两个RDD类型为(K,V)和(K,W)格式数据,返回一个数据集RDD(K,(Iterable<V>,Iterable<W>))。

注意:产生shuffle,子RDD的分区与父RDD多的一致。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("CogroupTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaPairRDD<String, Integer> personRDD = sc.<String, Integer>parallelizePairs(Arrays.asList(
        new Tuple2<String, Integer>("zhangsan", 18),
        new Tuple2<String, Integer>("zhangsan", 180),
        new Tuple2<String, Integer>("lisi", 19),
        new Tuple2<String, Integer>("wangwu", 20),
        new Tuple2<String, Integer>("wangwu", 200),
        new Tuple2<String, Integer>("maliu", 21)
),3);

JavaPairRDD<String, Integer> scoreRDD = sc.<String, Integer>parallelizePairs(Arrays.asList(
        new Tuple2<String, Integer>("zhangsan", 90),
        new Tuple2<String, Integer>("zhangsan", 900),
        new Tuple2<String, Integer>("lisi", 80),
        new Tuple2<String, Integer>("lisi", 800),
        new Tuple2<String, Integer>("wangwu", 70),
        new Tuple2<String, Integer>("wangwu", 700),
        new Tuple2<String, Integer>("tianqi", 60),
        new Tuple2<String, Integer>("tianqi", 600)
),4);

JavaPairRDD<String, Tuple2<Iterable<Integer>, Iterable<Integer>>> cogroupRDD = personRDD.cogroup(scoreRDD);
System.out.println("cogroupRDD 分区数:" + cogroupRDD.getNumPartitions());

cogroupRDD.foreach(new VoidFunction<Tuple2<String, Tuple2<Iterable<Integer>, Iterable<Integer>>>>() {
    @Override
    public void call(Tuple2<String, Tuple2<Iterable<Integer>, Iterable<Integer>>> tp) throws Exception {
        String name = tp._1;
        Iterable<Integer> ages = tp._2._1;
        Iterable<Integer> scores = tp._2._2;
        System.out.println("name: " + name+" ages: " + ages + " scores: " + scores);
    }
});

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("CogroupTest")
val sc = new SparkContext(conf)

val personRDD: RDD[(String, Int)] = sc.parallelize(List(
  ("zhangsan", 18),
  ("zhangsan", 180),
  ("lisi", 19),
  ("lisi", 190),
  ("wangwu", 20),
  ("wangwu", 200),
  ("maliu", 21)),3)

val scoreRDD: RDD[(String, Int)] = sc.parallelize(List(
  ("zhangsan", 90),
  ("zhangsan", 900),
  ("lisi", 80),
  ("lisi", 800),
  ("wangwu", 70),
  ("wangwu", 700),
  ("tianqi", 60)),2)

//cogroup: 两个RDD中key相同的元素进行连接
val cogroupRDD: RDD[(String, (Iterable[Int], Iterable[Int]))] = personRDD.cogroup(scoreRDD)
println(s"cogroupRDD 分区数:${cogroupRDD.getNumPartitions}")
cogroupRDD.foreach(tp=>{
  val name: String = tp._1
  val ageList: List[Int] = tp._2._1.toList
  val scoreList: List[Int] = tp._2._2.toList
  println(s"name:${name}, ageList:${ageList}, scoreList:${scoreList}")
})

sc.stop()

1.17 zip

将两个RDD中的元素(KV格式/非KV格式)变成一个KV格式的RDD,两个RDD的每个分区元素个数必须相同。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("zipTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd1 = sc.parallelize(Arrays.asList("a", "b", "c", "d"));
JavaRDD<String> rdd2 = sc.parallelize(Arrays.asList("e", "f", "g", "h"));
//zip: 将两个RDD中的元素按照位置一一对应,返回一个新的RDD
JavaPairRDD<String, String> zip = rdd1.zip(rdd2);
zip.foreach(tp-> System.out.println(tp));

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("zipTest")
val sc = new SparkContext(conf)

val rdd1: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5))
val rdd2: RDD[String] = sc.parallelize(Array("a", "b", "c", "d", "e"))

rdd1.zip(rdd2).foreach(println)

sc.stop()

1.18 zipWithIndex

该函数将RDD中的元素和这个元素在RDD中的索引号(从0开始)组合成(K,V)对。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("zipWithIndexTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.parallelize(Arrays.asList("a", "b", "c", "d"));
//zipWithIndex: 将RDD中的元素和其索引对应,返回一个新的RDD
JavaPairRDD<String, Long> zipWithIndex = rdd.zipWithIndex();
zipWithIndex.foreach(tp-> System.out.println(tp));

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("zipWithIndexTest")
val sc = new SparkContext(conf)

val rdd: RDD[String] = sc.parallelize(Array("a", "b", "c", "d", "e"))
//zipWithIndex: 将RDD中的元素和其索引对应,返回一个新的RDD
rdd.zipWithIndex().foreach(println)

sc.stop()

1.19 mapPartitions

与map类似,map在处理数据时,遍历单位是每条数据,而mapPartition遍历的单位是每个partition上的数据,可以对每个分区中的数据进行批量操作。

mapPartitions 适用于需要对每个分区的数据进行批量处理的场景,例如:初始化一次性建立的数据库连接,然后对分区内的数据进行批量写入,这种在分区级别进行资源密集型操作非常适合使用mapPartitions,以减少重复的初始化开销。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("MapPartitionsTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd1 = sc.parallelize(Arrays.asList("a", "b", "c", "d", "e", "f"), 2);

//通过map将数据写入数据库,每条数据都要创建一次数据库连接,效率低
/*rdd1.map(new Function<String, String>() {
    @Override
    public String call(String s) throws Exception {
        System.out.println("创建数据库连接...");
        System.out.println("插入数据:" + s);
        System.out.println("关闭数据库连接...");
        return s;
    }
}).count();*/

//通过mapPartitions将数据写入数据库,每个分区创建一次数据库连接,效率高
rdd1.mapPartitions(new FlatMapFunction<Iterator<String>, String>() {
    @Override
    public Iterator<String> call(Iterator<String> iter) throws Exception {
        List<String> list = new ArrayList<>();
        System.out.println("创建数据库连接...");
        while (iter.hasNext()) {
            String s = iter.next();
            list.add(s);
            System.out.println("插入数据:" + s);
        }
        System.out.println("关闭数据库连接...");
        return list.iterator();
    }
}).count();

sc.stop();

Sala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("MapPartitionsTest")
val sc = new SparkContext(conf)

val rdd: RDD[String] = sc.parallelize(List("a", "b", "c", "d", "e", "f"), 3)
//mapPartitions算子:对RDD中的每个分区进行操作,返回一个新的RDD,分区数不变。
rdd.mapPartitions(iter=>{
  val list = ListBuffer[String]()
  println("创建数据库连接...")
  while (iter.hasNext){
    val next = iter.next()
    list.append(next)
    println(s"插入数据... $next")
  }
  println("关闭数据库连接...")
  list.iterator
}).count()

sc.stop()

1.20 mapPartitionsWithIndex

类似于mapPartitions,除此之外可以在函数中获取当前分区的索引值。函数签名如下:

复制代码
def mapPartitionsWithIndex[U: ClassTag](f: (Int, Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false): RDD[U]

其中,f 是应用于每个分区的函数,接受两个参数:分区索引(Int)和分区内元素的迭代器(Iterator[T]),返回一个新的迭代器(Iterator[U])。preservesPartitioning 参数指示输入函数是否保留分区器,默认为 false,如果输入函数不修改键,则可以将其设置为 true。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("mapPartitionsWithIndexTest");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> rdd1 = sc.parallelize(Arrays.asList(
        "love1", "love2", "love3", "love4",
        "love5", "love6", "love7", "love8",
        "love9", "love10", "love11", "love12"
), 3);

JavaRDD<String> rdd2 = rdd1.mapPartitionsWithIndex(new Function2<Integer, Iterator<String>, Iterator<String>>() {
    //index: 分区的索引,从0开始
    //iter: 分区中的元素
    @Override
    public Iterator<String> call(Integer index, Iterator<String> iter) throws Exception {
        ArrayList<String> list = new ArrayList<>();
        while (iter.hasNext()) {
            String next = iter.next();
            list.add("rdd1 partition index: " + index + " current value: " + next);
        }
        return list.iterator();
    }
}, true);

//collect: 将RDD中的元素收集到Driver中
List<String> collect = rdd2.collect();
for (String s : collect) {
    System.out.println(s);
}

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("MapPartitionsWithIndexTest")
val sc = new SparkContext(conf)

val rdd1: RDD[String] = sc.parallelize(List(
  "love1", "love2", "love3", "love4",
  "love5", "love6", "love7", "love8",
  "love9", "love10", "love11", "love12"
), 3)

val rdd2: RDD[String] = rdd1.mapPartitionsWithIndex((index, iter) => {
  val list = new ListBuffer[String]()
  while (iter.hasNext) {
    list.append(s"rdd1 partition index: $index ,current value: ${iter.next()}")
  }
  list.iterator
})

rdd2.foreach(println)

sc.stop()

Java和Scala API运行结果如下:

复制代码
rdd1 partition index: 0 current value: love1
rdd1 partition index: 0 current value: love2
rdd1 partition index: 0 current value: love3
rdd1 partition index: 0 current value: love4
rdd1 partition index: 1 current value: love5
rdd1 partition index: 1 current value: love6
rdd1 partition index: 1 current value: love7
rdd1 partition index: 1 current value: love8
rdd1 partition index: 2 current value: love9
rdd1 partition index: 2 current value: love10
rdd1 partition index: 2 current value: love11
rdd1 partition index: 2 current value: love12

1.21 repartition

repartition可以对RDD进行重新分区,可以增加或减少分区,这个过程会产生shuffle,常用于对RDD进行增加分区,提高并行度场景。

注意:在底层,repartition(numPartitions) = coalesce(numPartitions,true)。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("repartitionTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd1 = sc.parallelize(Arrays.asList(
        "love1", "love2", "love3", "love4",
        "love5", "love6", "love7", "love8",
        "love9", "love10", "love11", "love12"
), 3);

JavaRDD<String> rdd2 = rdd1.mapPartitionsWithIndex(new Function2<Integer, Iterator<String>, Iterator<String>>() {
    //index: 分区的索引,从0开始
    //iter: 分区中的元素
    @Override
    public Iterator<String> call(Integer index, Iterator<String> iter) throws Exception {
        ArrayList<String> list = new ArrayList<>();
        while (iter.hasNext()) {
            String next = iter.next();
            list.add("rdd1 partition index: " + index + " current value: " + next);
        }
        return list.iterator();
    }
}, true);

//对rdd2 进行重新分区
//JavaRDD<String> rdd3 = rdd2.repartition(4); //增加分区
JavaRDD<String> rdd3 = rdd2.repartition(2);//减少分区

JavaRDD<String> rdd4 = rdd3.mapPartitionsWithIndex(new Function2<Integer, Iterator<String>, Iterator<String>>() {
    //index: 分区的索引,从0开始
    //iter: 分区中的元素
    @Override
    public Iterator<String> call(Integer index, Iterator<String> iter) throws Exception {
        ArrayList<String> list = new ArrayList<>();
        while (iter.hasNext()) {
            String next = iter.next();
            list.add("rdd3 partition index: 【" + index + "】,current value: 【" + next + "】");
        }
        return list.iterator();
    }
}, true);

List<String> result = rdd4.collect();
for (String s : result) {
    System.out.println(s);
}

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("RepartitionTest")
val sc = new SparkContext(conf)

val rdd1: RDD[String] = sc.parallelize(List(
  "love1", "love2", "love3", "love4",
  "love5", "love6", "love7", "love8",
  "love9", "love10", "love11", "love12"
), 3)

val rdd2: RDD[String] = rdd1.mapPartitionsWithIndex((index, iter) => {
  val list = new ListBuffer[String]()
  while (iter.hasNext) {
    list.append(s"rdd1 partition index: $index ,current value: ${iter.next()}")
  }
  list.iterator
})

//对rdd2进行重分区
val rdd3: RDD[String] = rdd2.repartition(4) //增加分区
//val rdd3: RDD[String] = rdd2.repartition(2) //减少分区

val rdd4: RDD[String] = rdd3.mapPartitionsWithIndex((index, iter) => {
  val list = new ListBuffer[String]()
  while (iter.hasNext) {
    list.append(s"rdd3 partition index: 【$index】 ,current value: 【${iter.next()}】")
  }
  list.iterator
})

rdd4.collect.foreach(println)

sc.stop()

Java和Scala API结果如下,可见通过repartition进行增加或者减少分区操作会产生shuffle操作。

复制代码
#repartition(4)结果
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love4】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 1 current value: love8】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 2 current value: love9】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 0 current value: love1】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love5】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 2 current value: love10】
rdd3 partition index: 【2】,current value: 【rdd1 partition index: 0 current value: love2】
rdd3 partition index: 【2】,current value: 【rdd1 partition index: 1 current value: love6】
rdd3 partition index: 【2】,current value: 【rdd1 partition index: 2 current value: love11】
rdd3 partition index: 【3】,current value: 【rdd1 partition index: 0 current value: love3】
rdd3 partition index: 【3】,current value: 【rdd1 partition index: 1 current value: love7】
rdd3 partition index: 【3】,current value: 【rdd1 partition index: 2 current value: love12】

#repartition(2)结果
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love2】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love4】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 1 current value: love6】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 1 current value: love8】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 2 current value: love9】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 2 current value: love11】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 0 current value: love1】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 0 current value: love3】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love5】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love7】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 2 current value: love10】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 2 current value: love12】

1.22 coalesce

coalesce也可以对RDD分区增加或者减少,常用于减少RDD的分区数量,常用于提高小数据集的执行效率。与 repartition 不同,coalesce 默认情况下不会触发 Shuffle 操作,因此在减少分区时更加高效。函数签名如下:

复制代码
def coalesce(numPartitions: Int, shuffle: Boolean = false): RDD[T]

其中,numPartitions 表示目标分区数,shuffle 参数指示是否进行 Shuffle,默认为 false。

特别注意:如果coalesce设置的分区数比原来的RDD的分区数还多的话,第二个参数设置为false不会起作用,如果设置成true,效果和repartition一样。即repartition(numPartitions) = coalesce(numPartitions,true)。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("CoalesceTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd1 = sc.parallelize(Arrays.asList(
        "love1", "love2", "love3", "love4",
        "love5", "love6", "love7", "love8",
        "love9", "love10", "love11", "love12"
), 3);

JavaRDD<String> rdd2 = rdd1.mapPartitionsWithIndex(new Function2<Integer, Iterator<String>, Iterator<String>>() {
    //index: 分区的索引,从0开始
    //iter: 分区中的元素
    @Override
    public Iterator<String> call(Integer index, Iterator<String> iter) throws Exception {
        ArrayList<String> list = new ArrayList<>();
        while (iter.hasNext()) {
            String next = iter.next();
            list.add("rdd1 partition index: " + index + " current value: " + next);
        }
        return list.iterator();
    }
}, true);

//coalesce对rdd2 进行重新分区,没有shuffle
//JavaRDD<String> rdd3 = rdd2.coalesce(2);
//JavaRDD<String> rdd3 = rdd2.coalesce(2,true);
JavaRDD<String> rdd3 = rdd2.coalesce(4,false);

JavaRDD<String> rdd4 = rdd3.mapPartitionsWithIndex(new Function2<Integer, Iterator<String>, Iterator<String>>() {
    //index: 分区的索引,从0开始
    //iter: 分区中的元素
    @Override
    public Iterator<String> call(Integer index, Iterator<String> iter) throws Exception {
        ArrayList<String> list = new ArrayList<>();
        while (iter.hasNext()) {
            String next = iter.next();
            list.add("rdd3 partition index: 【" + index + "】,current value: 【" + next + "】");
        }
        return list.iterator();
    }
}, true);

List<String> result = rdd4.collect();
for (String s : result) {
    System.out.println(s);
}

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("CoalesceTest")
val sc = new SparkContext(conf)

val rdd1: RDD[String] = sc.parallelize(List(
  "love1", "love2", "love3", "love4",
  "love5", "love6", "love7", "love8",
  "love9", "love10", "love11", "love12"
), 3)

val rdd2: RDD[String] = rdd1.mapPartitionsWithIndex((index, iter) => {
  val list = new ListBuffer[String]()
  while (iter.hasNext) {
    list.append(s"rdd1 partition index: $index ,current value: ${iter.next()}")
  }
  list.iterator
})

//coalesce对rdd2进行重分区,不产生shuffle
//val rdd3: RDD[String] = rdd2.coalesce(2)
//val rdd3: RDD[String] = rdd2.coalesce(2,true)
val rdd3: RDD[String] = rdd2.coalesce(4)

val rdd4: RDD[String] = rdd3.mapPartitionsWithIndex((index, iter) => {
  val list = new ListBuffer[String]()
  while (iter.hasNext) {
    list.append(s"rdd3 partition index: 【$index】 ,current value: 【${iter.next()}】")
  }
  list.iterator
})

rdd4.collect.foreach(println)

sc.stop()

Java和Scala API结果如下:

复制代码
#coalesce(2)
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love1】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love2】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love3】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love4】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love5】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love6】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love7】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love8】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 2 current value: love9】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 2 current value: love10】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 2 current value: love11】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 2 current value: love12】

#coalesce(2,true),等同于repartition(2)
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love2】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love4】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 1 current value: love6】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 1 current value: love8】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 2 current value: love9】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 2 current value: love11】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 0 current value: love1】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 0 current value: love3】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love5】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love7】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 2 current value: love10】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 2 current value: love12】

#coalesce(4,false) 不起作用
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love1】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love2】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love3】
rdd3 partition index: 【0】,current value: 【rdd1 partition index: 0 current value: love4】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love5】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love6】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love7】
rdd3 partition index: 【1】,current value: 【rdd1 partition index: 1 current value: love8】
rdd3 partition index: 【2】,current value: 【rdd1 partition index: 2 current value: love9】
rdd3 partition index: 【2】,current value: 【rdd1 partition index: 2 current value: love10】
rdd3 partition index: 【2】,current value: 【rdd1 partition index: 2 current value: love11】
rdd3 partition index: 【2】,current value: 【rdd1 partition index: 2 current value: love12】

1.23 glom

glom可以将每个分区中的数据元素合并为一个数组,将RDD[T]转换成RDD[Array[T]]类型。如果RDD数据量小且需要对分区内的数据进行统计时(最大、最小值统计)可以使用glom。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("GlomTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(10, 20, 5, 7, 9, 20, 15, 3, 8), 3);
rdd.mapPartitionsWithIndex(new Function2<Integer, Iterator<Integer>, Iterator<String>>() {
    @Override
    public Iterator<String> call(Integer index, Iterator<Integer> iter) throws Exception {
        ArrayList<String> list = new ArrayList<>();
        while (iter.hasNext()) {
            Integer next = iter.next();
            list.add("rdd1 partition index: " + index + " current value: " + next);
        }
        return list.iterator();

    }
},true).foreach(s->System.out.println(s));

//glom: 将RDD每个分区中的元素放到一个集合中,形成RDD
JavaRDD<List<Integer>> glomRDD = rdd.glom();

glomRDD.foreach(list->{
    System.out.println(list);
    //计算每个分区元素总和
    int sum = 0;
    for (Integer i : list) {
        sum += i;
    }
    System.out.println("sum: " + sum);
});

sc.stop();

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("GlomTest")
val sc = new SparkContext(conf)

val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 3)

rdd.mapPartitionsWithIndex((index,iter)=>{
  val list = new ListBuffer[String]()
  while (iter.hasNext) {
    list.append(s"rdd partition index: $index ,current value: ${iter.next()}")
  }
  list.iterator
},true)
  .foreach(println)

val result: RDD[Array[Int]] = rdd.glom()
result.foreach(list=>{
  println(list.mkString(","))
  println(list.sum)
})

sc.stop()

1.24 foldByKey

foldByKey针对K,V格式RDD进行数据聚合操作,与reduceByKey类似,该算子是map端有预聚合的算子,但可以为每个分区中的每个不同K提供一个初始值。其函数签名如下:

复制代码
def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
  • zeroValue:在map端聚合过程,每个分区中的每个不同K的初始值。
  • func:用于在map端和reduce端合并具有相同K的V的函数。

注意:需要对具有相同键的数据进行聚合操作,且分区内和分区间的聚合规则相同时,可以使用foldByKey,如果分区内和分区间的聚合规则不相同可以使用aggregateByKey。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("foldByKeyTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaPairRDD<String, Integer> rdd = sc.parallelizePairs(Arrays.asList(
        new Tuple2<String, Integer>("a", 1),
        new Tuple2<String, Integer>("b", 2),
        new Tuple2<String, Integer>("a", 3),
        new Tuple2<String, Integer>("b", 4),
        new Tuple2<String, Integer>("c", 5)
));

JavaPairRDD<String, Integer> result = rdd.foldByKey(10, new Function2<Integer, Integer, Integer>() {
    @Override
    public Integer call(Integer v1, Integer v2) throws Exception {
        return v1 + v2;
    }
});

//结果:a:14 b:16 c:15
result.foreach(tp-> System.out.println(tp));

sc.stop();

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("FoldByKeyTest")
val sc = new SparkContext(conf)

val rdd: RDD[(String, Int)] = sc.parallelize(Array(
  ("a", 1),
  ("b", 2),
  ("a", 3),
  ("b", 4),
  ("c", 5)), 3)

rdd.foldByKey(10)(_+_)
  .foreach(println)

sc.stop()

结果:

复制代码
#如果分区设置为1个,每个分区不同key初始值为10
结果:a:14 b:16 c:15

#如果分区设置为3个,每个分区不同key初始值为10
结果:a:24 b:26 c:15

1.25 aggregateByKey

aggregateByKey针对K,V格式RDD进行数据聚合操作,也是map端有预聚合的算子,与 reduceByKey 和 foldByKey 等算子不同,aggregateByKey 允许用户分别定义分区内和分区间的聚合规则,提供了更大的灵活性。其函数签名如下:

复制代码
def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U, combOp: (U, U) => U): RDD[(K, U)]
  • zeroValue:聚合操作的初始值,在每个分区中,每个K都会使用该初始值开始其聚合过程。
  • seqOp:map端分区内的聚合函数,决定将分区内相同K的V如何合并。
  • combOp:reduce端分区间的聚合函数,用于将不同分区中相同K的map聚合结果进行合并。

特别注意:zeroValue 会在每个分区的每个键上应用一次,因此应根据具体需求选择合适的初始值,以避免对结果产生不必要的影响。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("aggregateByKeyTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaPairRDD<String, Integer> rdd = sc.parallelizePairs(Arrays.asList(
        new Tuple2<>("zhangsan", 10),
        new Tuple2<>("zhangsan", 20),
        new Tuple2<>("wangwu", 30),
        new Tuple2<>("lisi", 40),
        new Tuple2<>("zhangsan", 50),
        new Tuple2<>("lisi", 60),
        new Tuple2<>("wangwu", 70),
        new Tuple2<>("wangwu", 80),
        new Tuple2<>("lisi", 90)

), 3);

rdd.mapPartitionsWithIndex(new Function2<Integer, Iterator<Tuple2<String, Integer>>, Iterator<String>>() {
    @Override
    public Iterator<String> call(Integer index, Iterator<Tuple2<String, Integer>> iter) throws Exception {
        ArrayList<String> list = new ArrayList<>();
        while (iter.hasNext()) {
            Tuple2<String,Integer> next = iter.next();
            list.add("rdd partition index: " + index + " current value: " + next);
        }
        return list.iterator();
    }
},true).foreach(x-> System.out.println(x));
/**
 * 0号分区:
 *    ("zhangsan",10) ("zhangsan",20) ("wangwu",30)
 * 1号分区:
 *    ("lisi",40) ("zhangsan",50) ("lisi",60)
 * 2号分区:
 *    ("wangwu",70) ("wangwu",80) ("lisi",90)
 * 经过第二个参数,map端聚合 :
 *  0:("zhangsan",hello~10~20),("wangwu",hello~30)
 *  1:("zhangsan",hello~50),("lisi",hello~40~60)
 *  2:("lisi",hello~90),("wangwu",hello~70~80)
 *  经过第三个参数,分区合并后:
 *  ("zhangsan",hello~10~20#hello~50)
 *  ("lisi",hello~40~60#hello~90)
 *  ("wangwu",hello~30#hello~70~80)
 */
JavaPairRDD<String, String> result = rdd.aggregateByKey("hello", new Function2<String, Integer, String>() {
    @Override
    public String call(String s, Integer integer) throws Exception {
        return s + "~" + integer;
    }
}, new Function2<String, String, String>() {
    @Override
    public String call(String s1, String s2) throws Exception {
        return s1 + "#" + s2;
    }
});

result.foreach(s-> System.out.println(s));

sc.stop();

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("aggregateByKeyTest")
val sc = new SparkContext(conf)

val rdd: RDD[(String, Int)] = sc.parallelize(List(
  ("zhangsan", 10),
  ("zhangsan", 20),
  ("wangwu", 30),
  ("lisi", 40),
  ("zhangsan", 50),
  ("lisi", 60),
  ("wangwu", 70),
  ("wangwu", 80),
  ("lisi", 90)
), 3)

rdd.mapPartitionsWithIndex((index, iter) => {
  val list = new ListBuffer[String]()
  while (iter.hasNext) {
    list.append(s"rdd partition index: $index ,current value: ${iter.next()}")
  }
  list.iterator
}).foreach(println)

/**
 * 0号分区:
 *  ("zhangsan",10) ("zhangsan",20) ("wangwu",30)
 * 1号分区:
 *  ("lisi",40) ("zhangsan",50) ("lisi",60)
 * 2号分区:
 *  ("wangwu",70) ("wangwu",80) ("lisi",90)
 * 经过第二个参数,map端聚合 :
 *  0:("zhangsan",hello~10~20),("wangwu",hello~30)
 *  1:("zhangsan",hello~50),("lisi",hello~40~60)
 *  2:("lisi",hello~90),("wangwu",hello~70~80)
 * 经过第三个参数,分区合并后:
 *  ("zhangsan",hello~10~20#hello~50)
 *  ("lisi",hello~40~60#hello~90)
 *  ("wangwu",hello~30#hello~70~80)
 */
val result: RDD[(String, String)] = rdd.aggregateByKey("hello")(
  //map端聚合,分区内聚合
  (x, y) => x + "~" + y,
  //reduce端聚合,分区间聚合
  (x, y) => x + "#" + y
)

result.foreach(println)

sc.stop()

1.26 combineByKey

combineeByKey针对K,V格式RDD进行数据聚合操作,也是map端有预聚合的算子,与aggregateByKey类似,支持在map端基于每个分区中的每个K第一个V进行初始化。其函数签名如下:

复制代码
def combineByKey[C](
    createCombiner: V => C,
    mergeValue: (C, V) => C,
    mergeCombiners: (C, C) => C,
    numPartitions: Int
): RDD[(K, C)]
  • createCombiner:定义如何将每个K的第一个值V转换为组合器(combiner)类型。
  • mergeValue:定义如何将新的值V合并到已有的组合器中。
  • mergeCombiners:定义如何合并来自不同分区的组合器。
  • numPartitions:指定结果 RDD 的分区数量。

对于每个分区内的每个K,使用createCombiner 将第一个遇到的V转换为组合器类型,随后,使用 mergeValue 将该键的其余值逐一合并到组合器中。当所有分区内的处理完成后,使用 mergeCombiners 将不同分区中相同键的组合器合并,得到最终结果。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("combineByKeyTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaPairRDD<String, Integer> rdd = sc.parallelizePairs(Arrays.asList(
        new Tuple2<>("zhangsan", 10),
        new Tuple2<>("zhangsan", 20),
        new Tuple2<>("wangwu", 30),
        new Tuple2<>("lisi", 40),
        new Tuple2<>("zhangsan", 50),
        new Tuple2<>("lisi", 60),
        new Tuple2<>("wangwu", 70),
        new Tuple2<>("wangwu", 80),
        new Tuple2<>("lisi", 90)

), 3);
rdd.mapPartitionsWithIndex(new Function2<Integer, Iterator<Tuple2<String, Integer>>, Iterator<String>>() {
    @Override
    public Iterator<String> call(Integer index, Iterator<Tuple2<String, Integer>> iter) throws Exception {
        ArrayList<String> list = new ArrayList<>();
        while (iter.hasNext()) {
            Tuple2<String,Integer> next = iter.next();
            list.add("rdd partition index: " + index + " current value: " + next);
        }
        return list.iterator();
    }
},true).foreach(x-> System.out.println(x));

/**
 * 0号分区:("zhangsan", 10), ("zhangsan", 20), ("wangwu", 30)
 * 1号分区:("lisi", 40), ("zhangsan", 50), ("lisi", 60)
 * 2号分区:("wangwu", 70), ("wangwu", 80), ("lisi", 90)
 *
 * 初始化后:
 * 0号分区:("zhangsan", 10hello),("wangwu", 30hello)
 * 1号分区:("lisi", 40hello), ("zhangsan", 50hello)
 * 2号分区:("wangwu", 70hello),("lisi", 90hello)
 *
 * 经过RDD map分区内的合并后:
 * 0号分区:("zhangsan", 10hello@20),("wangwu", 30hello)
 * 1号分区:("lisi", 40hello@60), ("zhangsan", 50hello)
 * 2号分区:("wangwu", 70hello@80),("lisi", 90hello)
 *
 * 经过RDD分区之间的合并:("zhangsan", 10hello@20#50hello),("lisi",40hello@60#90hello),("wangwu", 30hello#70hello@80)
 */
JavaPairRDD<String, String> result = rdd.combineByKey(new Function<Integer, String>() {
    @Override
    public String call(Integer v) throws Exception {
        return v+"hello";
    }
}, new Function2<String, Integer, String>() {
    @Override
    public String call(String s, Integer v) throws Exception {
        return s+"@"+v;
    }
}, new Function2<String, String, String>() {
    @Override
    public String call(String s1, String s2) throws Exception {
        return s1+"#"+s2;
    }
});

result.foreach(x-> System.out.println(x));

sc.stop();

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("combineByKeyTest")
val sc = new SparkContext(conf)

val rdd: RDD[(String, Int)] = sc.parallelize(List(
  ("zhangsan", 10),
  ("zhangsan", 20),
  ("wangwu", 30),
  ("lisi", 40),
  ("zhangsan", 50),
  ("lisi", 60),
  ("wangwu", 70),
  ("wangwu", 80),
  ("lisi", 90)
), 3)

rdd.mapPartitionsWithIndex((index, iter) => {
  val list = new ListBuffer[String]()
  while (iter.hasNext) {
    list.append(s"rdd partition index: $index ,current value: ${iter.next()}")
  }
  list.iterator
}).foreach(println)

/**
 * 0号分区:("zhangsan", 10), ("zhangsan", 20), ("wangwu", 30)
 * 1号分区:("lisi", 40), ("zhangsan", 50), ("lisi", 60)
 * 2号分区:("wangwu", 70), ("wangwu", 80), ("lisi", 90)
 *
 * 初始化后:
 * 0号分区:("zhangsan", 10hello),("wangwu", 30hello)
 * 1号分区:("lisi", 40hello), ("zhangsan", 50hello)
 * 2号分区:("wangwu", 70hello),("lisi", 90hello)
 *
 * 经过RDD分区内的合并后:
 * 0号分区:("zhangsan", 10hello@20),("wangwu", 30hello)
 * 1号分区:("lisi", 40hello@60), ("zhangsan", 50hello)
 * 2号分区:("wangwu", 70hello@80),("lisi", 90hello)
 *
 * 经过RDD分区之间的合并:("zhangsan", 10hello@20#50hello),("lisi",40hello@60#90hello),("wangwu", 30hello#70hello@80)
 */
val result: RDD[(String, String)] = rdd.combineByKey(
  v=>{v+"hello"},
  (s:String, v)=>{s+"@"+v},
  (s1:String, s2:String)=>{s1+"#"+s2}
)
result.foreach(println)

sc.stop()

2. Action-行动算子

Action算子对 RDD 执行计算操作,将结果返回到Driver端或写入外部存储,这些操作会触发实际的计算过程,即:Transformations类算子是延迟执行,Action类算子是触发执行。此外,一个action算子对应一个spark job,一个application中有几个action算子就有几个job。下面对常见的Action算子进行介绍。

2.1 foreach

循环遍历数据集中的每个元素,运行相应的逻辑。

2.2 foreachpartition

foreach遍历单位为每条数据,foreachPartition遍历单位是每个分区,可以对每个分区的数据进行批量操作。适用于需要对每个分区的数据进行批量处理的场景,例如在每个分区中建立一次数据库连接,然后对分区内的数据进行批量写入,避免为每个元素建立连接的开销。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("foreachPartitionTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.parallelize(Arrays.asList("a", "b", "c", "d", "e", "f"), 2);

//foreach 遍历每条数据,每条数据都要创建一次数据库连接,效率低
/*rdd.foreach(str->{
    System.out.println("创建数据库连接...");
    System.out.println("插入数据:" + str);
    System.out.println("关闭数据库连接...");
});*/

//foreachPartition 遍历每个分区的数据,每个分区创建一次数据库连接,效率高
rdd.foreachPartition(iter->{
    System.out.println("创建数据库连接...");
    while (iter.hasNext()) {
        String s = iter.next();
        System.out.println("插入数据:" + s);
    }
    System.out.println("关闭数据库连接...");
});

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("ForeachPartitionTest")
val sc = new SparkContext(conf)

val rdd: RDD[String] = sc.parallelize(List("a", "b", "c", "d", "e", "f"), 2)

//foreach 遍历每条数据,每条数据都要创建一次数据库连接,效率低
/*rdd.foreach(str=>{
  println("创建数据库连接...")
  println(s"插入数据... $str")
  println("关闭数据库连接...")
})*/

//foreachPartition 遍历每个分区的数据,每个分区创建一次数据库连接,效率高
rdd.foreachPartition(iter=>{
  println("创建数据库连接...")
  while (iter.hasNext){
    val next = iter.next()
    println(s"插入数据... $next")
  }
  println("关闭数据库连接...")
})


sc.stop()

2.3 count

返回数据集中的元素总数量,会在结果计算完成后回收到Driver端。

案例:统计数据总条数。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("filter");
JavaSparkContext sc = new JavaSparkContext(conf);
//count:统计RDD中元素的个数
long count = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).count();
System.out.println(count);
sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("CountTest")
val sc = new SparkContext(conf)

//count: 统计RDD中元素的个数
val count: Long = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).count()
println(count)
sc.stop()

2.4 reduce

reduce可以对RDD中所有元素进行聚合操作,最终形成一个单一的结果。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("CustomPartitionerTest");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
Integer reduce = rdd.reduce((a, b) -> a + b);
System.out.println(reduce);
sc.stop();

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("filter")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
val result: Int = rdd.reduce(_ + _)
println(result)
sc.stop()

2.5 take(n)

返回一个包含数据集前n个元素的集合,结果会返回到Driver端。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("TakeTest");
JavaSparkContext sc = new JavaSparkContext(conf);
//take:返回RDD中的前n个元素
List<String> takes = sc.parallelize(Arrays.asList("a", "b", "c", "d")).take(3);
System.out.println(takes.toString());
sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("TakeTest")
val sc = new SparkContext(conf)

//take: 取出RDD中前n个元素
val nums: Array[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).take(3)
println(nums.mkString(","))
sc.stop()

2.6 takeSample

takeSample用于从RDD中随机抽取指定数量的元素,返回一个集合到Driver端。方法签名如下:

复制代码
def takeSample(
    withReplacement: Boolean,
    num: Int,
    seed: Long = Utils.random.nextLong
  ): Array[T]

其中,withReplacement 指定抽样时是否放回,true表示有放回抽样,同一个元素可能被多次抽取,false表示无放回抽样,同一个元素最多被抽取一次;num 指定要随机抽取的元素数量;seed 是随机数生成器的种子,默认为随机生成,该参数如果固定,那么每次获取的数据固定。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("takeSampleTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.parallelize(Arrays.asList(
        "a", "b", "c", "d", "e", "f","h","i","j","k"));

//takeSample: 从RDD中随机抽取num个元素,withReplacement表示是否放回,seed表示随机数种子
List<String> list = rdd.takeSample(true, 4,100);
for (String s : list) {
    System.out.println(s);
}

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("ForeachPartitionTest")
val sc = new SparkContext(conf)

val rdd: RDD[String] = sc.parallelize(List("a", "b", "c", "d", "e", "f", "h", "i", "j", "k"))

//takeSample: 从RDD中随机抽取num个元素,withReplacement表示是否放回,seed表示随机数种子
val strings: Array[String] = rdd.takeSample(true, 4, 100)
println(strings.mkString(","))

sc.stop()

2.7 first

返回数据集中第一个元素,first=take(1)。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("FirstTest");
JavaSparkContext sc = new JavaSparkContext(conf);
//first:返回RDD中的第一个元素
String first = sc.parallelize(Arrays.asList("a", "b", "c", "d")).first();
System.out.println(first);
sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("FirstTest")
val sc = new SparkContext(conf)

//first: 取出RDD中第一个元素
val first: Int = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).first()
println(first)
sc.stop()

2.8 collect

将RDD中所有数据回收到Driver端。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("CollectTest");
JavaSparkContext sc = new JavaSparkContext(conf);

//collect:将RDD中的所有元素收集到Driver端
List<String> collect = sc.parallelize(Arrays.asList("a", "b", "c", "d")).collect();
for (String s : collect) {
    System.out.println(s);
}

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("CollectTest")
val sc = new SparkContext(conf)
//collect: 以数组的形式返回RDD中的所有元素
val nums: Array[String] = sc.parallelize(Array("a","b","c","d")).collect()
println(nums.mkString(","))
sc.stop()

2.9 collectAsMap

对K,V格式的RDD数据回收为Map<K,V>对象到Driver端。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("CollectTest");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaPairRDD<String, Integer> rdd = sc.parallelizePairs(Arrays.asList(
        new Tuple2<String, Integer>("a", 1),
        new Tuple2<String, Integer>("b", 2),
        new Tuple2<String, Integer>("c", 3)
));

//collectAsMap:将RDD中的元素转换为Map
Map<String, Integer> map = rdd.collectAsMap();
//遍历Map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " " + entry.getValue());
}

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("CollectAsMapTest")
val sc = new SparkContext(conf)

//collectAsMap: 将RDD中的元素转换为Map
val rdd: RDD[(String, Int)] = sc.parallelize(List(("a", 1), ("b", 2), ("c", 3), ("d", 4)))
val map: collection.Map[String, Int] = rdd.collectAsMap()
map.foreach(println)
sc.stop()

2.10 saveAsTextFile

将RDD数据以文本的形式保存在指定的目录中,该方法会将每个分区的数据保存为单独的文件,文件名通常以 part-00000、part-00001 等形式命名,存储在指定的目录下,如果指定的目录已存在,saveAsTextFile 方法会抛出异常。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("takeSampleTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.parallelize(Arrays.asList(
        "a", "b", "c", "d", "e", "f","g","h","i","j","k"),3);

//saveAsTextFile: 将RDD中的元素保存为文本文件,每个分区保存为一个文件
rdd.saveAsTextFile("./data/result");

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("ForeachPartitionTest")
val sc = new SparkContext(conf)

val rdd: RDD[String] = sc.parallelize(List("a", "b", "c", "d", "e", "f","g", "h", "i", "j", "k"),4)

//saveAsTextFile: 将RDD中的元素保存为文本文件,每个分区保存为一个文件
rdd.saveAsTextFile("./data/result")

sc.stop()

2.11 top(num)

对RDD中的所有元素进行由大到小降序排序,获取前num个元素返回集合到Driver端。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("TopTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.parallelize(Arrays.asList(
        "a", "b", "c", "d", "e", "f","h","i","j","k"));

//top: 从RDD中取前num个元素
List<String> top = rdd.top(3);
for (String s : top) {
    System.out.println(s);
}

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("TopTest")
val sc = new SparkContext(conf)

//top: 取出RDD中前n个元素
val rdd:RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),4)
val ints: Array[Int] = rdd.top(3)
ints.foreach(println)

sc.stop()

2.12 takeOrdered(num)

对RDD中的所有元素进行由小到大的排序,获取前num个元素返回。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("takeOrderedTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.parallelize(Arrays.asList(
        "k", "b", "c", "d", "e", "f","h","i","j","a"),3);

List<String> strings = rdd.takeOrdered(3);
for (String str : strings) {
    System.out.println(str);
}

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("TakeOrderedTest")
val sc = new SparkContext(conf)

//takeOrdered: 取出RDD中前n个元素,并且按照指定的排序规则排序
val rdd: RDD[Int] = sc.parallelize(Array(10, 2, 3, 4, 5, 6, 7, 8, 9))
val nums: Array[Int] = rdd.takeOrdered(3)
println(nums.mkString(","))

sc.stop()

2.13 countByKey

作用到K,V格式的RDD上,根据Key计数相同Key出现的次数,结果会回收到Driver端。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("CountByKeyTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaPairRDD<String, Integer> rdd = sc.parallelizePairs(Arrays.asList(
        new Tuple2<>("a", 1),
        new Tuple2<>("b", 2),
        new Tuple2<>("c", 3),
        new Tuple2<>("a", 4),
        new Tuple2<>("b", 5),
        new Tuple2<>("a", 6),
        new Tuple2<>("c", 7)

));

//countByKey:统计每种key的个数
Map<String, Long> map = rdd.countByKey();
map.forEach((k,v)-> System.out.println(k+":"+v));

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("CountByKeyTest")
val sc = new SparkContext(conf)
val rdd: RDD[(String, Int)] = sc.parallelize(List(
  ("a", 1),
  ("b", 2),
  ("c", 3),
  ("a", 4),
  ("b", 5),
  ("a", 6),
  ("c", 7)
))

val result: collection.Map[String, Long] = rdd.countByKey()
result.foreach(println)

sc.stop()

2.14 countByValue

根据RDD数据集每个元素相同的内容来计数,返回相同元素对应的条数,作用到KV或者非KV格式RDD上都可以,结果也会回收到Driver端。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("CountByValueTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.parallelize(Arrays.asList("a", "b", "c", "a", "b", "c", "a", "b", "c"));
//countByValue:统计每种value的个数
Map<String, Long> map = rdd.countByValue();
map.forEach((k,v)-> System.out.println(k+":"+v));
sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("CountByValueTest")
val sc = new SparkContext(conf)
val rdd: RDD[String] = sc.parallelize(List("a", "b", "c", "a", "b", "a", "c"))

val map: collection.Map[String, Long] = rdd.countByValue()
map.foreach(println)
sc.stop()

2.15 fold

fold用于对RDD中的元素进行聚合操作,最终返回一个结果。类似reduce算子,但与reduce不同的是其可以对每个分区中的数据提供一个初始值,让分区中的数据与该初始值进行聚合,最终该初始值还会与各个分区的结果再次聚合。

fold的函数签名如下:

复制代码
def fold(zeroValue: T)(op: (T, T) => T): T
  • zeroValue:聚合操作的初始值,类型为 T。
  • op:用于合并元素的二元操作函数。

fold的工作原理:在每个分区内,fold 使用初始值 zeroValue 和二元操作函数 op,将该分区内的所有元素进行聚合。在所有分区内的聚合完成后,fold 将各分区的结果与初始值 zeroValue 一起,使用相同的二元操作函数 op 进行全局聚合,得到最终结果。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("FoldTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.parallelize(Arrays.asList("a","b","c","d","e","f"), 3);
rdd.mapPartitionsWithIndex(new Function2<Integer, Iterator<String>, Iterator<String>>() {
    @Override
    public Iterator<String> call(Integer index, Iterator<String> iter) throws Exception {
        ArrayList<String> list = new ArrayList<>();
        while (iter.hasNext()) {
            String next = iter.next();
            list.add("rdd partition index: " + index + " current value: " + next);
        }
        return list.iterator();
    }
},true).foreach(x-> System.out.println(x));

/**
 * 0号分区:a b
 * 1号分区:c d
 * 2号分区:e f
 *
 * 0号分区:hello~a~b
 * 1号分区:hello~c~d
 * 2号分区:hello~e~f
 *
 * 最终结果:hello~hello~a~b~hello~c~d~hello~e~f
 */
String str = rdd.fold("hello", new Function2<String, String, String>() {
    @Override
    public String call(String v1, String v2) throws Exception {
        return v1 + "~" + v2;
    }
});

System.out.println(str);
sc.stop();

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("FoldTest")
val sc = new SparkContext(conf)

val rdd: RDD[String] = sc.parallelize(List("a", "b", "c", "d", "e", "f"), 3)
rdd.mapPartitionsWithIndex((index, iter) => {
  val list = new ListBuffer[String]()
  while (iter.hasNext) {
    list.append(s"rdd partition index: $index ,current value: ${iter.next()}")
  }
  list.iterator
}).foreach(println)

/**
 * 0号分区:a b
 * 1号分区:c d
 * 2号分区:e f
 * map端聚合:
 * 0号分区:hello~a~b
 * 1号分区:hello~c~d
 * 2号分区:hello~e~f
 *
 * 最终结果:hello~hello~a~b~hello~c~d~hello~e~f
 */
val result: String = rdd.fold("hello")((v1, v2) => {
  v1 + "~" + v2
})

println(result)

sc.stop()

2.16 aggregate

aggregate用于对RDD中的元素进行聚合操作,最终返回一个结果。与 fold 和 reduce 等算子不同,aggregate 允许用户分别定义分区内和分区间的聚合函数,提供了更大的灵活性。

aggregate函数签名如下:

复制代码
def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
  • zeroValue:聚合操作的初始值,类型为 U。
  • seqOp:分区内的聚合函数,用于将分区内的元素与累加器进行合并,类型为 (U, T) => U。
  • combOp:分区间的聚合函数,用于将不同分区的累加器结果进行合并,类型为 (U, U) => U

aggregate工作原理:在每个分区内,使用初始值 zeroValue 和函数 seqOp,将该分区内的所有元素进行聚合。在所有分区内的聚合完成后,使用初始值 zeroValue 和函数 combOp,将各分区的结果进行全局聚合,得到最终结果。

Java代码:

复制代码
SparkConf conf = new SparkConf().setMaster("local").setAppName("AggregateTest");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.parallelize(Arrays.asList("a","b","c","d","e","f"), 3);
rdd.mapPartitionsWithIndex(new Function2<Integer, Iterator<String>, Iterator<String>>() {
    @Override
    public Iterator<String> call(Integer index, Iterator<String> iter) throws Exception {
        ArrayList<String> list = new ArrayList<>();
        while (iter.hasNext()) {
            String next = iter.next();
            list.add("rdd partition index: " + index + " current value: " + next);
        }
        return list.iterator();
    }
},true).foreach(x-> System.out.println(x));

/**
 * 0号分区:a b
 * 1号分区:c d
 * 2号分区:e f
 *
 * map端聚合:
 * 0号分区:hello~a~b
 * 1号分区:hello~c~d
 * 2号分区:hello~e~f
 *
 * 最终结果:hello@hello~a~b@hello~c~d@hello~e~f
 */
String result = rdd.aggregate("hello", new Function2<String, String, String>() {

    @Override
    public String call(String s1, String s2) throws Exception {
        return s1 + "~" + s2;
    }
}, new Function2<String, String, String>() {
    @Override
    public String call(String s1, String s2) throws Exception {
        return s1 + "@" + s2;
    }
});

System.out.println(result);
sc.stop();

Scala代码:

复制代码
val conf = new SparkConf().setMaster("local").setAppName("FoldTest")
val sc = new SparkContext(conf)

val rdd: RDD[String] = sc.parallelize(List("a", "b", "c", "d", "e", "f"), 3)
rdd.mapPartitionsWithIndex((index, iter) => {
  val list = new ListBuffer[String]()
  while (iter.hasNext) {
    list.append(s"rdd partition index: $index ,current value: ${iter.next()}")
  }
  list.iterator
}).foreach(println)

val result: String = rdd.aggregate("hello")(
  (v1, v2) => {
    v1 + "~" + v2
  },
  (v1, v2) => {
    v1 + "@" + v2
  }
)

println(result)
sc.stop()

3. Persistence-持久化算子

在 Spark 应用中,如果一个 RDD 会被多次使用,为避免重复计算,可以将该 RDD 缓存到内存或磁盘中,持久化算子能显著提升性能,尤其是当 RDD 的计算代价较高时。

例如,如下示例操作中,得到mysql_errors结果时会通过count算子触发errors RDD执行,溯源读取一遍文件,得到http_errors结果时会通过count算子触发errors RDD 再次执行,会再次读取一遍文件。可见,errors后续使用多次,每次都要重新读取一遍文件,重复计算,效率较慢。

复制代码
lines = sc.textFile(...)
errors = lines.filter(_.startWith("Error"))
mysql_errors = errors.filter(_.contain("MySQL")).count
http_errors = errors.filter(_.contain("Http")).count

以上这种某个RDD多次使用,为了避免重复计算,加快计算效率,可以使用Spark中的持久化算子对RDD进行持久化操作。

持久化算子有三种:cache、persist、checkpoint,以上算子都可以将RDD持久化,持久化的单位是partition。cache和persist都是懒执行的,必须有一个action类算子触发执行。checkpoint算子不仅能将RDD持久化到磁盘,还能切断RDD之间的依赖关系。下面分别介绍。

3.1 cache

cache默认将RDD的数据暂存到内存中,以便后续操作直接使用,避免重复计算。使用cache注意如下两点:

  • cache是懒执行的,需要Action算子触发执行。
  • cache()=persist()=persist(StorageLevel.Memory_Only)

如下示例中,读取的数据文件中大约有500多万数据,进行行数统计,测试使用cache和不使用cache的效率差别。

Java代码:

复制代码
SparkConf conf = new SparkConf().setAppName("CacheTest").setMaster("local");
JavaSparkContext sc = new JavaSparkContext(conf);

JavaRDD<String> rdd = sc.textFile("./data/persistData.txt");

rdd.cache();

// 第一次计算,从磁盘读取数据文件
long startTime1 = System.currentTimeMillis();
long count1 = rdd.count();
long endTime1 = System.currentTimeMillis();
System.out.println("第一次计算,从磁盘读取数据文件条数:"+count1+",耗时:" + (endTime1 - startTime1) + "ms");

// 第二次计算,从内存读取数据
long startTime2 = System.currentTimeMillis();
long count2 = rdd.count();
long endTime2 = System.currentTimeMillis();
System.out.println("第二次计算,从内存读取数据条数:"+count2+",耗时:" + (endTime2 - startTime2) + "ms");

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("CacheTest")
val sc = new SparkContext(conf)

val rdd: RDD[String] = sc.textFile("./data/persistData.txt")

rdd.cache()

// 第一次计算,从磁盘读取数据文件
val startTime1: Long = System.currentTimeMillis()
val count1: Long = rdd.count()
val endTime1: Long = System.currentTimeMillis()
println(s"第一次计算,从磁盘读取数据文件条数:$count1,耗时:${endTime1 - startTime1} ms")

// 第二次计算,从内存读取数据
val startTime2: Long = System.currentTimeMillis()
val count2: Long = rdd.count()
val endTime2: Long = System.currentTimeMillis()
println(s"第二次计算,从内存读取数据条数:$count2,耗时:${endTime2 - startTime2} ms")

sc.stop()

3.2 persist

cache允许用户将数据存储在内存中,而persist可以让用户指定数据不同的存储级别,以满足不同的存储和性能需求。实际上cache()=persist()=persist(StorageLevel.MEMORY_ONLY),使用方式上,persist与cache类似,如果一个持久化的数据集不再需要,可以使用unpersist()方法释放存储资源。使用方式如下:

复制代码
#Java API中调用persist必须传入StorageLevel
rdd.persist(StorageLevel.MEMORY_ONLY());
rdd.unpersist();

#Scala API可以直接调用persist,persist()=persist(StorageLevel.MEMORY_ONLY)
rdd.persist()
rdd.unpersist();

StorageLevel提供了多种存储级别,如下:

  • None:不进行任何持久化,相当于不调用 persist()。
  • DISK_ONLY:数据仅存储在磁盘上,不占用内存,且是序列化存储的。
  • DISK_ONLY_2:与DISK_ONLY 类似,但数据会被复制 2 份,存储在多个节点上,提高容错性。
  • DISK_ONLY_3:与DISK_ONLY 类似,但数据会被复制 3 份,存储在多个节点上,提高容错性。
  • MEMORY_ONLY:数据仅存储在 JVM 内存中(以分区为单位存储),不会存储到磁盘。如果内存不足,无法存储的数据会被丢弃,并在需要时重新计算。
  • MEMORY_ONLY_2:与 MEMORY_ONLY 类似,但数据会被复制两份,提高容错能力。
  • MEMORY_ONLY_SER:数据存储在内存中,但以序列化形式存储,以减少内存占用。但每次读取时需要反序列化,会增加 CPU 计算开销。
  • MEMORY_ONLY_SER_2:与MEMORY_ONLY_SER 类似,但数据会被复制两份,提升可靠性。
  • MEMORY_AND_DISK:数据优先存储在内存中,如果内存不足,则会存储到磁盘,而不会丢弃数据。
  • MEMORY_AND_DISK_2:与MEMORY_AND_DISK 类似,但数据会被复制两份。
  • MEMORY_AND_DISK_SER:数据优先存储在内存中,并以序列化形式存储。如果内存不足,则存储到磁盘。
  • MEMORY_AND_DISK_SER_2:与MEMORY_AND_DISK_SER 类似,但数据会被复制2份,提高容错性。
  • OFF_HEAP:数据存储在 堆外内存(Off-Heap Memory) 中,而不是 JVM 堆内存。

注意:关于堆外内存介绍可参考后续小节。

cache与persist区别:

  1. cache()默认将数据存储在内存中,而persist()允许用户根据需求指定不同的存储级别,两者都是懒执行,必须有一个action算子触发执行。
  2. cache和persist算子的返回值可以赋值给一个变量,在其他job中直接使用这个变量就是使用持久化的数据了,持久化的单位是partition。
  3. cache和persist算子后不能立即紧跟action算子,因为rdd.cache().count() 返回的不是持久化的RDD,而是一个数值。
  4. RDD持久化数据需要是不同Job中重复使用某个RDD,可以对这个RDD进行持久化,如果只有一个Job,对RDD没必要进行cache或者persist。
  5. 适时使用unpersist释放不再需要的缓存数据,以优化资源利用。
  6. cache和persist算子持久化的数据当applilcation执行完成之后会被清除。

3.3 checkpoint

Spark中,Checkpoint机制用于将RDD的数据保存到可靠的存储系统(如HDFS),并切断RDD之间的依赖关系。这样在节点故障或Application重启时,可以基于Checkpoint保存的状态恢复计算,避免从头开始计算。

如下图所示,rdd1到rdd8整个血缘关系中,如果由于意外原因需要重新计算RDD8数据,此刻只能基于RDD1按照血缘关系重新依次进行数据计算,如果此刻对RDD6进行了Checkpoint,我们就可以直接基于保存在磁盘上RDD6的数据计算,减少重新计算的开销。

heckpoint使用方式:

使用checkpoint时,首先需要通过SparkContext指定checkpoint的存储路径(如HDFS路径),然后再对需要checkpoint的RDD调用checkpoint方法即可。

复制代码
#1.开启checkpoint
sc.setCheckpointDir("your_dir");

#2.对rdd进行checkpoint
rdd.checkpoint();

#3.RDD Action算子触发执行
rdd.count

Java代码:

复制代码
SparkConf conf = new SparkConf().setAppName("checkpointTest").setMaster("local");
JavaSparkContext sc = new JavaSparkContext(conf);

//1. 开启 checkpoint
sc.setCheckpointDir("./data/checkpoint");

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

//2. 对 RDD 进行 checkpoint
rdd.checkpoint();

//要触发 checkpoint 操作,必须先触发一个行动操作
rdd.count();

sc.stop();

Scala代码:

复制代码
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("checkpoint")
val sc = new SparkContext(conf)

//1.设置checkpoint目录
sc.setCheckpointDir("./data/checkpoint")

val rdd: RDD[String] = sc.textFile("./data/data.txt")

//2.对RDD进行checkpoint操作
rdd.checkpoint()

//需要执行action操作
rdd.count

sc.stop()

checkpoint执行原理:

  1. 当RDD的job执行完毕后,会从finalRDD从后往前回溯。

  2. 当回溯到某一个RDD调用了checkpoint方法,会对当前的RDD做一个标记。

  3. Spark框架会自动启动一个新的job,重新计算这个RDD的数据,将数据持久化到HDFS上。

为了避免在Checkpoint时重复计算,建议在对RDD执行Checkpoint之前,先对其进行缓存(Cache)操作。这样,Checkpoint操作可以直接从内存中读取数据,避免重复计算。

checkpoint与persist区别:

  1. persist 和 checkpoint 都可以对 RDD 进行持久化,persist 主要用于在不同 Job 中多次复用同一数据集,提高计算效率;而 checkpoint 除此外还会切断 RDD 之间的依赖关系,通常用于程序故障恢复,避免 RDD 血缘关系过长导致的恢复困难。

  2. checkpoint 需要指定额外的存储目录,数据由外部存储系统(如 HDFS)管理,不受 Spark 运行时控制,即使 Spark Application 结束,数据仍然存在;而 persist() 由 Spark 框架管理,数据存储在内存或磁盘中,当 Spark Application 结束后,持久化的数据会自动清空。

  3. checkpoint除了持久化 RDD 之外,还可以用于保存状态数据,在长时间运行的流式计算任务(如 Spark Streaming)中很重要,可以防止状态数据因应用重启或故障而丢失。

相关推荐
CICI131414132 小时前
藦卡机器人:让焊接更洁净、更精准、更智能
大数据·人工智能
一直在追2 小时前
别再用 Java 多线程思维写 Python 了!Asyncio 才是 LLM 高并发的王道
大数据
大厂技术总监下海2 小时前
来自美团生产环境的实战派:开源CAT监控,如何保障超大规模分布式系统可观测性?
分布式·开源
短视频矩阵源码定制2 小时前
矩阵系统源头厂家
大数据·人工智能·矩阵
Linux Huang2 小时前
spring注册组件/服务无效,问题排查
大数据·服务器·数据库·spring
天竺鼠不该去劝架3 小时前
传统财务管理瓶颈:财务机器人如何提升效率
大数据·数据库·人工智能
WZGL12303 小时前
“近邻+数智”:解码智慧养老的温情答案
大数据·人工智能·科技·生活·智能家居
A3608_(韦煜粮)3 小时前
从数据沼泽到智慧引擎:现代大数据分析与应用架构全景解密
大数据·数据分析·数据治理·实时计算·数据架构·数据网格·数据湖仓
Dxy12393102163 小时前
如何基于 Elasticsearch 构建亿级相似图片搜索系统
大数据·elasticsearch·搜索引擎