网络<磁盘<内存
1.spark将数据读到内存中然后进行查询,底层是mr代码简洁,运行速度快
2.四种运行模式: Local多用于测试 Standalone(saprk独立集群) Mesos YARN最具前景(公司主流形式)
3.scala是单机的,spark可以是分布式的
引入依赖 scala三件套外加下面一个
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.4.5</version>
</dependency>
local:存储文件失败:本地部署
在本地Hadoop的bin下加上winutils插件,配置环境变量
1.RDD小知识点
RDD:弹性的分布式数据集
弹性:可以很多可以很少
分布式:数据分布在多台服务器中
数据集:从RDD中流过,边读边处理边输出
spqrk采用的也是mr读取数据的方法
block对应RDD中的分区,分区数越多并行度越高,在资源充足的情况下效率越高
RDD分区数在读取文件时默认等于切片的数量,生成分区数是大于最小分区数的3的倍数,小于3则为3,保证每一个分区数据量是差不多的 (sc.textFile("path",数量))
在mr上进行封装,代码简洁,可以读取一个目录下的文件
shuffle阶段,数据会落地到磁盘生成磁盘小文件
窄依赖的算子不能改变分区数,默认等于前一个rdd的分区数,宽依赖默认也是等于前面的,在shuffle阶段可以重新设置分区数
2.RDD五大特性
RDD五大特性:
1.LinesRDD由一组分区组成,读取文件默认一个block块对应一个分区,后面分区数和前面相同
2.函数(代码)作用在每个分区上的,有多少分区就有多少task(一一对应)
3.后面的RDD依赖前面的RDD,有shuffle的(宽依赖)宽依赖前是一个stage(map或者reduce端)
4.分区内的算子只能作用在kv格式的RDD上 groupbykey()
5.spark为task的计算提供了最佳的计算位置
3.算子(方法) 转换transformation 操作action
1.配置spark的基本四步操作
//获取SparkContext对象 有两种方法
val conf=new SparkConf()
conf.setMaster("local")
conf.setAppName("map")
val sc=new SparkContext(conf)
2.Transformation
map
filter
flatMap
sample 抽样
1.group和groupByKey
group和groupByKey的区别:
1.groupBy可以在任何类型的RDD上使用,groupByKey只能作用在kv格式的RDD上
2.groupByKey之后的RDD结构相对简单一些
3.groupByKey需要传输的数据量更小,性能更高
val groupByKeyRDD: RDD[(String, Iterable[Int])] = clazzAndAgeRDD.groupByKey()
val avgAgeRDD: RDD[(String, Double)] = groupByKeyRDD.map {
case (clazz: String, ages: Iterable[Int]) =>
val avgAge: Double = ages.sum.toDouble / ages.size
(clazz, avgAge)
}
/**
* groupBy和groupByKey的区别
* 1、代码:groupBy可以在任何类型的rdd上使用,groupByKey只能作用在kv格式的RDD上
* 2、groupByKey之后RDD的结构相对简单一点
* 3、性能:groupByKey shuffle过程需要传输的数据量比groupBy小,性能更高
*/
2.reducebykey
reduceByKey 比groupByKey性能更好
作用在kv格式的RDD Scala中没有
按照key对value做聚合的函数
操作后也会产生shuffle,数据量比groupByKey少,
因为reduceByKey在map端做预聚合算一次局部结果,减少shuffle过程传输数据量,
但是有些复杂逻辑做不了,比如计算方差
//取出班级和年龄
val clazzRDD: RDD[(String, Int)] =splitRDD.map{
case Array(_,_,age:String,_,clazz:String)=>
(clazz,1)
}
/**
* reduceByKey按照key对值进行聚合,需要传聚合函数,会产生shuffle
*/
val clazz_numRDD: RDD[(String, Int)] =clazzRDD.reduceByKey((x:Int, y:Int)=>x+y)
clazz_numRDD.foreach(println)
3.union
union 类型一样的RDD进行合并,但不对数据进行去重,逻辑层面进行了合并但是物理层面没有合并,代码一起写但是RDD还是两个,新的RDD分区数等于两者的和
4.join
数据: parallelize:根据一个kv格式的list转换构建成RDD
val kvRDD1: RDD[(String, String)] =sc.parallelize(List(("001","张三"),("002","李四"),("003","王五")))
val kvRDD2: RDD[(String, String)] =sc.parallelize(List(("001","张三"),("002","铁柱"),("003","姚欣"),("004","默认值")))
val kvRDD3: RDD[(String, Int)] =sc.parallelize(List(("001",23),("002",24),("003",25),("006",26)))
1.inner join 内关联,两边对应的key都有数据 常用
join 需要kv格式的RDD,相同的key进行合并 提取出一个key
val innerRDD: RDD[(String, (String, String))] =kvRDD1.join(kvRDD2)
innerRDD.foreach(println)
(003,(王五,姚欣))
(002,(李四,铁柱)) //这个结果没有用map方法展开
(001,(张三,张三))
2.leftOuterJoin : 以左表为基础,如果右表没有数据补null 常用
option: 可选的值,可能没有关联上,两个取值,有值或者None,有值的在case时写 some(值)
val leftjoin: RDD[(String, (String, Option[String]))] =kvRDD1.leftOuterJoin(kvRDD2)
//分开处理数据
leftjoin.map{
case(id:String,(name:String,Some(age)))=>
(id,name,age)
case(id:String,(name:String,None))=>
(id,name,1)
}.foreach(println)
3.fullOuterJoin 一边有数据就会出结果,没有的补null,3种情况都写
val fulljoin: RDD[(String, (Option[Int], Option[String]))] =kvRDD3.fullOuterJoin(kvRDD2)
fulljoin.map{
case(id:String,(Some(name),Some(age)))=>
(id,name,age)
case(id:String,(Some(name),None))=>
(id,name,1)
case(id:String,(None,Some(age)))=>
(id,1,age)
}.foreach(println)
(003,25,姚欣)
(004,1,默认值)
(002,24,铁柱)
(006,26,1)
(001,23,张三)
读取数据得到(id,总分)形式的数据
5.sortBy
sortby:指定一个字段进行排序,默认升序,追加 ascending=false 为降序
需要有一个可以进行排序的数据
//根据对key分组来求和
val sumsco: RDD[(String, Int)] =id_sco.reduceByKey((x:Int, y:Int)=>x+y)
val sortRDD: RDD[(String, Int)] =sumsco.sortBy(kv=>kv._2,ascending=false)
6.mapvalues
key不变,只对value做处理
val sumsco: RDD[(String, Int)] =id_sco.reduceByKey((x:Int, y:Int)=>x+y)
val mapValues:RDD[(String,Int)]=sumsco.mapValues(scp=>scp/10)
以往方法:sumsco.map(kv=>(kv._1,kv._2/10))
7.mapPartitions
mapPartitions
1.传入参数是String类型迭代器,在函数内对一个分区的数据进行处理,一次处理一个分区的数据
2.迭代器中是一个分区的数据,将分区内是数据一个个传给函数
3.得到分区数:println(linesRDD.getNumPartitions)
val mapRDD: RDD[String] = linesRDD.mapPartitions
((iter: Iterator[String]) => {
//在函数类对一个分区的数据进行处理
val words: Iterator[String] = iter.flatMap(line => line.split(","))
words
})
8.mapPartitionsWithIndex
mapPartitionsWithIndex 对应一个分区编号
linesRDD.mapPartitionsWithIndex{
case(index:Int,iter:Iterator[String])=>
println(s"当前处理的分区编号:$index")
iter
}.foreach(println)
//当前处理的分区编号:2
java,hadoop,hive
java,hadoop,hive
java,hadoop,hive
3.Actions
转换算子是将一个RDD转换成另一个RDD,转换算子是懒执行,需要一个action算子来触发执行
一个操作算子用来触发一次任务执行,同时每一个操作算子都会出发前面代码执行,结果不是RDD,具体结果具体分析
3.1 froeach 打印数据
打印两次,没有操作算子的话转换算子会失效
val sc=new SparkContext(conf)
val lineRDD: RDD[String] =sc.textFile("data/students.txt")
val jieguo: RDD[(String, String)] =lineRDD.map(_.split(",")).map{
case Array(id:String,_,_,_,clazz:String)=>
(id,clazz)
}
jieguo.foreach(println)
jieguo.foreach(println)
3.2 saveAsTextFile 保持数据
val studnetRDD: RDD[(String, String, Int, String, String)] = linesRDD
.map(_.split(","))
.map {
case Array(id: String, name: String, age: String, gender: String, clazz: String) =>
println("=======map============")
(id, name, age.toInt, gender, clazz)
}
//保持数据
studnetRDD.saveAsTextFile("data/temp")
//统计RDD行数
val count: Long = studnetRDD.count()
//将RDD转集合
val studentArr: Array[(String, String, Int, String, String)] = studnetRDD.collect()
//取top
val top: Array[(String, String, Int, String, String)] = studnetRDD.take(100)
3.3 count 统计行数
3.4 collect 将RDD转换成集合
3.5 take 取top
3.6 sum RDD必须可以求和(数字类型)
3.7 reduce 全局聚合
4.spark缓存
1. 如何让RDD中有数据? cache
在spark中对一个转换RDD多次使用,需要对这个RDD计算多次,因为RDD默认不保存数据
需要缓存的RDD进行:xxxRDD.cache() cache()默认的缓存级别是MEMORY_ONLY 保存在内存中不做压缩
当第一个任务执行的过程中会将数据缓存起来,后面执行的时候直接从缓存中获取数据,不需要重复进行计算了
2.缓存的级别和原则(缓存后存在哪里?)
RDD缓存的级别:磁盘,内存,堆外内存,序列化(是否压缩),副本(数量)
原则:1.如果RDD的数据量超过了内存限制,使用MEMORY_ONLY会导致内存溢出,值适合数据量不大内存充足的情况
2.如果将RDD的数据缓存在磁盘上,大部分情况和重新计算一次RDD的数据没有区别,重新读也是读磁盘,除了on disk之前还有很多逻辑
3.如果数据量比较大内存不是很够,可选择MEMORY_AND_DISK_SER,内存放不下再放磁盘,同时会对数据做压缩cpu执行时间换存储空间)
4.对同一个RDD多次使用时才执行缓存策略
5.索引:空间换时间
xxxRDD.persist(StorageLevel.MEMORY_AND_DISK_SER)
3.checkpoint 和 cache
checkpoint
将RDD的数据缓存到hdfs,任务失败了不会丢失
通过sc提前设置保存路径 sc.setCheckpointDir("data/checkpoint")
主要在spark streaming中使用,保证任务高可用
cache
将数据缓存在spark执行的服务器的内存或者磁盘上,任务失败数据就丢失