RDD
RDD的过程就像一个水龙头,前面的那些只是状态,像水流一样向下,直到“动作”之后,才会运算的到结果。
RDD的过程是一个有向无环图(DAG)(不会有死循环)
RDD编程基础
1.RDD创建
(1)从文件系统中加载数据创建(用的是textFile()方法)
本地👇
sc.textFile("file:///usr/local/code/word.txt") //sc这个对象就是SparkContext
如果是从HDFS上加载数据👇
sc.textFile("hdfs://localhost:9000/user/hadoop/word.txt") //默认到本用户找(/user/hadoop)
(2)从集合创建
2.RDD操作
两种操作:
- 转换操作
- 行动操作
转换操作只是记录轨迹,还没有拧水龙头,也就是没有进行真正的计算。只有发生动作(要产生输出了,水龙头拧开),才会从左向右的计算。这个也就是惰性操作。
转换操作:filter过滤,map映射,flatMap拍扁得到每一个单词,groupByKey分组,reduceByKey按照key进行规约......(转换操作得到的结果都是RDD,RDD可能不一样)
行动操作:count(),collect(),reduce(),first()......(得到一个结果。这就是行动操作,行动操作触发计算)
持久化(deepSeek生成)
链太长了,遇到第一个动作才会触发持久化。
RDD持久化(Persistence)是Spark中一个重要的优化技术,它允许将RDD的计算结果保存在内存或磁盘中,以便在后续操作中重复使用,避免重复计算。
持久化机制
-
惰性求值与持久化触发:
- RDD转换操作是惰性的,只有在遇到第一个动作(action)时才会触发实际计算
- 持久化操作(
persist()或cache())也遵循这一规则,只有在第一个动作执行后才会真正缓存数据
-
持久化级别:
MEMORY_ONLY // 只存储在内存中(默认) MEMORY_AND_DISK // 先存内存,内存不足再存磁盘 DISK_ONLY // 只存储在磁盘 MEMORY_ONLY_SER // 序列化存储在内存 MEMORY_AND_DISK_SER // 序列化存储,内存不足时存磁盘 OFF_HEAP // 存储在堆外内存 -
使用方法:
val rdd = sc.textFile("data.txt").cache() // 等价于.persist(StorageLevel.MEMORY_ONLY) // 或者明确指定存储级别 import org.apache.spark.storage.StorageLevel val rdd = sc.textFile("data.txt").persist(StorageLevel.MEMORY_AND_DISK)
分区(deepSeek生成)
分区(Partitioning)是RDD并行计算的基础,决定了数据如何分布在集群中。
分区机制
-
分区特点:
- 每个分区对应一个任务(task)
- 分区数决定并行度
- 合理分区能显著提高性能
-
分区类型:
- 默认分区:根据输入数据源自动确定
- Hash分区:根据键的哈希值分配
- Range分区:根据键的范围分配
- 自定义分区:实现Partitioner接口
-
分区操作:
// 查看分区数 rdd.partitions.size // 重新分区 val repartitioned = rdd.repartition(10) // 增加分区(触发shuffle) val coalesced = rdd.coalesce(5) // 减少分区(默认不触发shuffle) // 自定义分区 val partitioned = rdd.partitionBy(new MyPartitioner(5))
持久化与分区的关系(略)(deepSeek生成)
- 持久化会保存RDD的分区信息
- 重新分区后,如果不持久化,后续操作会重新计算
- 合理分区能提高持久化效率,减少数据倾斜
最佳实践(略)(deepSeek生成)
- 对需要多次使用的RDD进行持久化
- 根据集群资源和数据大小选择合适的持久化级别
- 避免过度分区导致小文件问题
- 数据倾斜时考虑自定义分区
- 持久化后建议立即执行一个action操作来触发实际缓存
示例:
val data = sc.textFile("largefile.txt")
.map(_.split(","))
.filter(_.length == 5)
.persist(StorageLevel.MEMORY_AND_DISK)
data.count() // 触发持久化
// 后续操作可以重用持久化的RDD
val result1 = data.map(...).reduce(...)
val result2 = data.filter(...).count()
一个例子
spark-shell
// 1. 读取文件(确保路径正确!)
val lines = sc.textFile("file:///home/xdt/桌面/data.txt")
// 2. 拆分单词 + 统计
val wordCounts1 = lines.flatMap(line => line.split(" ")) // 按空格分割
val wordCounts2 = wordCounts1.map(word => (word, 1)) // 每个单词计数 1
val wordCounts3 = wordCounts2.reduceByKey(_ + _) // 相同单词累加
// 3. 显示结果(前20个)
wordCounts3.take(20).foreach(println)
// 4. 保存结果
wordCounts3.saveAsTextFile("file:///home/xdt/桌面/wordcount_output")
Spark RDD API(scala) - jinggangshan - 博客园
3.持久化(缓存)
链太长了,遇到第一个动作才会触发持久化。(遇到行动操作,触发一次真正的从头到尾的计算。从头到尾计算那肯定代价很大,只要把持久化的RDD数据保存到内存,也就是缓存)
可以使用persist()方法对一个RDD标记为持久化。这个方法也是惰性的,要等到第一个动作触发才触发持久化。
rdd.cache()和persist()可以看成等价
4.分区
如何把RDD数据分到很多台机器上。
RDD分区原则:使分区的个数尽量等于集群中CPU核心(core)数。
键值对RDD
1.键值对RDD的创建
使用map()函数创建键值对RDD
2.常用的键值对RDD转换操作
reduceByKey(func) 返回的是按key聚合后的结果(如求和、计数等)
groupByKey() 返回的是按key分组后的值集合(未聚合)
sortByKey() 按照key排序
sortBy() 更灵活的排序
mapValues(func) 对每个value都应用一个函数,key不会变
join 内连接 (K,V1)和(K,V2)=>(K,(V1,V2))

浙公网安备 33010602011771号