在 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区别:
- cache()默认将数据存储在内存中,而persist()允许用户根据需求指定不同的存储级别,两者都是懒执行,必须有一个action算子触发执行。
- cache和persist算子的返回值可以赋值给一个变量,在其他job中直接使用这个变量就是使用持久化的数据了,持久化的单位是partition。
- cache和persist算子后不能立即紧跟action算子,因为rdd.cache().count() 返回的不是持久化的RDD,而是一个数值。
- RDD持久化数据需要是不同Job中重复使用某个RDD,可以对这个RDD进行持久化,如果只有一个Job,对RDD没必要进行cache或者persist。
- 适时使用unpersist释放不再需要的缓存数据,以优化资源利用。
- 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执行原理:
-
当RDD的job执行完毕后,会从finalRDD从后往前回溯。
-
当回溯到某一个RDD调用了checkpoint方法,会对当前的RDD做一个标记。
-
Spark框架会自动启动一个新的job,重新计算这个RDD的数据,将数据持久化到HDFS上。
为了避免在Checkpoint时重复计算,建议在对RDD执行Checkpoint之前,先对其进行缓存(Cache)操作。这样,Checkpoint操作可以直接从内存中读取数据,避免重复计算。
checkpoint与persist区别:
-
persist 和 checkpoint 都可以对 RDD 进行持久化,persist 主要用于在不同 Job 中多次复用同一数据集,提高计算效率;而 checkpoint 除此外还会切断 RDD 之间的依赖关系,通常用于程序故障恢复,避免 RDD 血缘关系过长导致的恢复困难。
-
checkpoint 需要指定额外的存储目录,数据由外部存储系统(如 HDFS)管理,不受 Spark 运行时控制,即使 Spark Application 结束,数据仍然存在;而 persist() 由 Spark 框架管理,数据存储在内存或磁盘中,当 Spark Application 结束后,持久化的数据会自动清空。
-
checkpoint除了持久化 RDD 之外,还可以用于保存状态数据,在长时间运行的流式计算任务(如 Spark Streaming)中很重要,可以防止状态数据因应用重启或故障而丢失。