RDD的创建、操作

本文介绍了Spark中RDD的创建方法,包括从外部数据集加载和通过并行集合创建。详细讲解了转换操作如map、filter和reduceByKey,以及行动操作如count、collect和reduce。此外,还讨论了RDD的持久化、分区策略以及如何设置默认分区数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

官网原文:There are two ways to create RDDs: parallelizing an existing collection in your driver program, or referencing a dataset in an external storage system, such as a shared filesystem, HDFS, HBase, or any data source offering a Hadoop InputFormat.

RDD可以通过两种方式创建:

  • 第一种:读取一个外部数据集。比如,从本地文件加载数据集,或者从HDFS文件系统、HBase、Cassandra、Amazon S3等外部数据源中加载数据集。Spark可以支持文本文件、SequenceFile文件(Hadoop提供的 SequenceFile是一个由二进制序列化过的key/value的字节流组成的文本存储文件)和其他符合Hadoop InputFormat格式的文件。

  • 第二种:调用SparkContext的parallelize方法,在Driver中一个已经存在的集合(数组)上创建。

从外部数据集中加载数据创建RDD

Spark可以从Hadoop支持的任何存储源创建分布式数据集,包括本地文件系统,HDFS,Cassandra,HBase,Amazon S3等.Spark支持test files,SequenceFiles和任何其他Hadoop InputFormat。

文本文件RDDS可以使用创建SparkContext的textFile()方法。此方法需要一个URI的文件(本地路径的机器上,或一个hdfs://,s3a://等URI),并读取其作为行的集合。这是一个示例调用:

scala> val distFile = sc.textFile("data.txt")
scala> val lines =sc.textFile("file:///user/hadoop/word.txt")
scala> val lines =sc.textFile("hdfs://localhost:9000/user/hadoop/word.txt")
distFile: org.apache.spark.rdd.RDD[String] = data.txt MapPartitionsRDD[10] at textFile at <console>:26

创建后,distFile可以通过数据集操作执行操作。例如,我们可以使用map和reduce操作添加所有行的大小,如下所示:distFile.map(s => s.length).reduce((a, b) => a + b)。

如果是读本地的文件,必须保证所有节点上都有这一份数据。比如standalone模式,需要确保每个节点上都有inputsource。

通过并行集合(数组)创建RDD

复制集合的元素以形成可以并行操作的分布式数据集。例如,以下是如何创建包含数字1到5的并行化集合:

val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)

一旦创建,RDD(distData)可以并行操作。例如,我们可能会调用distData.reduce((a, b) => a + b)添加数组的元素。

并行集合的一个重要参数是将数据集切割为的分区数。Spark将为群集的每个partition运行一个task。可以通过将分区数其作为第二个参数传递给parallelize(例如sc.parallelize(data, 10))来手动设置。不设置就是local[ ]里的值

有关使用Spark读取文件的一些注意事项:

  • 如果在本地文件系统上使用路径,则必须可以在所有工作节点上的相同路径上访问该文件。将文件复制到所有工作者或使用网络安装的共享文件系统。

  • 所有Spark的基于文件的输入方法,包括textFile支持在目录,压缩文件和通配符上运行。例如,你可以使用textFile("/my/directory"),textFile("/my/directory/.txt")和textFile("/my/directory/.gz")。

  • textFile()方法也可以接受第2个输入参数(可选),用来指定分区的数目。默认情况下,Spark会为HDFS的每个block创建一个分区(HDFS中每个block默认是128MB)。你也可以提供一个比block数量更大的值作为分区数目,但是,你不能提供一个小于block数量的值作为分区数目。

除文本文件外,Spark的Scala API还支持其他几种数据格式:

  • SparkContext.wholeTextFiles允许您读取包含多个小文本文件的目录,并将它们作为(文件名,内容)对返回。这与之相反textFile,它将在每个文件中每行返回一条记录。分区由数据局部性决定,在某些情况下,可能导致分区太少。对于这些情况,wholeTextFiles提供可选的第二个参数来控制最小数量的分区。

  • 对于SequenceFiles,使用SparkContext的sequenceFile[K, V]方法,其中K和V是文件中键和值的类型。这些应该是Hadoop的Writable接口的子类,如IntWritable和Text。此外,Spark允许您为一些常见的Writable指定本机类型; 例如,sequenceFile[Int, String]将自动读取IntWritables和文本。

  • 对于其他Hadoop InputFormats,您可以使用该SparkContext.hadoopRDD方法,该方法采用任意JobConf输入格式类,键类和值类。设置这些与使用输入源的Hadoop作业的方式相同。您还可以使用SparkContext.newAPIHadoopRDD基于“新”MapReduce API(org.apache.hadoop.mapreduce)的InputFormats 。

  • RDD.saveAsObjectFile并SparkContext.objectFile支持以包含序列化Java对象的简单格式保存RDD。虽然这不像Avro这样的专用格式有效,但它提供了一种保存任何RDD的简便方法。


RDD 操作

RDD被创建好以后,在后续使用过程中一般会发生两种操作:

  • 转换(Transformation): 基于现有的数据集创建一个新的数据集。

  • 行动(Action):在数据集上进行运算,返回计算值。

例如,map是一个转换,它通过一个函数传递每个数据集元素,并返回一个表示结果的新RDD。另一方面,reduce是一个使用某个函数聚合RDD的所有元素并将最终结果返回给驱动程序的动作(尽管还有一个reduceByKey返回分布式数据集的并行)。

Spark中的所有转换都是惰性的,因为它们不会立即计算结果。相反,他们只记得应用于某些基础数据集的转换(例如文件)。仅当操作需要将结果返回到driver program时才会计算转换。这种设计使Spark能够更有效地运行。例如,我们可以意识到,通过map创建的数据集将会被一个reduce使用,然后仅将reduce的处理结果result返回给driver,而不是更大的映射数据集。

默认情况下,每次你运行一个action,每一个RDD转换都会被重新执行。但是,您也可以使用persist()方法或cache()在内存中保留 RDD ,在这种情况下,Spark会在群集上保留元素,以便在下次查询时更快地访问。还支持在磁盘上保留RDD或在多个节点上复制。

转换操作

对于RDD而言,每一次转换操作都会产生不同的RDD,供给下一个“转换”使用。转换得到的RDD是惰性求值的,也就是说,整个转换过程只是记录了转换的轨迹,并不会发生真正的计算,只有遇到行动操作时,才会发生真正的计算,开始从血缘关系源头开始,进行物理的转换操作。

下面列出一些常见的转换操作(Transformation API):

  • filter(func):筛选出满足函数func的元素,并返回一个新的数据集

  • map(func):将每个元素传递到函数func中,并将结果返回为一个新的数据集

  • flatMap(func):与map()相似,但每个输入元素都可以映射到0或多个输出结果

  • groupByKey():应用于(K,V)键值对的数据集时,返回一个新的(K, Iterable)形式的数据集

  • mapValues :key不改变,只改变value

  • reduceByKey(func):应用于(K,V)键值对的数据集时,返回一个新的(K, V)形式的数据集,其中的每个值是将每个key传递到函数func中进行聚合

行动操作

行动操作是真正触发计算的地方。Spark程序执行到行动操作时,才会执行真正的计算,从文件中加载数据,完成一次又一次转换操作,最终,完成行动操作得到结果。

下面列出一些常见的行动操作(Action API):

  • count() 返回数据集中的元素个数

  • collect() 以数组的形式返回数据集中的所有元素

  • first() 返回数据集中的第一个元素

  • take(n) 以数组的形式返回数据集中的前n个元素

  • reduce(func) 通过函数func(输入两个参数并返回一个值)聚合数据集中的元素

  • foreach(func) 将数据集中的每个元素传递到函数func中运行*

持久化persist

在一些情形下,我们需要多次调用不同的行动操作,这就意味着,每次调用行动操作,都会触发一次从头开始的计算。这对于迭代计算而言,代价是很大的,迭代计算经常需要多次重复使用同一组数据。

scala> val list =List("Hadoop","Spark","Hive")

list: List[String] = List(Hadoop, Spark,Hive)

scala> val rdd = sc.parallelize(list)

rdd: org.apache.spark.rdd.RDD[String] =ParallelCollectionRDD[22] at parallelize at <console>:29

scala> println(rdd.count()) //行动操作,触发一次真正从头到尾的计算
3

scala>println(rdd.collect().mkString(",")) //行动操作,触发一次真正从头到尾的计算

Hadoop,Spark,Hive

上面代码执行过程中,前后共触发了两次从头到尾的计算。

实际上,可以通过持久化(缓存)机制避免这种重复计算的开销。可以使用persist()方法对一个RDD标记为持久化,之所以说“标记为持久化”,是因为出现persist()语句的地方,并不会马上计算生成RDD并把它持久化,而是要等到遇到第一个行动操作触发真正计算以后,才会把计算结果进行持久化,持久化后的RDD将会被保留在计算节点的内存中被后面的行动操作重复使用。

persist()的圆括号中包含的是持久化级别参数,比如,persist(MEMORY_ONLY)表示将RDD作为反序列化的对象存储于JVM中,如果内存不足,就要按照LRU原则替换缓存中的内容。persist(MEMORY_AND_DISK)表示将RDD作为反序列化的对象存储在JVM中,如果内存不足,超出的分区将会被存放在硬盘上。一般而言,使用cache()方法时,会调用persist(MEMORY_ONLY)。

例子如下:

scala> val list =List("Hadoop","Spark","Hive")

list: List[String] = List(Hadoop, Spark,Hive)

scala> val rdd = sc.parallelize(list)

rdd: org.apache.spark.rdd.RDD[String] =ParallelCollectionRDD[22] at parallelize at <console>:29

scala> rdd.cache()  //会调用persist(MEMORY_ONLY),但是,语句执行到这里,并不会缓存rdd,这是rdd还没有被计算生成

scala> println(rdd.count()) //第一次行动操作,触发一次真正从头到尾的计算,这时才会执行上面的rdd.cache(),把这个rdd放到缓存中
3

scala>println(rdd.collect().mkString(",")) //第二次行动操作,不需要触发从头到尾的计算,只需要重复使用上面缓存中的rdd

Hadoop,Spark,Hive

最后,可以使用unpersist()方法手动地把持久化的RDD从缓存中移除。

分区

RDD是弹性分布式数据集,通常RDD很大,会被分成很多个分区,分别保存在不同的节点上。RDD分区的一个分区原则是使得分区的个数尽量等于集群中的CPU核心(core)数目。

对于不同的Spark部署模式而言(本地模式、Standalone模式、YARN模式、Mesos模式),都可以通过设置spark.default.parallelism这个参数的值,来配置默认的分区数目,一般而言:

*本地模式:默认为本地机器的CPU数目,若设置了local[N],则默认为N;

*Apache Mesos:默认的分区数为8;

*Standalone或YARN:在“集群中所有CPU核心数目总和”和“2”二者中取较大值作为默认值;

因此,对于parallelize而言,如果没有在方法中指定分区数,则默认为spark.default.parallelism,比如:

scala>val array = Array(1,2,3,4,5)

array: Array[Int] = Array(1, 2, 3, 4, 5)

scala>val rdd = sc.parallelize(array,2)#设置两个分区

rdd: org.apache.spark.rdd.RDD[Int] =ParallelCollectionRDD[13] at parallelize at :29

对于textFile而言,如果没有在方法中指定分区数,则默认为min(defaultParallelism,2),其中,defaultParallelism对应的就是spark.default.parallelism。

如果是从HDFS中读取文件,则分区数为文件分片数(比如,128MB/片)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值