文章目录
前言
虽然是一名后端开发,由于工作需要,大数据的知识后续需要用到,之前学习过一阵hadoop相关的知识。后续有空将继续深入了解大数据的技术栈。边学习,边总结,希望不会半途而废。
Spark基础
Spark是什么
Apache Spark是一个开源的分布式计算框架,Spark被设计用于处理大规模数据处理任务,可以在大规模数据集上进行快速的批处理、流处理和机器学习等操作。
spark官网:http://spark.incubator.apache.org/
spark和hadoop区别
- 数据处理模型:Hadoop使用批处理模型,即一次性处理整个数据集,而Spark支持批处理和流处理两种模型,可以实时处理数据流。
- 处理速度:Spark比Hadoop更快。Spark的速度在内存计算和基于内存的数据共享机制的帮助下得到提升,而Hadoop的速度受限于磁盘读写速度。
- 编程语言:Hadoop主要使用Java编程,Spark支持多种编程语言,如Java、Scala、Python和R。
- 内存管理:Spark采用更为高效的内存管理方式,可以在内存中缓存数据,而Hadoop需要频繁地进行磁盘读写操作。
- 数据库支持:Spark可以使用Hive、HBase、Cassandra等多种数据库,而Hadoop主要使用HDFS。
- 生态系统:Hadoop拥有庞大的生态系统,包括Hive、Pig、MapReduce等工具,而Spark的生态系统相对较小,但是正在不断地发展和壮大。
Spark在速度、处理模型和编程语言等方面比Hadoop更为灵活和高效,同时Spark也与Hadoop兼容,可以与Hadoop组件一起使用,为大数据处理提供更多的选择和灵活性。
Spark 核心模块
Spark的核心模块包括:
- Spark Core:Spark的基础模块,提供分布式任务调度、内存管理和容错等功能。Spark Core提供了一组API,支持Scala、Java和Python编程语言。
- Spark SQL:Spark的SQL查询模块,支持关系型数据处理,可以通过SQL语句查询数据,也可以与Hive等数据仓库进行交互。
- Spark Streaming:Spark的流处理模块,支持实时数据流处理。Spark Streaming提供了一组API,可以将实时数据流转换为批处理数据进行处理。
- MLlib:Spark的机器学习模块,提供了一组机器学习算法库,支持分类、聚类、回归、协同过滤等任务。
- GraphX:Spark的图处理模块,支持图形数据处理和分析。
除了这些核心模块外,Spark还提供了许多其他的模块和扩展,例如:
- SparkR:Spark的R语言接口,支持在R中使用Spark进行数据处理和分析。
- PySpark:Spark的Python语言接口,支持在Python中使用Spark进行数据处理和分析。
- Spark Streaming Kafka:支持在Spark Streaming中与Apache Kafka集成。
- Spark Streaming Flume:支持在Spark Streaming中与Apache Flume集成。
这些模块和扩展都能够满足不同的数据处理和分析需求。
Spark运行模式
- Local模式:在本地机器上运行Spark应用程序,用于开发、测试和调试。
- Standalone模式:使用Spark自带的集群管理器运行Spark应用程序。它支持将应用程序部署到一个由多个节点组成的集群中。如果需要在Standalone模式下实现高可用性,则可以使用Standalone-HA模式。
- Standalone-HA模式:在Standalone模式下实现高可用性。通过使用ZooKeeper来协调主节点的选举和故障恢复,确保Spark应用程序能够在主节点发生故障时自动切换到备用节点。
- On YARN模式:在Apache Hadoop YARN集群上运行Spark应用程序。YARN是Hadoop的集群管理系统,支持多种分布式应用程序的运行,包括Spark。
- On Mesos模式:在Apache Mesos集群上运行Spark应用程序。Mesos是一个通用的集群管理系统,支持多种分布式应用程序的运行,包括Spark。
- On Cloud模式:将Spark应用程序部署到云平台上运行,例如Amazon EMR、Google Cloud Dataproc等。
后续我们将具体实操下几种常见方式
Spark运行架构
运行架构
Spark运行架构主要由以下四个组件组成:
- Driver:驱动器程序是运行在主节点上的进程,负责控制整个应用程序的执行过程,包括调度任务、分配资源、收集和汇总任务执行结果等。驱动器程序会向集群管理器请求资源,并将任务分发到集群中的Executor上执行。
- Executor:Executor是运行在工作节点上的进程,负责执行驱动器程序分配给它们的任务,并将任务执行结果返回给驱动器程序。每个Executor都有自己的JVM进程,可以在任务执行期间缓存数据,从而加速任务执行速度。
- Cluster Manager:集群管理器是用于管理集群资源的组件,Spark支持多种集群管理器,包括Standalone、Apache Mesos、Hadoop YARN等。集群管理器负责接收驱动器程序的请求,并为驱动器程序分配合适的Executor和资源。
- Spark Application:Spark应用程序是由驱动器程序和一组任务组成的分布式应用程序,驱动器程序负责控制整个应用程序的执行过程,任务则在Executor上执行。Spark应用程序可以通过编写Spark SQL、Spark Streaming、Spark MLlib等组件来实现各种数据处理和分析任务。
在Spark的运行架构中,驱动器程序和Executor之间通过网络通信来传递数据和任务,集群管理器负责调度资源和管理集群。由于Spark采用的是内存计算,因此可以大大提高数据处理和分析的速度和效率。
Executor与Core(核)
在Spark中,Executor是运行在工作节点上的进程,它们负责执行驱动器程序分配给它们的任务,并将任务执行结果返回给驱动器程序。每个Executor都有自己的JVM进程,可以在任务执行期间缓存数据,从而加速任务执行速度。
而Core(核)是Executor中的计算资源单元,每个Executor都由多个Core组成。Core的数量通常由硬件配置决定,例如一个节点有16个CPU核心,则可以将Executor配置为使用8个核心。
在Spark中,Executor的数量和每个Executor所使用的Core数量可以通过配置文件来进行设置。通过增加Executor的数量和每个Executor所使用的Core的数量,可以提高Spark应用程序的并行度和运行速度。然而,如果Executor数量过多或者每个Executor所使用的Core数量过多,可能会导致资源浪费或者资源不足,从而影响Spark应用程序的性能和稳定性。因此,在设置Executor数量和Core数量时需要根据具体的应用场景和硬件配置进行调整。
应用程序相关启动参数如下:
名称 | 说明 |
---|---|
–num-executors | 配置Executor的数量 |
–executor-memory | 配置每个Executor的内存大小 |
–executor-cores | 配置每个Executor的虚拟CPU core数量 |
并行度(Parallelism)
并行度(Parallelism)是指同时处理多个任务或数据的能力。在计算机科学中,它通常用于描述一个应用程序或系统可以同时处理多少个任务或数据。在Spark中,并行度是指同时处理Spark应用程序中的多个任务或数据的能力,通常使用Executor的数量和每个Executor所使用的Core的数量来表示。
在Spark应用程序中,提高并行度可以提高任务执行的速度和效率,从而加快数据处理和分析的速度。在实际应用中,提高并行度的方法主要有以下几种:
- 增加Executor的数量:通过增加Executor的数量可以增加Spark应用程序的并行度,从而加快任务执行的速度。需要注意的是,增加Executor的数量需要根据硬件配置和应用程序的资源消耗进行合理的设置。
- 增加每个Executor所使用的Core的数量:通过增加每个Executor所使用的Core的数量可以增加每个Executor的计算能力,从而加快任务执行的速度。需要注意的是,增加Core的数量也需要根据硬件配置和应用程序的资源消耗进行合理的设置。
- 使用数据分区:数据分区是将数据按照一定规则划分为多个部分,以便于并行处理的技术。通过使用数据分区,可以将数据分散到不同的Executor上进行处理,从而提高并行度和执行效率。
- 使用并行算法:并行算法是指可以同时在多个处理器上执行的算法。通过使用并行算法,可以将任务分解成多个部分并行处理,从而提高并行度和执行效率。
有向无环图(DAG)
有向无环图(Directed Acyclic Graph,简称DAG)是一种由有向边连接的节点组成的图形结构,在该结构中不存在环(即任意节点通过边连接不能构成一个环),通常用于描述计算流程或数据处理过程中的依赖关系。
在Spark中,DAG是一个非常重要的概念,它用于描述Spark应用程序中的数据处理流程。在Spark应用程序中,DAG由一系列的RDD(Resilient Distributed Datasets)和转换操作组成。每个RDD代表一个分布式的数据集,而转换操作则用于对RDD进行变换和计算。Spark应用程序中的每个操作都会生成一个新的RDD,并将其加入到DAG中。DAG中的每个节点表示一个RDD,每个有向边表示一个转换操作。
在Spark应用程序中,DAG的生成和优化是由Spark的任务调度器负责的。当Spark应用程序被提交到集群上运行时,Spark会将应用程序中的每个操作转化为一组任务,然后将任务按照依赖关系组织成一个DAG。然后,Spark会对DAG进行优化,如合并相邻的操作,移除无用的操作等,以便于提高任务的执行效率和并行度。最后,Spark会将DAG拆分成多个阶段,每个阶段都可以并行执行,从而提高任务执行的效率。
简单来说,DAG是一种图形结构,用于描述数据处理流程中的依赖关系,而在Spark中,DAG则用于描述RDD之间的转换关系和操作顺序,通过DAG可以优化任务的执行计划,提高任务的并行度和执行效率。
spark 的提交方式
client
在 client 模式下,Driver 程序运行在提交 Spark 应用程序的客户端进程中,而 Executor 程序运行在集群中的计算节点上。这种提交方式通常用于调试和开发环境中,因为可以更方便地查看和调试应用程序的运行状态和结果。
cluster
在 cluster 模式下,Driver 程序运行在集群中的某个节点上,而 Executor 程序也在集群中的其他节点上运行。这种提交方式通常用于生产环境中,因为可以更好地利用集群资源,提高任务的并行度和执行效率。
国内工作中,将Spark引用部署到Yarn环境中会更多一些,所以本课程中的提交流程是基于Yarn环境的。
Spark核心编程
三大数据结构
-
RDD(Resilient Distributed Datasets):RDD是Spark中最基础的分布式数据结构之一,它是一个不可变的分布式对象集合。RDD中的每个分区都存储着一部分数据,并且分布在集群的多个节点上。RDD提供了许多转换操作和行动操作,可以对其进行变换和计算。
-
累加器(Accumulator):累加器是一种特殊的变量,在分布式环境下可以进行并行操作。累加器只能进行加法操作,而且只能由Driver端向Executor端累加数据,不能反过来。累加器主要用于计数、求和等聚合操作。
-
广播变量(Broadcast Variable):广播变量是一种可以在集群中共享的只读变量。在分布式计算过程中,如果有一些变量需要在多个节点之间共享,可以将这些变量使用广播变量的方式进行传输。广播变量只会被发送一次,然后在Executor端被缓存起来供后续使用,可以减少网络传输和内存占用。
RDD
什么是RDD
RDD(Resilient Distributed Datasets)是Spark中最基础的分布式数据结构之一,它是一个不可变的分布式对象集合。RDD中的每个分区都存储着一部分数据,并且分布在集群的多个节点上。RDD提供了许多转换操作和行动操作,可以对其进行变换和计算。
RDD的特点如下:
- 分布式:RDD中的数据被分布在集群的多个节点上,可以进行并行计算。
- 不可变性:RDD是不可变的,一旦创建就不能修改。如果需要对RDD进行变换,需要生成一个新的RDD。
- 容错性:RDD可以通过数据的划分和备份来实现容错性。如果某个节点失效,可以从备份中重新计算数据。
惰性计算:Spark中的转换操作是惰性计算的,只有在执行行动操作时才会真正计算数据。
执行原理
在Yarn环境中,Spark的RDD工作原理如下:
- 首先,Driver程序会向ResourceManager请求资源,并启动ApplicationMaster。
- ApplicationMaster会向ResourceManager申请Container,然后在Container中启动Executor。
- Executor会向Driver程序请求RDD,并将数据分成多个分区存储到内存中。
- Driver程序会将要执行的任务(包括转换操作和行动操作)分发给Executor执行。
- 在Executor中,任务会以依赖关系的方式被组织成一个有向无环图(DAG)。
- 当Driver程序执行行动操作时,任务会被提交到Executor中执行。如果某个任务依赖于其他任务的输出,会在之前的任务完成后执行。
- 执行完成后,Executor会将结果返回给Driver程序。
RDD API
RDD创建
- 从内存中的集合(Parallelized Collections)中创建RDD:
val rdd = sc.parallelize(1 to 100)
- 从外部存储系统(如HDFS、本地文件系统等)中读取数据创建RDD:
使用SparkContext.textFile()方法可以从一个或多个文件中创建一个文本文件的RDD。例如,可以使用以下代码从HDFS中读取一个文件创建RDD:
val rdd = sc.textFile("hdfs://localhost:9000/data.txt")
- 通过转换一个已有的RDD来创建新的RDD:
可以通过对一个已有的RDD执行一系列的转换操作来创建新的RDD。例如,可以使用以下代码创建一个新的RDD,其中每个数字都加倍:
val rdd1 = sc.parallelize(1 to 100)
val rdd2 = rdd1.map(_ * 2)
- 通过并行化已有的数据集来创建RDD:
使用SparkContext.newAPIHadoopRDD()方法可以从一个已有的Hadoop输入格式(如TextInputFormat)中创建一个RDD。例如,可以使用以下代码从一个Hadoop输入格式的文件中创建RDD:
val conf = new Configuration()
val file = sc.newAPIHadoopFile("hdfs://localhost:9000/data.txt", classOf[TextInputFormat], classOf[LongWritable], classOf[Text], conf)
val rdd = file.map(pair => pair._2.toString)
RDD转换算子
转换算子 | 含义 |
---|---|
map(func) | 返回一个新的 RDD,该 RDD 由每一个输入元素经过 func 函数转换后组成 |
filter(func) | 返回一个新的 RDD,该 RDD 由经过 func 函数计算后返回值为 true 的输入元素组成 |
flatMap(func) | 类似于 map,但是每一个输入元素可以被映射为 0 或多个输出元素(所以 func 应该返回一个序列,而不是单一元素) |
mapPartitions(func) | 类似于 map,但独立地在 RDD 的每一个分片上运行,因此在类型为 T 的 RDD 上运行时,func 的函数类型必须是 Iterator[T] => Iterator[U] |
mapPartitionsWithIndex(func) | 类似于 mapPartitions,但 func 带有一个整数参数表示分片的索引值,因此在类型为 T 的 RDD 上运行时,func 的函数类型必须是(Int, Interator[T]) => Iterator[U] |
sample(withReplacement, fraction, seed) | 根据 fraction 指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed 用于指定随机数生成器种子 |
union(otherDataset) | 对源 RDD 和参数 RDD 求并集后返回一个新的 RDD |
intersection(otherDataset) | 对源 RDD 和参数 RDD 求交集后返回一个新的 RDD |
distinct([numTasks])) | 对源 RDD 进行去重后返回一个新的 RDD |
groupByKey([numTasks]) | 在一个(K,V)的 RDD 上调用,返回一个(K, Iterator[V])的 RDD |
reduceByKey(func, [numTasks]) | 在一个(K,V)的 RDD 上调用,返回一个(K,V)的 RDD,使用指定的 reduce 函数,将相同 key 的值聚合到一起,与 groupByKey 类似,reduce 任务的个数可以通过第二个可选的参数来设置 |
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) | 对 PairRDD 中相同的 Key 值进行聚合操作,在聚合过程中同样使用了一个中立的初始值。和 aggregate 函数类似,aggregateByKey 返回值的类型不需要和 RDD 中 value 的类型一致 |
sortByKey([ascending], [numTasks]) | 在一个(K,V)的 RDD 上调用,K 必须实现 Ordered 接口,返回一个按照 key 进行排序的(K,V)的 RDD |
sortBy(func,[ascending], [numTasks]) | 与 sortByKey 类似,但是更灵活 |
join(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素对在一起的(K,(V,W))的 RDD |
cogroup(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD |
cartesian(otherDataset) | 笛卡尔积 |
pipe(command, [envVars]) | 对 rdd 进行管道操作 |
coalesce(numPartitions) | 减少 RDD 的分区数到指定值。在过滤大量数据之后,可以执行此操作 |
repartition(numPartitions) | 重新给 RDD 分区 |
Action 行动算子
动作算子 | 含义 |
---|---|
reduce(func) | 通过 func 函数聚集 RDD 中的所有元素,这个功能必须是可交换且可并联的 |
collect() | 在驱动程序中,以数组的形式返回数据集的所有元素 |
count() | 返回 RDD 的元素个数 |
first() | 返回 RDD 的第一个元素(类似于 take(1)) |
take(n) | 返回一个由数据集的前 n 个元素组成的数组 |
takeSample(withReplacement,num, [seed]) | 返回一个数组,该数组由从数据集中随机采样的 num 个元素组成,可以选择是否用随机数替换不足的部分,seed 用于指定随机数生成器种子 |
takeOrdered(n, [ordering]) | 返回自然顺序或者自定义顺序的前 n 个元素 |
saveAsTextFile(path) | 将数据集的元素以 textfile 的形式保存到 HDFS 文件系统或者其他支持的文件系统,对于每个元素,Spark 将会调用 toString 方法,将它装换为文件中的文本 |
saveAsSequenceFile(path) | 将数据集中的元素以 Hadoop sequencefile 的格式保存到指定的目录下,可以使 HDFS 或者其他 Hadoop 支持的文件系统 |
saveAsObjectFile(path) | 将数据集的元素,以 Java 序列化的方式保存到指定的目录下 |
countByKey() | 针对(K,V)类型的 RDD,返回一个(K,Int)的 map,表示每一个 key 对应的元素个数 |
foreach(func) | 在数据集的每一个元素上,运行函数 func 进行更新 |
foreachPartition(func) | 在数据集的每一个分区上,运行函数 func |
统计操作
算子 | 含义 |
---|---|
count | 个数 |
mean | 均值 |
sum | 求和 |
max | 最大值 |
min | 最小值 |
variance | 方差 |
sampleVariance | 从采样中计算方差 |
stdev | 标准差:衡量数据的离散程度 |
sampleStdev | 采样的标准差 |
stats | 查看统计结果 |
RDD序列化
RDD序列化是Spark中的一个重要概念,它是指将RDD中的数据对象转换为字节流的过程,以便在不同的节点之间进行网络传输或磁盘存储。在Spark中,需要对RDD进行序列化是因为RDD在分布式计算中需要在多个节点之间传输和存储,而这些节点的操作系统和硬件环境可能不同,因此需要将RDD数据对象进行序列化,以便能够在不同的节点之间传输和存储。
Spark支持两种类型的RDD序列化方式:Java序列化和Kryo序列化。Java序列化是JVM自带的序列化机制,它具有通用性,但是效率较低。Kryo序列化是一个高性能的序列化库,它能够将对象序列化成较小的字节数组,从而提高网络传输和磁盘存储的效率。在使用Kryo序列化时,需要先注册需要序列化的类,以便Kryo能够正确地序列化和反序列化这些对象。
在Spark中,默认情况下使用Java序列化方式,但是可以通过设置SparkConf的“spark.serializer”属性来指定使用Kryo序列化方式,例如:
val conf = new SparkConf()
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
val sc = new SparkContext(conf)
RDD依赖关系
在Spark中,RDD的依赖关系是指一个RDD与其他RDD之间的关系,包括它们之间的转换操作和依赖类型。根据依赖类型的不同,RDD的依赖关系可以分为两种:宽依赖和窄依赖。
-
窄依赖(Narrow Dependency):指一个RDD的每个分区只依赖于另一个RDD的一个或多个分区。例如,map、filter等转换操作都是窄依赖。
-
宽依赖(Wide Dependency):指一个RDD的每个分区依赖于另一个RDD的多个分区,即一个RDD的每个分区需要与另一个RDD的所有分区进行计算。例如,reduceByKey、groupByKey等转换操作都是宽依赖。
在Spark中,RDD的依赖关系是以有向无环图(DAG)的形式组织起来的。每个RDD都有一组父RDD和一组子RDD,每个父RDD与子RDD之间都有一条有向边来表示依赖关系。在一个DAG中,每个RDD都是通过一系列转换操作从原始数据集导出的,最终得到的RDD被称为输出RDD。
在Spark中,DAG的构建是惰性的,也就是说,只有在需要执行操作时才会计算DAG。这种惰性计算的好处是能够避免不必要的计算,提高计算效率。同时,由于DAG是有向无环图,因此可以通过优化DAG来减少计算的开销,提高计算性能。
RDD持久化
在Spark中,RDD持久化(Persistence)指的是将RDD的数据缓存到内存或磁盘中,以便后续重复使用,可以使用RDD的persist()方法或cache()方法实现。这两个方法的作用是一样的,都可以将RDD的数据缓存到内存或磁盘中。
这两个方法的使用方式非常简单,只需要在RDD上调用persist()方法或cache()方法,并指定缓存的级别即可。例如:
val rdd = sc.parallelize(Seq(1, 2, 3))
rdd.persist(StorageLevel.MEMORY_ONLY)
或者:
val rdd = sc.parallelize(Seq(1, 2, 3))
rdd.cache()
其中,StorageLevel.MEMORY_ONLY指定了缓存级别为内存缓存,表示将RDD的数据存储在内存中。除了MEMORY_ONLY外,Spark还支持其他多种缓存级别,如MEMORY_ONLY_SER、MEMORY_AND_DISK、MEMORY_AND_DISK_SER等,可以根据实际需求进行选择。
需要注意的是,持久化RDD需要消耗内存或磁盘空间,因此需要根据实际情况来选择合适的缓存级别。另外,可以使用unpersist()方法来释放缓存的RDD数据,例如:
rdd.unpersist()
当调用persist()或cache()方法后,Spark会根据缓存级别和当前的可用内存或磁盘空间来决定RDD数据是放在内存中还是磁盘中。如果数据太大无法全部缓存到内存中,Spark会按照缓存级别和LRU算法来淘汰不常用的数据。如果数据在内存中被淘汰,则后续使用该数据时需要重新计算。
RDD分区器
在Spark中,RDD的分区(Partition)是指将一个RDD的数据集划分成若干个小的数据块,每个数据块称为一个分区,每个分区都可以被一个Task并行处理。RDD的分区是Spark实现高效计算的关键,因为它可以将数据并行处理,从而提高计算性能。
而RDD分区器(Partitioner)则是对RDD的分区进行进一步的优化,即对数据进行重新分区,以便更好地利用Spark的并行计算能力。RDD分区器主要用于控制RDD的数据如何被分配到不同的节点上进行计算,从而更好地利用集群的资源。
在Spark中,RDD分区器分为两种类型:哈希分区器和范围分区器。哈希分区器(HashPartitioner)是根据键的哈希值来对RDD进行分区,而范围分区器(RangePartitioner)则是根据键的范围来对RDD进行分区。对于哈希分区器,Spark默认使用的是HashPartitioner,而对于范围分区器,Spark会根据数据的范围和RDD的分区数自动选择使用RangePartitioner。
在使用RDD的groupByKey、reduceByKey等聚合操作时,需要使用到哈希分区器。在使用RDD的sortByKey、join等操作时,需要使用到范围分区器。可以通过对RDD调用partitionBy方法来指定分区器,例如:
val rdd = sc.parallelize(Seq(("a", 1), ("b", 2), ("c", 3)))
val partitioner = new HashPartitioner(2)
val partitionedRDD = rdd.partitionBy(partitioner)
在上面的例子中,我们创建了一个包含三个元素的RDD,并使用HashPartitioner对其进行分区,分为两个分区。其中,HashPartitioner的构造函数需要指定分区数,这里指定为2。然后,我们使用RDD的partitionBy方法将RDD重新分区,并指定使用HashPartitioner进行分区。这样,RDD的数据就会被重新分配到两个节点上,以便更好地利用集群的资源进行计算。
RDD文件读取与保存
在Spark中,可以使用RDD的读取和保存操作,从而将数据加载到RDD中,或将RDD中的数据保存到外部存储系统中。常用的RDD读取和保存方式包括:
从文件中读取数据
可以使用sc.textFile()方法从文件中读取数据,例如:
val rdd = sc.textFile("path/to/file")
其中,path/to/file指定要读取的文件路径,该方法返回一个包含文件中所有行的RDD。
将RDD保存到文件中
可以使用RDD.saveAsTextFile()方法将RDD中的数据保存到文件中,例如:
val rdd = sc.parallelize(Seq("Hello", "World", "Spark"))
rdd.saveAsTextFile("path/to/output")
其中,path/to/output指定要保存的文件路径,该方法将RDD中的数据保存为文本格式。
从Hadoop文件系统中读取数据
可以使用sc.hadoopFile()方法从Hadoop文件系统中读取数据,例如:
val rdd = sc.hadoopFile("path/to/hdfs/file", classOf[TextInputFormat], classOf[LongWritable], classOf[Text])
其中,path/to/hdfs/file指定要读取的Hadoop文件路径,TextInputFormat指定要读取的文件格式,LongWritable和Text分别指定键和值的类型,该方法返回一个包含Hadoop文件中所有行的RDD。
将RDD保存到Hadoop文件系统中
可以使用RDD.saveAsHadoopFile()方法将RDD中的数据保存到Hadoop文件系统中,例如:
val rdd = sc.parallelize(Seq("Hello", "World", "Spark"))
rdd.saveAsHadoopFile("path/to/hdfs/output", classOf[Text], classOf[Text], classOf[TextOutputFormat])
其中,path/to/hdfs/output指定要保存的Hadoop文件路径,Text指定要保存的键和值的类型,TextOutputFormat指定要保存的文件格式,该方法将RDD中的数据保存为Hadoop文件格式。