Spark-RDD和共享变量

news/2024/9/13 22:37:23/ 标签: spark, 大数据, 分布式

概览

        每个Spark应用程序都由一个driver program  组成,该驱动程序运行我们编写的main函数,并在集群上执行各种 并行 操作。Spark提供的主要抽象是一个 弹性分布式数据集(RDD),它是一个跨集群节点分区的元素集合,可以并行操作。RDD是通过从Hadoop文件系统(或任何其他Hadoop支持的文件系统)中的文件或 driver program 中现有的Scala集合开始并转换它来创建的。用户还可以要求Spark在内存中 持久化RDD,允许它在并行操作中有效地重用。最后,RDD会自动从节点故障中恢复。

        Spark中的第二个抽象是可以在并行操作中使用的 共享变量 。默认情况下,当Spark在不同节点上作为一组任务并行运行函数时,它会将函数中使用的每个变量的副本发送给每个任务。有时,一个变量需要在任务之间共享,或者在任务和 driver program 之间共享。Spark支持两种类型的共享变量:广播变量,可用于在所有节点上的内存中缓存一个值,累加器,是仅可“添加”的变量,例如计数器和求和。

一、RDD

        弹性分布式数据集(Resilient Distributed Dataset) 简称 RDD,是一个可以并行操作的元素的容错集合。RDD有五大特性:

        1、有一系列分区构成(分区即切片)

        2、每个函数都作用在每个分区上(并行计算)

        3、和其他RDD是有依赖关系的

        4、分区类算子只能作用在kv型RDD上

        5、Spark会为每个分区计算最佳的位置,并把函数移动过去(计算向数据移动)

        RDD支持两种类型的操作:Transformations 和 Actions ,其中Transformations算子有 lazy特性,只要需要将结果返回给驱动程序时,才会触发计算。默认每个转换后的RDD都可以重新计算,也可以使用persist或cache方法将RDD持久化。这样,当后面的算子用这个RDD时就不用重新计算了。

1、Transformations 算子

map(func)

解释:返回一个新的分布式数据集,该数据集是通过函数func传递源的每个元素而形成的

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)

val mapRdd = sourceRdd.map(_+10)

mapRdd.foreach(println)

结果:

11
12
13
14
15
16
17
18
19
20        


filter(func)

解释:返回一个新的数据集,该数据集是通过选择func在其上返回true的源的那些元素而形成的。

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)

val filterRdd = sourceRdd.filter(_>5)

filterRdd.foreach(println)

结果:

6
7
8
9
10


flatMap(func)

解释:类似于map,但每个输入项都可以映射到0个或多个输出项(因此func应该返回一个Seq而不是单个项)

val sourceRdd2 = sc.parallelize(List("java scala js","c++ c python","java vba python"))
val flatMapRdd = sourceRdd2.flatMap(_.split(" "))
flatMapRdd.foreach(println)        

结果:

java
scala
js
c++
c
python
java
vba
python


mapPartitions(func)

解释:类似于map,但在RDD的每个分区(块)上单独运行,因此在T类型的RDD上运行时,func必须是Iterator<T>=>Iterator<U>类型。

mapPartitionsWithIndex(func)

解释:类似于map分区,但也为func提供了一个表示分区索引的整数值,因此func必须是类型(Int, Iterator<T>)=>Iterator<U>当在T类型的RDD上运行时。

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
//打印每个分区的数据
sourceRdd.mapPartitionsWithIndex((index,partition)=>{val sourceData = partition.mkString(",")println(index +"-"+sourceData)partition
}).collect()
println("------------------------")
val mapPartitionsRDD = sourceRdd.mapPartitions(iter => {// 在这个例子中,我们计算每个分区的总和val sum = iter.foldLeft(0)(_ + _)Iterator(sum) // 返回一个包含分区总和的新迭代器
})
mapPartitionsRDD.foreach(println)

结果:

0-1,2,3
1-4,5,6
2-7,8,9,10
------------------------
6
15
34


sample(withReplacement, fraction, seed)

解释:使用给定的随机数生成器种子对数据的一小部分进行采样,无论是否进行替换。

        所谓抽样那么返回的数据占比就没有那么准确

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
/ 进行随机采样,采样比例为0.5,有放回的抽样
val sampleRdd = sourceRdd.sample(true, 0.5)
sampleRdd.foreach(println)
println("-------------------------------")
// 进行随机采样,采样比例为0.5,无放回的抽样
val sampleRdd2 = sourceRdd.sample(false, 0.5)
sampleRdd2.foreach(println)

 结果:

1
2
5
7
7
9
-------------------------------
3
4
5
6
7
9


union(otherDataset)

解释:返回一个新数据集,其中包含源数据集中元素和参数的联合

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
val sourceRdd2 = sc.parallelize(List(1000,2000,9000))
val unionRdd = sourceRdd.union(sourceRdd2)
unionRdd.foreach(println)        

结果:

1
2
3
4
5
6
7
8
9
10
1000
2000
9000


intersection(otherDataset)

解释:返回一个新RDD,其中包含源数据集中元素和参数的交集。

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
val sourceRdd2 = sc.parallelize(List(3,5,6,1000,2000,9000))
val intersectionRdd = sourceRdd.intersection(sourceRdd2)
intersectionRdd.foreach(println)

结果:

6
3
5


distinct([numTasks]))

解释:返回一个包含源数据集不同元素的新数据集

val sourceRdd3 = sc.parallelize(List(1,2,3,3,4,4,5,6,6))
val distinctRdd = sourceRdd3.distinct()
distinctRdd.foreach(println)

结果:(无序)

4
1
6
3
5
2


groupByKey([numTasks])

解释:当在(K, V)对的数据集上调用时,返回(K,Iterable<V>)对的数据集

注意:如果您正在分组以执行聚合(例如总和或 平均)在每个键上,使用reduceByKeyaggregateByKey会产生更好的结果 性能。

注意:默认情况下,输出中的并行级别取决于父RDD的分区数。 您可以传递一个可选的numTasks参数来设置不同数量的任务。

val sourceRdd4 = sc.parallelize(Seq(
  ("java", 1),
  ("java",3),
  ("java",18),
  ("c++", 1),
  ("js", 1),
  ("R",1),
  ("python",3),
  ("python",6),
  ("scala",2),
  ("scala", 1),
  ("scala",21)), 2)
val groupedRDD = sourceRdd4.groupByKey()
groupedRDD.foreach(println)

结果:

(scala,CompactBuffer(1, 2, 21))
(R,CompactBuffer(1))
(python,CompactBuffer(3, 6))
(java,CompactBuffer(1, 3, 18))
(c++,CompactBuffer(1))
(js,CompactBuffer(1))


reduceByKey(func, [numTasks])

解释:当在(K, V)对的数据集上调用时,返回(K,V)对的数据集,其中每个键的值使用给定的duce函数func聚合,该函数的类型必须是(V,V)=>V。与groupByKey中一样,可以通过可选的第二个参数配置Reduce任务的数量。

val sourceRdd4 = sc.parallelize(Seq(
  ("java", 1),
  ("java",3),
  ("java",18),
  ("c++", 1),
  ("js", 1),
  ("R",1),
  ("python",3),
  ("python",6),
  ("scala",2),
  ("scala", 1),
  ("scala",21)), 2)
val reduceByKeyRDD = sourceRdd4.reduceByKey(_+_)
reduceByKeyRDD.foreach(println)

结果:

(scala,24)
(R,1)
(python,9)
(java,22)
(c++,1)
(js,1)


aggregateByKey(zeroValue)(seqOp, combOp, [numTasks])

解释:当在(K,V)对的数据集上调用时,返回(K,U)对的数据集,其中每个键的值使用给定的组合函数和中性“零”值聚合。允许不同于输入值类型的聚合值类型,同时避免不必要的分配。与groupByKey中一样,可以通过可选的第二个参数来配置Reduce任务的数量。

val sourceRdd4 = sc.parallelize(Seq(
  ("java", 1),
  ("java",3),
  ("java",18),
  ("c++", 1),
  ("js", 1),
  ("R",1),
  ("python",3),
  ("python",6),
  ("scala",2),
  ("scala", 1),
  ("scala",21)), 2)
val aggregateRdd = sourceRdd4.aggregateByKey((0, 0))(//初始值
      (acc, num) => (acc._1 + num, acc._2 + 1), // 部分聚合
      (acc1, acc2) => (acc1._1 + acc2._1, acc1._2 + acc2._2) // 合并聚合结果
    )
aggregateRdd.foreach(println)
 

结果:

(scala,(24,3))
(R,(1,1))
(python,(9,2))
(java,(22,3))
(c++,(1,1))
(js,(1,1))


sortByKey([ascending], [numTasks])

解释:当在K实现Order的(K, V)对的数据集上调用时,返回(K,V)对的数据集,这些数据集按键按升序或降序排序,如布尔ascending参数中指定的那样。

val sourceRdd5 = sc.parallelize(Seq(("java", 96),("c++", 94),("js", 85),("R",88),("python",96),("scala",91)))
//使用 sortByKey 进行排序,默认为升序
val sortedAscRdd = sourceRdd5.sortByKey()
//降序排序
val sortedDescRdd = sourceRdd5.sortByKey(false)
sortedAscRdd.foreach(println)
println("-------------------------------")
sortedDescRdd.foreach(println)
println("------------将key和value调换-------------------")
val sortedAscRdd2 = sourceRdd5.map(x=>{(x._2,x._1)}).sortByKey()
val sortedDescRdd2 = sourceRdd5.map(x=>{(x._2,x._1)}).sortByKey(false)
sortedAscRdd2.foreach(println)
println("-------------------------------")
sortedDescRdd2.foreach(println)

 结果:

(R,88)
(c++,94)
(java,96)
(js,85)
(python,96)
(scala,91)
-------------------------------
(scala,91)
(python,96)
(js,85)
(java,96)
(c++,94)
(R,88)
------------将key和value调换-------------------
(85,js)
(88,R)
(91,scala)
(94,c++)
(96,java)
(96,python)
-------------------------------
(96,java)
(96,python)
(94,c++)
(91,scala)
(88,R)
(85,js)
 


join(otherDataset, [numTasks])

解释:当在(K,V)和(K,W)类型的数据集上调用时,返回(K,(V,W))对的数据集,其中每个键的所有元素对。 外连接通过leftOuterJoinrightOuterJoin和fullOuterJoinfullOuterJoin

val languageTimeRdd = sc.parallelize(List(("pascal",1970), ("c", 1972),("c++", 1983),("perl", 1987),("python", 1991),("java", 1995)))
val languageCreatorRdd2 = sc.parallelize(List(("java", "James Gosling"), ("python", "Guido van Rossum"), ("perl", "Larry Wall"), ("ruby", "Yukihiro Matsumoto")))

//inner join
println("----------inner join-------------")
val innerJoinResult = languageTimeRdd.join(languageCreatorRdd2)
innerJoinResult.foreach(println)
//left join
println("----------left join-------------")
val leftJoinResult = languageTimeRdd.leftOuterJoin(languageCreatorRdd2)
leftJoinResult.foreach(println)
//right join
println("----------right join-------------")
val rightJoinResult = languageTimeRdd.rightOuterJoin(languageCreatorRdd2)
rightJoinResult.foreach(println)
//full outer join
println("----------full outer join-------------")
val fullJoinResult = languageTimeRdd.fullOuterJoin(languageCreatorRdd2)
fullJoinResult.foreach(println)

结果:

----------inner join-------------
(python,(1991,Guido van Rossum))
(java,(1995,James Gosling))
(perl,(1987,Larry Wall))
----------left join-------------
(c++,(1983,None))
(python,(1991,Some(Guido van Rossum)))
(pascal,(1970,None))
(java,(1995,Some(James Gosling)))
(perl,(1987,Some(Larry Wall)))
(c,(1972,None))
----------right join-------------
(python,(Some(1991),Guido van Rossum))
(java,(Some(1995),James Gosling))
(perl,(Some(1987),Larry Wall))
(ruby,(None,Yukihiro Matsumoto))
----------full outer join-------------
(c++,(Some(1983),None))
(python,(Some(1991),Some(Guido van Rossum)))
(pascal,(Some(1970),None))
(java,(Some(1995),Some(James Gosling)))
(perl,(Some(1987),Some(Larry Wall)))
(ruby,(None,Some(Yukihiro Matsumoto)))
(c,(Some(1972),None))


cogroup(otherDataset, [numTasks])

解释:当在(K, V)和(K,W)类型的数据集上调用时,返回(K,(Iterable<V>,Iterable<W>))元组的数据集。此操作也称为groupWith.

val languageTimeRdd = sc.parallelize(List(("pascal",1970), ("c", 1972),("c++", 1983),("perl", 1987),("python", 1991),("java", 1995)))
val languageCreatorRdd2 = sc.parallelize(List(("java", "James Gosling"), ("python", "Guido van Rossum"), ("perl", "Larry Wall"), ("ruby", "Yukihiro Matsumoto")))
val cogroupRdd = languageTimeRdd.cogroup(languageCreatorRdd2)
cogroupRdd.foreach(println)

结果:

(c++,(CompactBuffer(1983),CompactBuffer()))
(python,(CompactBuffer(1991),CompactBuffer(Guido van Rossum)))
(pascal,(CompactBuffer(1970),CompactBuffer()))
(java,(CompactBuffer(1995),CompactBuffer(James Gosling)))
(perl,(CompactBuffer(1987),CompactBuffer(Larry Wall)))
(ruby,(CompactBuffer(),CompactBuffer(Yukihiro Matsumoto)))
(c,(CompactBuffer(1972),CompactBuffer()))


cartesian(otherDataset)

解释:当在T和U类型的数据集上调用时,返回(T,U)对(所有元素对)的数据集。

val languageTimeRdd = sc.parallelize(List(("pascal",1970), ("c", 1972),("c++", 1983),("perl", 1987),("python", 1991),("java", 1995)))
val languageCreatorRdd2 = sc.parallelize(List(("java", "James Gosling"), ("python", "Guido van Rossum"), ("perl", "Larry Wall"), ("ruby", "Yukihiro Matsumoto")))
val cartesianRdd = languageTimeRdd.cartesian(languageCreatorRdd2)
cartesianRdd.foreach(println)

结果:

((pascal,1970),(java,James Gosling))
((pascal,1970),(python,Guido van Rossum))
((pascal,1970),(perl,Larry Wall))
((pascal,1970),(ruby,Yukihiro Matsumoto))
((c,1972),(java,James Gosling))
((c,1972),(python,Guido van Rossum))
((c,1972),(perl,Larry Wall))
((c,1972),(ruby,Yukihiro Matsumoto))
((c++,1983),(java,James Gosling))
((c++,1983),(python,Guido van Rossum))
((c++,1983),(perl,Larry Wall))
((c++,1983),(ruby,Yukihiro Matsumoto))
((perl,1987),(java,James Gosling))
((perl,1987),(python,Guido van Rossum))
((perl,1987),(perl,Larry Wall))
((perl,1987),(ruby,Yukihiro Matsumoto))
((python,1991),(java,James Gosling))
((python,1991),(python,Guido van Rossum))
((python,1991),(perl,Larry Wall))
((python,1991),(ruby,Yukihiro Matsumoto))
((java,1995),(java,James Gosling))
((java,1995),(python,Guido van Rossum))
((java,1995),(perl,Larry Wall))
((java,1995),(ruby,Yukihiro Matsumoto))


pipe(command, [envVars])

解释:通过shell命令(例如Perl或bash脚本)管道RDD的每个分区。RDD元素被写入进程的标准输入,输出到其标准输出的行作为字符串的RDD返回。

因为需要用到脚本,所以必须要到linux环境中

我们执行spark-shell --master yarn 来测试该算子

val sourceRdd6 = sc.parallelize(List("24/07/08 16:43:39 INFO Executor:",
  "24/07/08 16:43:40 INFO Utils:",
  "24/07/08 16:43:36 INFO SecurityManager",
  "24/07/08 16:43:35 INFO SparkContext"))
val pipeRdd = sourceRdd6.pipe("cut -c 19-")
pipeRdd.collect().foreach(println)

结果:

INFO Executor:
INFO Utils:
INFO SecurityManager
INFO SparkContext

coalesce(numPartitions)

解释:将RDD中的分区数量减少到num分区。对于过滤大型数据集后更有效地运行操作很有用。

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
//分区变少 无shuffle
val coalesceRDD1 =  sourceRdd.coalesce(1,false)
//分区变少 有shuffle
val coalesceRDD2 =  sourceRdd.coalesce(1,true)
//分区变多 无shuffle
val coalesceRDD3 =  sourceRdd.coalesce(5,false)
//分区变多 有shuffle
val coalesceRDD4 =  sourceRdd.coalesce(5,true)
println(coalesceRDD1.getNumPartitions)
println(coalesceRDD2.getNumPartitions)
println(coalesceRDD3.getNumPartitions)
println(coalesceRDD4.getNumPartitions)

结果:

1
1
3
5


repartition(numPartitions)

解释:随机重新洗牌RDD中的数据以创建更多或更少的分区并在它们之间进行平衡。 这总是在网络上打乱所有数据。(内部调用的也是coalesce 默认是进行shuffle)

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
//分区变少
val repartitionRdd1 = sourceRdd.repartition(2)
//分区变多
val repartitionRdd2 = sourceRdd.repartition(6)
println(repartitionRdd1.getNumPartitions)
println(repartitionRdd2.getNumPartitions)

结果:

2
6


repartitionAndSortWithinPartitions(partitioner)

解释:根据给定的分区器重新分区RDD,并且在每个结果分区中, 按键对记录进行排序。这比调用repartition然后在其中排序更有效 每个分区,因为它可以将排序向下推到洗牌机械中。

repartitionAndSortWithinPartitions是Spark官网推荐的一个算子,官方建议,如果需要在repartition重分区之后,还要进行排序,建议直接使用repartitionAndSortWithinPartitions算子。因为该算子可以一边进行重分区的shuffle操作,一边进行排序。shuffle与sort两个操作同时进行,比先shuffle再sort来说,性能可能是要高的。

val sourceRdd4 = sc.parallelize(Seq(
  ("java", 1),
  ("java",3),
  ("java",18),
  ("c++", 1),
  ("js", 1),
  ("R",1),
  ("python",3),
  ("python",6),
  ("scala",2),
  ("scala", 1),
  ("scala",21)), 2)
println("-----------------------key是字符串--分区变多--------------------------")
val repartitionAndSortWithinPartitionsRdd1 = sourceRdd4.repartitionAndSortWithinPartitions(new MyPartitioner(4))
repartitionAndSortWithinPartitionsRdd1.foreach(println)
println("-----------------------key是字符串--分区变少--------------------------")
val repartitionAndSortWithinPartitionsRdd2 = sourceRdd4.repartitionAndSortWithinPartitions(new MyPartitioner(1))
repartitionAndSortWithinPartitionsRdd2.foreach(println)

println("-----------------------key是数字--分区变多--------------------------")
val repartitionAndSortWithinPartitionsRdd3 = sourceRdd4.map(x=>{(x._2,x._1)}).repartitionAndSortWithinPartitions(new MyPartitioner(4))
repartitionAndSortWithinPartitionsRdd3.foreach(println)
println("-----------------------key是数字--分区变少--------------------------")
val repartitionAndSortWithinPartitionsRdd4 = sourceRdd4.map(x=>{(x._2,x._1)}).repartitionAndSortWithinPartitions(new MyPartitioner(1))
repartitionAndSortWithinPartitionsRdd4.foreach(println)

class MyPartitioner(partitions: Int) extends Partitioner {require(partitions >= 0, s"Number of partitions ($partitions) cannot be negative.")override def numPartitions: Int = partitionsoverride def getPartition(key: Any): Int = {val index = Math.abs(key.hashCode()) % numPartitionsprintln("key:"+key+"---所属分区:"+index)index}
}

结果:

-----------------------key是字符串--分区变多--------------------------
key:java---所属分区:2
key:java---所属分区:2
key:java---所属分区:2
key:c++---所属分区:3
key:js---所属分区:1
key:R---所属分区:2
key:python---所属分区:0
key:python---所属分区:0
key:scala---所属分区:2
key:scala---所属分区:2
key:scala---所属分区:2
(python,3)
(python,6)
(js,1)
(R,1)
(java,1)
(java,3)
(java,18)
(scala,2)
(scala,1)
(scala,21)
(c++,1)
-----------------------key是字符串--分区变少--------------------------
key:java---所属分区:0
key:java---所属分区:0
key:java---所属分区:0
key:c++---所属分区:0
key:js---所属分区:0
key:R---所属分区:0
key:python---所属分区:0
key:python---所属分区:0
key:scala---所属分区:0
key:scala---所属分区:0
key:scala---所属分区:0
(R,1)
(c++,1)
(java,1)
(java,3)
(java,18)
(js,1)
(python,3)
(python,6)
(scala,2)
(scala,1)
(scala,21)
-----------------------key是数字--分区变多--------------------------
key:1---所属分区:1
key:3---所属分区:3
key:18---所属分区:2
key:1---所属分区:1
key:1---所属分区:1
key:1---所属分区:1
key:3---所属分区:3
key:6---所属分区:2
key:2---所属分区:2
key:1---所属分区:1
key:21---所属分区:1
(1,java)
(1,c++)
(1,js)
(1,R)
(1,scala)
(21,scala)
(2,scala)
(6,python)
(18,java)
(3,java)
(3,python)
-----------------------key是数字--分区变少--------------------------
key:1---所属分区:0
key:3---所属分区:0
key:18---所属分区:0
key:1---所属分区:0
key:1---所属分区:0
key:1---所属分区:0
key:3---所属分区:0
key:6---所属分区:0
key:2---所属分区:0
key:1---所属分区:0
key:21---所属分区:0
(1,java)
(1,c++)
(1,js)
(1,R)
(1,scala)
(2,scala)
(3,java)
(3,python)
(6,python)
(18,java)
(21,scala)

2、Actions 算子

reduce(func)

解释:使用函数func(它接受两个参数并返回一个)聚合数据集的元素。该函数应该是可交换和关联的,以便可以并行正确计算。

val sourceRdd4 = sc.parallelize(Seq(("java", 1),("java",3),("java",18),("c++", 1),("js", 1),("R",1),("python",3),("python",6),("scala",2),("scala", 1),("scala",21)), 2)

val reduceRdd = sourceRdd4.reduce((kv1,kv2)=>{
      var newKey:String = kv1._1
      val sumNum:Int = kv1._2+kv2._2
      if(!kv1._1.contains(kv2._1)){
        newKey = kv1._1 +"_"+kv2._1
      }
      (newKey,sumNum)
    })
println(reduceRdd._1+"----"+reduceRdd._2)

结果:

java_c++_js_R_python_scala----58


collect()

解释:在驱动程序中以数组的形式返回数据集的所有元素。这通常在返回足够小的数据子集的过滤器或其他操作之后很有用。

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
sourceRdd.collect().foreach(println)

结果:

1
2
3
4
5
6
7
8
9
10


count()

解释:返回数据集中元素的数量。
 

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
println(sourceRdd.count())

结果:

10


first()

解释:返回数据集的第一个元素(类似于take(1) )。

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
println(sourceRdd.first())

结果:

1


take(n)

解释:返回包含数据集前n个元素的数组

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)

sourceRdd.take(3).foreach(println)

结果:

1
2
3


takeSample(withReplacement, num, [seed])

解释:返回一个数组,其中包含数据集num元素的随机样本,有或没有替换,可选地预先指定随机数生成器种子。

val sourceRdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
sourceRdd.takeSample(false,5).foreach(println)
println("---------------")
sourceRdd.takeSample(true,5).foreach(println)

结果:

6
1
9
4
3
---------------
3
2
9
4
2


takeOrdered(n, [ordering])

解释:使用它们的自然顺序或自定义比较器返回RDD的前n个元素

val sourceRd8 = sc.parallelize(List(13,2,6,4,8,3,7,8,91,1),3)
sourceRd8.takeOrdered(5).foreach(println)

结果:

1
2
3
4
6


saveAsTextFile(path)

解释:将数据集的元素作为文本文件(或一组文本文件)写入本地文件系统、HDFS或任何其他Hadoop支持的文件的给定目录中系统。Spark将在每个元素上调用toString将其转换为文件中的一行文本。


saveAsSequenceFile(path)

解释:将数据集的元素写入本地文件系统、HDFS或任何其他Hadoop支持的文件系统的给定路径中的Hadoop Sequence File。这在实现Hadoop的Writable接口的键值对的RDD上可用。在Scala中,它也适用于隐式转换为Writable的类型(Spark包括Int、Double、String等基本类型的转换)。


saveAsObjectFile(path)

解释:使用Java序列化以简单格式写入数据集的元素,然后可以使用 SparkContext.objectFile()。



countByKey()

解释:仅在(K,V)类型的RDD上可用。返回(K,Int)对的哈希图以及每个键的计数。

val sourceRdd4 = sc.parallelize(Seq(
      ("java", 1),
      ("java",3),
      ("java",18),
      ("c++", 1),
      ("js", 1),
      ("R",1),
      ("python",3),
      ("python",6),
      ("scala",2),
      ("scala", 1),
      ("scala",21)), 2)
sourceRdd4.countByKey().foreach(println)

结果:

(java,3)
(c++,1)
(scala,3)
(python,2)
(js,1)
(R,1)


foreach(func)(上面已经演示过)

解释:对数据集的每个元素运行一个函数func。这通常用于副作用,例如更新累加器或与外部存储系统交互。

注意:在foreach()之外修改除Accumulator之外的变量可能会导致未定义的行为。有关详细信息,请参阅了解闭包。

3、Shuffle操作

Spark中的某些操作会触发称为shuffle的事件。shuffle是Spark重新分配数据的机制,以便在分区之间以不同的方式分组。这通常涉及跨 executors  和 节点 复制数据,因此shuffle是复杂且成本高昂的操作。

我们以 reduceByKey 为例说明:

reduceByKey 生成一个新的RDD,相同key的所有value会被组合到一起,这就需要将本分区中不同的key发送到不同的节点。这对于Spark来说是一个全向操作。它必须从所有分区中读取以查找所有key的所有value, 然后将跨分区的value聚集在一起以计算每个key的最终结果- 这就是Shuffle

尽管新洗牌数据的每个分区中的元素集是确定的,分区本身的顺序也是确定的,但这些元素的顺序不是。如果希望在洗牌后可预测地排序数据,那么可以使用:

mapPartitions(对每个分区进行排序,例如,使用.sorted) 

repartitionAndSortWithinPartitions(在重新分区的同时有效地对分区进行排序 

sortBy(创建全局排序的RDD

由于Shuffle涉及磁盘I/O、数据序列化和 网络输入输出。为了整理这些数据,Spark会产生map任务和reduce任务来实现(这里说的并不是MapReduce中的mapTask和reduceTask)

在内部,单个map任务的结果会保存在内存中,当数据不合适再内存中时会溢写到磁盘(会产生磁盘IO开销和垃圾回收机制)

Shuffle会在磁盘产生大量的中间文件,这些文件会一直保留,直到不再使用相关RDD并被垃圾收集。这是为了再次使用RDD时提升Shuffle的性能。

4、持久化

当我们有迭代算法的需求时,我们就需要将之前的RDD进行缓存或者持久化,可以直接使用persist()或cache()来实现。Spark的缓存也是容错的,如果发现缓存的RDD有分区丢失,会通过血缘关系再次创建它。

每个RDD都可以选择不同的持久化级别,比如磁盘、内存。这些级别通过传递一个 StorageLevel对象来实现。

存储级别含义
MEMORY_ONLY将RDD存储为JVM中反序列化的Java对象。如果RDD不适合内存,某些分区将不会被缓存,并且每次需要时都会立即重新计算。这是默认级别。
MEMORY_AND_DISK将RDD存储为JVM中反序列化的Java对象。如果RDD不适合内存,则将不适合磁盘的分区存储在磁盘上,并在需要时从那里读取它们。
MEMORY_ONLY_SER只适用于Java和Scala,将RDD存储为序列化的Java对象(每个分区一个字节数组)。 这通常比反序列化对象更节省空间,尤其是在使用 快速序列化程序,但读取更占用CPU。
MEMORY_AND_DISK_SER只适用于Java和Scala,类似于MEMORY_ONLY_SER,但是将不适合内存的分区溢出到磁盘,而不是每次需要时立即重新计算它们。
DISK_ONLY仅将RDD分区存储在磁盘上
MEMORY_ONLY_2、MEMORY_AND_DISK_2等。与上面的级别相同,但在两个集群节点上复制每个分区。
OFF_HEAP(实验阶段)类似于MEMORY_ONLY_SER,但将数据存储在 堆外内存。这需要启用堆外内存。

Spark还会自动在shuffle操作中持久化一些中间数据(例如reduceByKey),即使没有用户调用persist。这样做是为了避免在洗牌过程中节点失败时重新计算整个输入。

那么选择哪种存储级别呢?

Spark的存储级别旨在在内存使用和CPU效率之间提供不同的权衡。官网给了我们一下建议:

1、如果您的RDD适合默认存储级别(MEMORY_ONLY),请保持不变。 这是CPU效率最高的选项,允许RDD上的操作尽可能快地运行。

2、如果没有,请尝试使用MEMORY_ONLY_SER并选择一个快速序列化库来使对象更加节省空间,但访问速度仍然相当快。(适用于Java和Scala)

3、不要溢出到磁盘,除非计算数据集的函数很昂贵,或者它们过滤了大量数据。否则,重新计算分区可能与从磁盘读取分区一样快

4、如果您想要快速故障恢复(例如,如果使用Spark处理来自Web应用程序的请求),请使用复制的存储级别。所有存储级别都通过重新计算丢失的数据提供完全的容错能力,但复制的存储级别允许您继续在RDD上运行任务,而无需等待重新计算丢失的分区。

5、删除数据

Spark自动监控每个节点上的缓存使用情况,并在 最近最少使用(LRU)方式。如果您想手动删除RDD而不是等待 如果要从缓存中删除,请使用RDD.unpersist()方法。

二、共享变量

通常情况下,我们传递给算子的函数是作用在集群中各个worker上的,其中的变量被复制到每个worker,那么如何跨任务共享变量或者将变量回传给driver呢?Spark给我们提供了两种类型的共享变量即:广播变量和累加器。

1、广播变量

广播变量是将只读变量缓存在每台机器上,而不是将其副本与任务一起发送。例如,它们可以用来以有效的方式为每个节点提供大型输入数据集的副本。Spark还尝试使用有效的广播算法来分发广播变量,以降低通信成本。

创建方法为:

val broadcastVar = sc.broadcast(Array(1, 2, 3))

 使用方法为:

broadcastVar.value

2、累加器

累加器是仅通过关联和交换操作“添加”到的变量,因此可以有效地并行支持。它们可用于实现计数器(如在MapReduce中)或求和。Spark原生支持数字类型的累加器,我们可以添加对其他类型的支持。

累加器在了解整体任务运行进度时很有用,使用方法如下:

创建累加器

val accum = sc.longAccumulator("My Accumulator")

//各个节点累加操作

accum.add(x)

//driver端获取结果
accum.value


http://www.ppmy.cn/news/1475617.html

相关文章

Linux——多线程(五)

1.线程池 1.1初期框架 thread.hpp #include<iostream> #include <string> #include <unistd.h> #include <functional> #include <pthread.h>namespace ThreadModule {using func_t std::function<void()>;class Thread{public:void E…

uniapp vue3微信小程序如何获取dom元素

在网上很多人说可以通过下面两种形式获取到指定dom元素 // 定义ref <div ref"box"></div>//1通过this.$refs获取dom元素 this.$refs.box//2通过ref(null)获取dom元素 let box ref(null)第一种方式在vue2中是可以获取到的&#xff0c;但是在vue3 setup中…

LVS集群(二)

DR模式 LVS三种模式 nat地址转换 DR直接路由模式 tun隧道模式 DR模式的特点&#xff1a; 调度器在整个lvs集群中是最重要的&#xff0c;在nat模式中&#xff0c;负责接收请求&#xff0c;同时根据负载均衡算法转发流量&#xff0c;响应发送给客户端 DR模式&#xff1a;调度…

多头注意力机制详解:多维度的深度学习利器

引言 多头注意力机制是对基础注意力机制的一种扩展&#xff0c;通过引入多个注意力头&#xff0c;每个头独立计算注意力&#xff0c;然后将结果拼接在一起进行线性变换。本文将详细介绍多头注意力机制的原理、应用以及具体实现。 原理 多头注意力机制的核心思想是通过多个注…

ES6 Generator函数的异步应用 (八)

ES6 Generator 函数的异步应用主要通过与 Promise 配合使用来实现。这种模式被称为 “thunk” 模式&#xff0c;它允许你编写看起来是同步的异步代码。 特性&#xff1a; 暂停执行&#xff1a;当 Generator 函数遇到 yield 表达式时&#xff0c;它会暂停执行&#xff0c;等待 …

51单片机3(51单片机最小系统)

一、序言&#xff1a;由前面我们知道&#xff0c;51单片机要工作&#xff0c;光靠一个芯片是不够的&#xff0c;它必须搭配相应的外围电路&#xff0c;我们把能够使51单片机工作的最简单&#xff0c; 最基础的电路统称为51单片机最小系统。 二、最小系统构成&#xff1a;&…

阿里云产品流转

本文主要记述如何使用阿里云对数据进行流转&#xff0c;这里只是以topic流转&#xff08;再发布&#xff09;为例进行说明&#xff0c;可能还会有其他类型的流转&#xff0c;不同服务器的流转也可能会不一样&#xff0c;但应该大致相同。 1 创建设备 具体细节可看&#xff1a;…

centos环境启动/重启java服务脚本优化

centos环境启动/重启java服务脚本优化 引部分命令说明根据端口查询服务进程杀死进程函数脚本接收参数 脚本注意重启文档位置异常 引 在离线环境部署的多个java应用组成的系统&#xff0c;测试阶段需要较为频繁的发布&#xff0c;因资源限制&#xff0c;没有弄devops或CICD那套…

【QT】Qt事件

目录 前置知识 事件概念 常见的事件描述 进入和离开事件 代码示例&#xff1a; 鼠标事件 鼠标点击事件 鼠标释放事件 鼠标双击事件 鼠标滚轮动作 键盘事件 定时器事件 开启定时器事件 窗口相关事件 窗口移动触发事件 窗口大小改变时触发的事件 扩展 前置知识…

Vue3响应系统的作用与实现

副作用函数的执行会直接或间接影响其他函数的执行。一个副作用函数中读取了某个对象的属性&#xff0c;当该属性的值发生改变后&#xff0c;副作用函数自动重新执行&#xff0c;这个对象就是响应式数据。 1 响应式系统的实现 拦截对象的读取和设置操作。当读取某个属性值时&a…

澳门建筑插画:成都亚恒丰创教育科技有限公司

澳门建筑插画&#xff1a;绘就东方之珠的斑斓画卷 在浩瀚的中华大地上&#xff0c;澳门以其独特的地理位置和丰富的历史文化&#xff0c;如同一颗璀璨的明珠镶嵌在南国海疆。这座城市&#xff0c;不仅是东西方文化交融的典范&#xff0c;更是建筑艺术的宝库。当画笔轻触纸面&a…

STM32MP135裸机编程:唯一ID(UID)、设备标识号、设备版本

0 资料准备 1.STM32MP13xx参考手册1 唯一ID&#xff08;UID&#xff09;、设备标识号、设备版本 1.1 寄存器说明 &#xff08;1&#xff09;唯一ID 唯一ID可以用于生成USB序列号或者为其它应用所使用&#xff08;例如程序加密&#xff09;。 &#xff08;2&#xff09;设备…

conda install问题记录

最近想用代码处理sar数据&#xff0c;解放双手。 看重了isce这个处理平台&#xff0c;在安装包的时候遇到了一些问题。 这一步持续了非常久&#xff0c;然后我就果断ctrlc了 后面再次进行尝试&#xff0c;出现一大串报错&#xff0c;不知道是不是依赖项的问题 后面看到说mam…

前端预览图片的两种方式:转Base64预览或转本地blob的URL预览,并再重新转回去

&#x1f9d1;‍&#x1f4bb; 写在开头 点赞 收藏 学会&#x1f923;&#x1f923;&#x1f923; 预览图片 一般情况下&#xff0c;预览图片功能&#xff0c;是后端返回一个图片地址资源&#xff08;字符串&#xff09;给前端&#xff0c;如&#xff1a;ashuai.work/static…

搜维尔科技:scalefit人体工程学分析表明站立式工作站的高度很重要

搜维尔科技&#xff1a;scalefit人体工程学分析表明站立式工作站的高度很重要 搜维尔科技&#xff1a;scalefit人体工程学分析表明站立式工作站的高度很重要

红酒与未来科技:传统与创新的碰撞

在岁月的长河中&#xff0c;红酒以其深邃的色泽、丰富的口感和不同的文化魅力&#xff0c;成为人类文明中的一颗璀璨明珠。而未来科技&#xff0c;则以其迅猛的发展速度和无限的可能性&#xff0c;领着人类走向一个崭新的时代。当红酒与未来科技相遇&#xff0c;一场传统与创新…

【2024最新】C++扫描线算法介绍+实战例题

扫描线介绍&#xff1a;OI-Wiki 【简单】一维扫描线&#xff08;差分优化&#xff09; 网上一维扫描线很少有人讲&#xff0c;可能认为它太简单了吧&#xff0c;也可能认为这应该算在差分里&#xff08;事实上讲差分的文章里也几乎没有扫描线的影子&#xff09;。但我认为&am…

1.26、基于概率神经网络(PNN)的分类(matlab)

1、基于概率神经网络(PNN)的分类简介 PNN(Probabilistic Neural Network,概率神经网络)是一种基于概率论的神经网络模型,主要用于解决分类问题。PNN最早由马科夫斯基和马西金在1993年提出,是一种非常有效的分类算法。 PNN的原理可以简单概括为以下几个步骤: 数据输入层…

Tomcat的服务部署于优化

一、tomcat是一个开源的web应用服务器&#xff0c;nginx主要处理静态页面&#xff0c;那么静态请求&#xff08;连接数据库&#xff0c;动态页面&#xff09;并不是nginx的强项&#xff0c;动态的请求会交给Tomcat进行处理&#xff0c;tomcat是用java代码写的程序&#xff0c;运…

[leetcode]partition-list 分隔链表

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:ListNode* partition(ListNode* head, int x) {ListNode *smlDummy new ListNode(0), *bigDummy new ListNode(0);ListNode *sml smlDummy, *big bigDummy;while (head ! nullptr) {if (head->val &l…