二次排序的案例
对下面两个文件(文件行内容有多个空格分分隔)进行二次排序:
文件:account
hadoop@apache 200
hive@apache 550
yarn@apache 580
hive@apache 159
hadoop@apache 300
hive@apache 258
hadoop@apache 150
yarn@apache 560
yarn@apache 260
文件:account1
zhang@apache 2
zhang@apache 5
zhang@apache 58
hadoop@apache 300
yarn@apache 100
在IDEA中写,代码如下:
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("二次排序").setMaster("local[2]")
val sc = new SparkContext(conf)
val rdd = sc.textFile("file:///E:\TIMDownload\\*") //构建 RDD,加载文件内容
.map(x=>x.replaceAll("\\s+"," ").split(" ")) //转换数据,将多个空格替换成一个空格,再按空格切分数据
.map(x=>(x(0),x(1))) //数组转换成 key,value
.repartition(1).groupByKey() //两个文件(分片),两个分区,要放到1个分区里面计算,所以要重分区,再按键值分组
.mapValues(x=>x.toList.sortBy(x=>x)) //对值进行排序(升序)
.sortByKey(true); //对键进行排序(升序)
println(rdd.toDebugString) //打印输出rdd依赖关系图
rdd.saveAsTextFile("file:///E:\\out") //结果保存到文本
}
rdd依赖关系如下:(从下向上)
(1) ShuffledRDD[10] at sortByKey at SecondSort.scala:12 []
+-(1) MapPartitionsRDD[9] at mapValues at SecondSort.scala:11 []
| ShuffledRDD[8] at groupByKey at SecondSort.scala:10 []
+-(1) MapPartitionsRDD[7] at repartition at SecondSort.scala:10 []
| CoalescedRDD[6] at repartition at SecondSort.scala:10 []
| ShuffledRDD[5] at repartition at SecondSort.scala:10 []
+-(3) MapPartitionsRDD[4] at repartition at SecondSort.scala:10 []
| MapPartitionsRDD[3] at map at SecondSort.scala:9 []
| MapPartitionsRDD[2] at map at SecondSort.scala:8 []
| file:///E:\TIMDownload\* MapPartitionsRDD[1] at textFile at SecondSort.scala:7 []
| file:///E:\TIMDownload\* HadoopRDD[0] at textFile at SecondSort.scala:7 []
再结合4040端口 webUI查看整个过程对的执行情况
执行结果:
(hadoop@apache,List(150, 200, 300, 300))
(hive@apache,List(159, 258, 550))
(yarn@apache,List(100, 260, 560, 580))
(zhang@apache,List(2, 5, 58))
行动操作定义
行动操作是第二种类型的 RDD 操作,它们会把最终求得的结果返回到驱动器程序,或者写入外部存储系统中,触发Job,调用runJob()方法 比如:collect、count
由于行动操作需要生成实际的输出,它们会强制执行那些求值必须用到的 RDD 的转化操作。
在大多数情况下,RDD 不能通过 collect() 收集到驱动器进程中,因为它们一般都很大。
此时,我们通常要把数据写到诸如 HDFS 或 Amazon S3 这样的分布式的存储系统中。
你可以使用 saveAsTextFile()、saveAsSequenceFile(),或者任意的其他行动操作来把 RDD 的 数据内容以各种自带的格式保存起来。
foreach
说明:将结果返回值执行器节点,而非驱动器
行动操作举例
aggregate聚合函数
def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
说明:aggregate用于聚合RDD中的元素,先使用seqOp将RDD中每个分区中的T类型元素聚合成U类型,
再使用combOp将之前每个分区聚合后的U类型聚合成U类型
特别注意:seqOp和combOp都会使用zeroValue的值,zeroValue的类型为U
//案例 1
val z = sc.parallelize(List(1,2,3,4,5,6), 2)
z.aggregate(0)(math.max(_,_), _ + _)
res40: Int = 9
说明:
step1:首先在第一个分区[0,1,2,3]中逐个元素执行math.max,结果为:3
step2:在第二个分区[0,4,5,6]中逐个元素执行math.max,结果为:6
stepn:在第N个分区中执行math.max,结果为:max
step:将所有分区结果执行combOp(_+_),0+3+6=9
z.aggregate(5)(math.max(_, _), _ + _)
res29: Int = 16
说明:
step1:初始值为 5
step2:第一分区(5, 1, 2, 3)中最大值math.max为 5
step3:第二分区(5, 4, 5, 6) 中最大值math.max为 6
step4:所有分区执行结果为 5 + 5 + 6 = 16
//案例 2
scala> val z = sc.parallelize(List("12","23","345","4567"),2)
z: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[3] at parallelize at <console>:24
scala> z.glom.collect
res11: Array[Array[String]] = Array(Array(12, 23), Array(345, 4567))
scala> z.aggregate("")((x,y) => math.max(x.length, y.length).toString, (x,y) => x + y)
res12: String = 42
scala> z.aggregate("")((x,y) => math.max(x.length, y.length).toString, (x,y) => x + y)
res13: String = 24
说明:
step1:第一个分区中执行math.max(x.length, y.length).toString代码
res0=:math.max(“”.length, “12”.length).toString = “2”
res1=:math.max(res0.length, “23”.length).toString = “2”
第一个分区最终返回值为:2
step2:第二个分区中执行math.max(x.length, y.length).toString代码
res2=:math.max(“”.length, “345”.length).toString = “3”
res3=:math.max(res2.length, “4567”.length).toString = “4”
第一个分区最终返回值为:4
step3:最后执行(x,y) => x + y :24 或 42
scala> z.aggregate("")((x,y) => math.min(x.length, y.length).toString, (x,y) => x + y)
res14: String = 11
说明:
step1:第一个分区中执行math.min(x.length, y.length).toString代码
res0=:math.min(“”.length, “12”.length).toString = “0”
res1=:math.min(res0.length, “23”.length).toString = “1”
第一个分区最终返回值为:1
step2:第二个分区中执行math.min(x.length, y.length).toString代码
res2=:math.min(“”.length, “345”.length).toString = “0”
res3=:math.min(res2.length, “4567”.length).toString = “1”
第一个分区最终返回值为:1
step3:最后执行(x,y) => x + y :11 或 11
scala> z.aggregate("12")((x,y) => math.min(x.length, y.length).toString, (x,y) => x + y)
res15: String = 1211
说明:
step1:第一个分区中执行math.min(x.length, y.length).toString代码
res0=:math.min(“12”.length, “12”.length).toString = “2”
res1=:math.min(res0.length, “23”.length).toString = “1”
第一个分区最终返回值为:1
step2:第二个分区中执行math.min(x.length, y.length).toString代码
res2=:math.min(“12”.length, “345”.length).toString = “2”
res3=:math.min(res2.length, “4567”.length).toString = “1”
第一个分区最终返回值为:1
step3:最后执行(x,y) => x + y :1211 或 1211
fold
def fold(zeroValue: T)(op: (T, T) => T): T
说明:fold理解为aggregate的简化
val a = sc.parallelize(List(1,2,3), 3)
a.fold(0)(_ + _)
res59: Int = 6
reduceByKeyLocally
def reduceByKeyLocally(func: (V, V) => V): Map[K, V]
该函数将RDD[K,V]中每个K对应的V值根据映射函数来运算,运算结果映射到一个Map[K,V]中,而不是RDD[K,V]
scala> val a = sc.parallelize(List("dog", "cat", "owl", "gnu", "ant"), 2)
a: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[0] at parallelize at <console>:24
scala> val b = a.map(x => (x.length, x))
b: org.apache.spark.rdd.RDD[(Int, String)] = MapPartitionsRDD[1] at map at <console>:26
scala> b.reduceByKeyLocally(_+_)
res0: scala.collection.Map[Int,String] = Map(3 -> dogcatowlgnuant)
RDD缓存(持久化)
为了能够多次使用同一个RDD,同时避免多次计算同一个RDD,可以让Spark对数据进行持久化
让 Spark 持久化 存储一个 RDD 时,计算出 RDD 的节点会分别保存它们所求出的分区数据
-
默认情况下,调用Persist()函数,实现RDD数据以反序列化形式持久化至内存中。
-
存储级别为【org.apache.spark.storage.StorageLevel】类,存储级别默认为 MEMRORY_ONLY
StorageLevel存储级别参数项为:磁盘、内存、堆外空间、反序列化和副本数
private var _useDisk: Boolean,
private var _useMemory: Boolean,
private var _useOffHeap: Boolean,
private var _deserialized: Boolean,
private var _replication: Int = 1)缓存级别StorageLevel如下:
val NONE = new StorageLevel(false, false, false, false)
val DISK_ONLY = new StorageLevel(true, false, false, false)
val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
val MEMORY_ONLY = new StorageLevel(false, true, false, true)
val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
val OFF_HEAP = new StorageLevel(true, true, true, false, 1)缓存案例: //在测试本地数据时,如果文件比较大,按根据块大小32MB来切分 def main(args: Array[String]): Unit = { val conf = new SparkConf().setAppName("持久化").setMaster("local[2]") val sc = new SparkContext(conf) val rdd = sc.textFile("file:///E:\\2.安装环境\\测试数据\\SogouQ\\SogouQ3.txt"); rdd.persist(); rdd.saveAsTextFile("file:///D:\\out5") Thread.sleep(10000000000l) }
-
如果要缓存的数据太多,内存中放不下,Spark 会自动利用最近最少使用(LRU)的缓存策略把最老的分区从内存中移除
对于仅把数据存放在内存中的缓存级别,下一次要用到 已经被移除的分区时,这些分区就需要重新计算。
但是对于使用内存与磁盘的缓存级别的分区来说,被移除的分区都会写入磁盘。不论哪一种情况,都不必担心你的作业因为缓存了太多数据而被打断
unpersist(),调用该方法可以手动把持久化的 RDD 从缓 存中移除
RDD依赖
- RDD依赖两种:宽依赖和窄依赖
- 定义:
- RDD依赖和RDD分区
RDD分区–>决定Task(任务)数
RDD依赖–>决定Stage(阶段)数 - 案例分析Wordcount
a. 首先通过RDD的依赖关系划分Stage(阶段),划分阶段依据:宽依赖,即(shuffledRDD),从后往前找
一旦找到宽依赖,就要来一刀,划分出阶段
b. 每个Stage阶段划分Task(任务)数,任务数取决于RDD的分区数,执行从前往后
Job总Task(任务)数:各Stage阶段任务数的总和
c.通过todebugString()方法获取血统依赖关系,划分阶段由上至下,执行任务由下至上!
案例:二次排序依赖图如下:
(1) ShuffledRDD[10] at sortByKey at SencordarySort.scala:12 [] //ShuffledRDD第一次划分Stage
+-(1) MapPartitionsRDD[9] at mapValues at SencordarySort.scala:11 []
| ShuffledRDD[8] at groupByKey at SencordarySort.scala:10 [] //ShuffledRDD第二次划分Stage
+-(1) MapPartitionsRDD[7] at repartition at SencordarySort.scala:10 []
| CoalescedRDD[6] at repartition at SencordarySort.scala:10 []
| ShuffledRDD[5] at repartition at SencordarySort.scala:10 [] //ShuffledRDD第三次划分Stage
+-(3) MapPartitionsRDD[4] at repartition at SencordarySort.scala:10 []
| MapPartitionsRDD[3] at map at SencordarySort.scala:9 []
| MapPartitionsRDD[2] at map at SencordarySort.scala:8 []
| file:///E:\2.安装环境\测试数据\二次排序\* MapPartitionsRDD[1] at textFile at SencordarySort.scala:7 []
| file:///E:\2.安装环境\测试数据\二次排序\* HadoopRDD[0] at textFile at SencordarySort.scala:7 []
Spark提交过程
- 构建sc对象,[RDD创建]–>[RDD转换]–>[RDD转换]–>…–>RDD行动==>Job
{…DAG…}
说明:构建DAG,将DAG发送给DAGScheduler - DAGScheduler(DAG调度器)作用:
通过血统(RDD的依赖关系:宽和窄)划分Stage,并将Stage组合成TaskSet(任务数)发送给相对应集群服务器的TaskScheduler - TaskSchedule(任务调度器)作用:
将TaskSet转化为Task,并根据Task调度相应Worker的Executor - Exextor执行器:运行Task