Spark
文章目录
spark与mr对比
mapreduce中多个job需要多次读写磁盘。而spark比mapreduce快,两个mapreduce之间不需要落地(shuffle是会落地到磁盘的),省去了多次读写磁盘,数据加载在内存中,每次直接读写内存就快了,但当数据量很大时(达到10T、1P),会内存溢出,这也是spark没有mr稳定的原因之一。
spark比mr快:
-
spark可以尽量将数据放在内存中计算(cache)
-
多个shuffle中间结果不需要落地
-
spark是粗粒度资源申请(在任务执行之前会将所需要的资源全部申请下来),
而mr是细粒度资源申请,每一个task执行的时候单独申请资源。
RDD五大特性
RDD:弹性的分布式数据集。
- RDD是由一组分区组成,默认一个切片对应一个分区;
- 算子实际是作用在每一个分区上面,每一个分区由一个task处理;
- RDD之间有一系列的依赖关系,后面一个RDD依赖前一个RDD。依赖分为宽依赖(有shuffle)和窄依赖(没有shuffle),在宽依赖的位置切分不同的stage:宽依赖之前是一个stage,宽依赖之后是一个stage。stage相当于mr中的map端或reduce端,stage是一组并行计算的task。
- 分区类的算子只能作用在KV格式的RDD上;
- Spark为task的计算提供了最佳的计算位置,移动计算而不是移动数据,会尽量将task发送到数据所在节点执行。
注意:RDD默认不保存数据,数据只是流过每一个RDD。
WordCount
- 本地运行
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo1WordCount {
def main(args: Array[String]): Unit = {
/** 创建spark运行环境
*/
//配置文件对象
val conf = new SparkConf()
//会在yarn中显示的名称
conf.setAppName("wc")
//运行方式,local:本地运行,
conf.setMaster("local")
//构建spark 上下文对象。 SparkContext是spark的入口
val sc: SparkContext = new SparkContext(conf)
//1、读取数据
//RDD: 弹性的分布式数据集(可以看作是scala中的一个集合来使用)
val linesRDD: RDD[String] = sc.textFile("spark/data/words")
//2、将每一行的多个单词拆分出来
//在spark中数据处理的方法一般称为算子,算子处理完了之后会返回一个新的RDD
val wordsRDD: RDD[String] = linesRDD.flatMap(line => line.split(","))
//3、按照单词进行分组,在底层会产生shuffle
val groupRDD: RDD[(String, Iterable[String])] = wordsRDD.groupBy(word => word)
//4、统计单词的数量
val countRDD: RDD[String] = groupRDD.map(kv => {
val word: String = kv._1
//一个组内所有单词
val iter: Iterable[String] = kv._2 //Iterable是迭代器
//单词的数量
val count: Int = iter.size
//返回
word + "," + count
})
//5、保存数据(如果目录已存在会报错)
countRDD.saveAsTextFile("spark/data/count")
}
}
- 将spark代码提交到服务器中运行
整合在yarn上
在公司一般不适用standalone模式,因为公司一般已经有yarn,不需要搞两个资源管理框架
1、spark on yarn client模式: 日志在本地输出,一般用于上线前测试
spark-submit --class org.apache.spark.examples.SparkPi --master yarn-client --executor-memory 512M --num-executors 2 spark-examples_2.11-2.4.5.jar 100
2、spark on yarn cluster模式: 上线使用,不会再本地打印日志。 减少io
spark-submit --class org.apache.spark.examples.SparkPi --master yarn-cluster --executor-memory 512m --num-executors 2 --executor-cores 1 spark-examples_2.11-2.4.5.jar 100
获取yarn程序执行日志,执行成功之后才能获取到
yarn logs -applicationId application_1560967444524_0003
package com.shujia.spark
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo2WordCountSubmit {
def main(args: Array[String]): Unit = {
/**
*
* 将spark代码提交到服务器中运行
* 1、删除本地 conf.setMaster("local")
* 2、修改输入输出路径
*
* 将项目打包上传到服务器
* 提交任务
* spark-submit --class com.shujia.spark.Demo2WordCountSubmit --master yarn-client spark-1.0.jar
*
*/
//创建spark运行环境:
//配置文件对象
val conf = new SparkConf()
//会在yarn中显示的名称
conf.setAppName("wc")
//运行方式,local:本地运行,
// conf.setMaster("local")
//构建spark 上下文对象。SparkContext是spark的入口
val sc: SparkContext = new SparkContext(conf)
//1、读取数据
//将读取文件的路径修改为hdfs的路径
val linesRDD: RDD[String] = sc.textFile("/data/words")//默认最小两个分区
//2、将每一行的多个单词拆分出来
//在spark中数据处理的方法一般称为算子,算子处理完了之后会返回一个新的RDD
val wordsRDD: RDD[String] = linesRDD.flatMap(line => line.split(","))
//3、按照单词进行分组,在底层会产生shuffle
val groupRDD: RDD[(String, Iterable[String])] = wordsRDD.groupBy(word => word)
//4、统计单词的数量
val countRDD: RDD[String] = groupRDD.map(kv => {
val word: String = kv._1
//一个组内所有单词
val iter: Iterable[String] = kv._2
//单词的数量
val count: Int = iter.size
//返回
word + "," + count
})
///删除输出路径
//使用hadoop的api
val configuration = new Configuration()
val fileSystem: FileSystem = FileSystem.get(configuration)
if (fileSystem.exists(new Path("/data/count"))) {
fileSystem.delete(new Path("/data/count"), true)
}
//5、保存数据
countRDD.saveAsTextFile("/data/count")
}
}
算子
在spark中数据处理的方法一般称为算子,算子处理完了之后会返回一个新的RDD。
-
转换算子:从一个rdd转换成另一个rdd,是
rdd之间的转换
。 转换算子是懒执行,需要action算子触发执行。
-
操作算子:触发任务执行,每一个action算子都会触发一个job
构建RDD的方式:
- 读取文件
- 基于scala集合构建RDD
- map
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo3Map {
def main(args: Array[String]): Unit = {
/**
* 转换算子:从一个Rdd转换成另一个rdd,是rdd之间的转换,
* 转换算子是懒执行,需要action算子触发执行
*
* 操作算子: 触发任务执行,每一个action算子都会触发一个job
*
*/
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
/**
* 构建RDD的方式
* 1、读取文件
* 2、基于scala集合构建RDD
*
* parallelize方法可以将集合变成一个RDD
*/
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8))
println("map之前")
//转换算子是懒执行,需要action算子触发执行,每一个action算子都会触发执行一次
/**
* map算子:是一个转换算子,从一个RDD转换成另一个RDD
*
* 处理完之后数据量行数不变
*
*/ //将rdd1的数据一个一个传给后面的函数
val rdd2: RDD[String] = rdd1.map((i: Int) => {// U 可以自定义
println("map" + i)
i.toString
})
println("map之后")
//操作算子。触发job运行,每一个操作算子都会触发一个job。所以测试时打印在后面了
rdd2.foreach(println)
rdd2.foreach(println)
while (true) {//为了在网页上面看job任务
}
}
}
- mapPartitions
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo4MapPartition {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
/**
* mapPartitions: 一次处理一个分区的数据,需要返回一个迭代器或者集合
*
* mapPartitionsWithIndex: 多了一个分区编号
*
*/
val rdd1: RDD[String] = sc.textFile("spark/data/words")
val rdd2: RDD[String] = rdd1.mapPartitions((iter: Iterator[String]) => {
println("一个分区")
//iter
iter.flatMap(line => line.split(","))
})
//rdd2.foreach(println)
val rdd3: RDD[String] = rdd1.mapPartitionsWithIndex((index: Int, iter: Iterator[String]) => {
println("分区编号:" + index)
iter
})
rdd3.foreach(println)
}
}
- filter
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object DEmo5Filter {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
/**
* filter: 过滤数据,函数返回true保留数据,函数返回false 过滤数据
*/
//基于集合构建RDD
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 0))
val rdd2: RDD[Int] = rdd1.filter((i: Int) => i % 2 == 1)
rdd2.foreach(println)
}
}
- flatMap
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo6FlatMap {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
/**
* flatMap: 将一行数据拆分成多行数据
*/
val rdd1: RDD[String] = sc.parallelize(List("java,spark,java", "hadoop,hive,hbase"))
val rdd2: RDD[String] = rdd1.flatMap((line: String) => line.split(","))
rdd2.foreach(println)
}
}
- sample
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo7Sample {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
/**
* sample:抽样
*/
val rdd1: RDD[String] = sc.textFile("spark/data/students.txt")
val rdd2: RDD[String] = rdd1.sample(true, 0.1)
rdd2.foreach(println)
}
}
- groupBy
package com.shujia.spark
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo8GroupBy {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
/**
* groupBy: 指定一个列进行分组
*/
val rdd1: RDD[String] = sc.textFile("spark/data/students.txt")
val rdd2: RDD[(String, Iterable[String])] = rdd1.groupBy(line => line.split(",")(4))
// rdd2.foreach(println)
//将rdd转换成kv格式,就可以使用groupByKey了
val rdd3: RDD[(String, String)] = rdd1.map(line => {
val clazz: String = line.split(",")(4)
(clazz, line)
})
/**
* groupByKey: 通过key进行分组,将通过一个班级的学生分到同一个组内
* 只能作用在kv格式的rdd上
*/ //就是通过key进行分组,所以方法里面不用穿东西了
val rdd4: RDD[(String, Iterable[String])] = rdd3.groupByKey()
rdd4.foreach(println)
}
}
- reduceByKey
package com.shujia.spark
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo9RedueByKey {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
val rdd1: RDD[String] = sc.textFile("spark/data/students.txt")
val rdd2: RDD[(String, Int)] = rdd1.map(line => {
val clazz: String = line.split(",")(4)
(clazz, 1)
})
//统计班级的人数
val rdd3: RDD[(String, Iterable[Int])] = rdd2.groupByKey()
val rdd4: RDD[(String, Int)] = rdd3.map(kv => {
val clazz: String = kv._1
//班级的人数
val count: Int = kv._2.sum
(clazz, count)
})
// rdd4.foreach(println)
/**
* reduceByKey: 通过key对value进行聚合,一般用于加和计算,需要传入一个聚合函数
* 效率比groupBykey高,reduceBykey可以在map端进行预聚合, (可以减少shuffle过程中传输的数据量)
*
* reduceByKey只能用于聚合操作,功能比groupBykeu弱,性能更高
*/
val rdd5: RDD[(String, Int)] = rdd2.reduceByKey((x, y) => x + y)
rdd5.foreach(println)
//简写,函数的参数只使用了一次,可以通过下划线代替
val rdd6: RDD[(String, Int)] = rdd2.reduceByKey(_ + _)
while (true) {
}
}
}
- union
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo10Union {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4))
val rdd2: RDD[Int] = sc.parallelize(List(5, 6, 7, 8, 9))
/**
* getNumPartitions:获取rdd分区数
* 不是一个算子,只是一个普通的方法
*/
println("rdd1:" + rdd1.getNumPartitions)
println("rdd2:" + rdd2.getNumPartitions)
/**
* union:将两个rdd合并成一个,在屋里层面没有合并,只是在代码层面合并
*
* 两个rdd的类型必须一致
*
* 新的rdd分区数量等于合并之前rdd分区数量之和
*/
val unionRDD: RDD[Int] = rdd1.union(rdd2)
println("unionRDD:" + unionRDD.getNumPartitions)
unionRDD.foreach(println)
}
}
- join
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo11Join {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
val nameRDD: RDD[(String, String)] = sc.parallelize(List(("001", "张三"), ("002", "李四"), ("003", "王五"), ("004", "小伟")))
val ageRDD: RDD[(String, Int)] = sc.parallelize(List(("001", 23), ("002", 24), ("003", 25)))
/**
* join: 默认是inner join 。 两个rdd都必须是kv格式
*/
val joinRDD: RDD[(String, (String, Int))] = nameRDD.join(ageRDD)
//join之后整理数据
val rdd3: RDD[(String, String, Int)] = joinRDD.map(kv => {
val id: String = kv._1
val name: String = kv._2._1
val age: Int = kv._2._2
(id, name, age)
})
//如果rdd结构比较复杂,可以通过case 简写,提高可读性
val rdd4: RDD[(String, String, Int)] = joinRDD.map {
//一行一行对rdd的数据进行匹配(匹配类型)。格式匹配上了就会执行
case (id: String, (name: String, age: Int)) => {
(id, name, age)
}
}
// rdd4.foreach(println)
val leftJoinRDD: RDD[(String, (String, Option[Int]))] = nameRDD.leftOuterJoin(ageRDD)
val rdd5: RDD[(String, String, Int)] = leftJoinRDD.map(kv => {
val id: String = kv._1
val name: String = kv._2._1
val age: Int = kv._2._2.getOrElse(0)
(id, name, age)
})
val rdd6: RDD[(String, String, Int)] = leftJoinRDD.map {
//匹配关联成功的情况
case (id: String, (name: String, Some(age))) => {
(id, name, age)
}
//匹配没有关联上的情况
case (id: String, (name: String, None)) => {
(id, name, 0)
}
}
rdd6.foreach(println)
}
}
- mapValues
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo12MapValues {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
val ageRDD: RDD[(String, Int)] = sc.parallelize(List(("001", 23), ("002", 24), ("003", 25)))
/**
*
* mapValues:处理kv格式的rdd, 处理value key 不变
*/
val rdd1: RDD[(String, Int)] = ageRDD.mapValues(value => value + 1)
rdd1.foreach(println)
}
}
- sortBy
package com.shujia.spark
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo13Sort {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
val ageRDD: RDD[(String, Int)] = sc.parallelize(List(("001", 231), ("002", 24), ("003", 25)))
/**
* sortBy: 指定一个排序的列, 默认是升序(true)
* sortByKey : 通过key进行排序
*/
val sortRDD: RDD[(String, Int)] = ageRDD.sortBy(kv => kv._2, ascending = false)
sortRDD.foreach(println)
}
}
-
操作算子
-
每一个action算子都会独立触发一个job任务
package com.shujia.spark
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo14Action {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
val rdd1: RDD[String] = sc.textFile("spark/data/students.txt")
/**
* count: 统计rdd的行数
* 会触发任务执行
*/
println(rdd1.count())
/**
* foreach:遍历数据,一般只能用于本地测试. 将前面的数据一行一行传递后面打印
* foreachPartition: 一次处理一个分区的数据, 一般用于将数据保存到外部数据的时候
*/
rdd1.foreach(println)
rdd1.foreachPartition(iter => {
println("一个分区")
iter.foreach(println)
})
/**
* collect: 将rdd转换成集合,将rdd的数据拉取到内存中,如果rdd数据量比较大会导致内存溢出
*
* 会内存溢出,这也是spark没有mapreduce稳定的原因
*/
val array: Array[String] = rdd1.collect()
println(array)
/**
* reduce: 全局聚合
*
* select sum(age) from student
*/
val ages: RDD[Int] = rdd1.map(line => line.split(",")(2).toInt)
val sumAge: Int = ages.reduce((x, y) => x + y)
println(sumAge)
/**
* save:将数据保存到hdfs中,如果输出目录已存在会报错
*/
rdd1.saveAsTextFile("spark/data/out1")
while (true) {
}
}
}
- PI
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.immutable
import scala.util.Random
object Demo15PI {
def main(args: Array[String]): Unit = {
//快速生成一个大集合
val list: immutable.Seq[Int] = 0 to 10000000
//构建spark环境
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
//将scala集合转换成RDD
val dataRDD: RDD[Int] = sc.parallelize(list)
//随机生成点
val pointRDD: RDD[(Double, Double)] = dataRDD.map(i => {
//随机生成点(x,y) 范围是-1,1
val x: Double = Random.nextDouble() * 2 - 1
val y: Double = Random.nextDouble() * 2 - 1
(x, y)
})
//取出园内的点
val filterRDD: RDD[(Double, Double)] = pointRDD.filter {
case (x: Double, y: Double) => {
x * x + y * y <= 1
}
}
//通过公式计算pi:
val pi: Double = filterRDD.count().toDouble / list.length * 4
println("pi = " + pi)
}
}
缓存
- Cache
spark的rdd中默认不保存数据,当对rdd使用多次时可以将rdd进行缓存
MEMORY_ONLY:数据量不大,内存足够。
MEMORY_AND_DISK_SER:数据量很大,超过内存限制。
package com.shujia.spark
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel
object Demo16Cache {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
val student: RDD[String] = sc.textFile("spark/data/students.txt")
val mapStudent: RDD[String] = student.map(line => {
println("map----")
line
})
/**
* 对多次使用的RDD进行缓存, 默认cache是将数据缓存在内存中
*
*/
// mapStudent.cache()
/**
* 持久化级别选择
* 1、如果rdd的数据量不大 ,没有超过内存的限制---> MEMORY_ONLY
* 2、如果数据操超过了内存的限制 ---> MEMORY_AND_DISK_SER (压缩之后放内存内存放不下放磁盘)
* 不管压不压缩放内存都比放磁盘要块,所有尽量将数据压到内存中
*
*
* 缓存实际上是将数据缓存在执行task所在的服务器的内存或者磁盘上
*
*/
mapStudent.persist(StorageLevel.MEMORY_AND_DISK_SER)
//统计班级的人数
val clazzKVRDD: RDD[(String, Int)] = mapStudent.map(line => (line.split(",")(4), 1))
val clazzNumRDD: RDD[(String, Int)] = clazzKVRDD.reduceByKey(_ + _)
clazzNumRDD.foreach(println)
//统计性别的人数
val genderKVRDD: RDD[(String, Int)] = mapStudent.map(line => (line.split(",")(3), 1))
val genderNumRDD: RDD[(String, Int)] = genderKVRDD.reduceByKey(_ + _)
genderNumRDD.foreach(println)
}
}
- Checkpoint:将rdd的数据保存到hdfs中
package com.shujia.spark
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo17Checkpoint {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
//设置checkpoint的路径
sc.setCheckpointDir("spark/data/checkpoint")
val student: RDD[String] = sc.textFile("spark/data/students.txt")
val mapStudent: RDD[String] = student.map(line => {
println("map----")
line
})
/**
* checkpoint: 将rdd的数据保存到hdfs中,
* 1、当第一个job执行完成之后会从最后一个rdd向前回溯,对调用了checkpoint的rdd打上标记
* 2、另启动一个job重新计算rdd的数据,并将rdd的数据保存到hdfs
*
* 在checkpoint之前进行cache 优化效率
*
*
* 主要在spark streaming 中使用
*
*/
mapStudent.cache()
mapStudent.checkpoint()
//统计班级的人数
val clazzKVRDD: RDD[(String, Int)] = mapStudent.map(line => (line.split(",")(4), 1))
val clazzNumRDD: RDD[(String, Int)] = clazzKVRDD.reduceByKey(_ + _)
clazzNumRDD.foreach(println)
//统计性别的人数
val genderKVRDD: RDD[(String, Int)] = mapStudent.map(line => (line.split(",")(3), 1))
val genderNumRDD: RDD[(String, Int)] = genderKVRDD.reduceByKey(_ + _)
genderNumRDD.foreach(println)
val genderKVRDD1: RDD[(String, Int)] = mapStudent.map(line => (line.split(",")(3), 1))
val genderNumRDD1: RDD[(String, Int)] = genderKVRDD1.reduceByKey(_ + _)
genderNumRDD1.foreach(println)
}
}
广播变量和累加器
-
Driver端:spark程序的主程序,Driver也是一个独立的JVM;
-
Executor:执行器,执行task,独立的JVM。
算子外面的代码运行在Driver端,算子里的代码会被封装成Task发送到Executor中执行。
广播变量
:在算子内使用算子外面的一个变量时,可以将这个变量进行广播,广播后减少变量副本的数量;如果不广播,每一个task中都会有一个变量副本,广播之后Executor中只有一个变量副本,一般task>>>>executor的数量。
累加器
:当在算子内修改算子外的一个普通变量时,是不会生效的。
- 累加器
package com.shujia.spark
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
import org.apache.spark.util.LongAccumulator
import scala.collection.mutable.ListBuffer
object Demo18Acc {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
val student: RDD[String] = sc.textFile("spark/data/students.txt")
/**
* 算子内的代码运行在Executor,算子外面的代码运行在Driver端
* 在算子内修改算子外的变量不会生效
*
* Executor:执行器,执行task,独立的JVM进程
* Driver端:spark程序的主程序,Driver也是一个独立的JVM
*/
var i = 0
student.foreach(line => {
i += 1
println(line)
})
println(i)
/**
* 累加器:只能用来做累加计算
* 1、在每一个task中进行局部累加
* 2、当job执行完成之后,在driver端进行汇总
*/
//定义一个累加器:1、在driver端定义; 2、在算子内累加; 3、在driver端读取累加结果
val accumulator: LongAccumulator = sc.longAccumulator
student.foreach(line => {
//累加器可以在算子内进行累加
accumulator.add(1)
println(line)
})
//读取累加器的结果
println(accumulator.value)
/* student.foreach(line => {
//在spark的算子内不能再使用rdd
//因为算子中的代码会被封装成一个task发生到Executor中执行,rdd 本身就是一个弹性的分布式数据集,不能在网络中传输
student.foreach(l => {
println(l)
})
})*/
}
}
package com.shujia.spark
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo19Bro {
def main(args: Array[String]): Unit = {
//构建spark环境
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("map")
val sc = new SparkContext(conf)
val student: RDD[String] = sc.textFile("spark/data/students.txt")
/* val ids = List("1500100009", "1500100005", "1500100007")
val filterRDD: RDD[String] = student.filter(line => {
val id: String = line.split(",")(0)
ids.contains(id)
})*/
//filterRDD.foreach(println)
/**
* 广播变量
* :使用广播变量副本会减少
*/
val ids = List("1500100009", "1500100005", "1500100007")
//在Driver将一个变量广播
val broad: Broadcast[List[String]] = sc.broadcast(ids)
val filterRDD: RDD[String] = student.filter(line => {
val id: String = line.split(",")(0)
//在算子内获取广播变量中的数据
val broadValue: List[String] = broad.value
broadValue.contains(id)
})
filterRDD.foreach(println)
/**
* 使用限制
* 1、广播变量只能在算子外面定义,只能在算子里面使用
*/
}
}
package com.shujia.spark
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo19Partition {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
//会在yarn中显示的名称
conf.setAppName("wc")
/**
* spark shuffle之后rdd默认的分区数据
*
*/
conf.set("spark.default.parallelism", "3")
conf.setMaster("local")
val sc: SparkContext = new SparkContext(conf)
/**
* 第一个rdd的分区数据默认等于切片数,可以指定一个最小分区数,最小分区所默认是2
*
*/
val linesRDD: RDD[String] = sc.textFile("spark/data/words", 10)
println("linesRDD:" + linesRDD.getNumPartitions)
/**
* 没有shuffle算子----->后面的rdd分区数默认等于父rdd分区数
*
*/
val wordRDD: RDD[String] = linesRDD.flatMap(_.split(","))
println("wordRDD:" + wordRDD.getNumPartitions)
val kvRDD: RDD[(String, Int)] = wordRDD.map((_, 1))
println("kvRDD:" + kvRDD.getNumPartitions)
/**
*
* countRDD 分区数决定因素
*
* 1、如果没有指定分区数据默认等于父rdd分区数
* 2、shuffle类的算子可以手动指定分区数据(reduce的数量)
*
*
* 分区数据决定优先级: 手动指定---> 默认分区数(spark.default.parallelism)---> 父rdd分区数
*
*/
val countRDD: RDD[(String, Int)] = kvRDD.reduceByKey(_ + _, 2)
println("countRDD:" + countRDD.getNumPartitions)
/**
* repartition: 改变rdd分区数据,会产生shuffle, 不做任何逻辑处理
*
* 可以用于提供并行度
*
*/
val repartitionRDD: RDD[(String, Int)] = countRDD.repartition(10)
println("repartitionRDD:" + repartitionRDD.getNumPartitions)
repartitionRDD.foreach(println)
while (true) {
}
}
}
练习
package com.shujia.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo20Student {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setAppName("student")
.setMaster("local[4]")
val sc = new SparkContext(conf)
/**
*
* 2、统计总分大于年级平均分的学生 [学号,姓名,班级,总分]
* 1、统计总分
* 2、统计年级平均分
* 3、过滤总分是否打印平均分
* 4、整理数据
*
*/
//1、读取数据
val scores: RDD[String] = sc.textFile("spark/data/score.txt")
scores.cache()
//转换成kv格式
val kvScore: RDD[(String, Double)] = scores.map(line => {
val split: Array[String] = line.split(",")
(split(0), split(2).toDouble)
})
/// 统计总分
val sumScore: RDD[(String, Double)] = kvScore.reduceByKey(_ + _)
//统计平均分
val avgScore: Double = sumScore.map(_._2).sum() / sumScore.count()
println(s"年级平均分:$avgScore")
//过滤总分是否打印平均分
val filterScore: RDD[(String, Double)] = sumScore.filter(_._2 > avgScore)
//管理学生表整理数据
val students: RDD[String] = sc.textFile("spark/data/students.txt")
//将学生表转换成kv格式
val kvStudent: RDD[(String, (String, String))] = students.map(line => {
val split: Array[String] = line.split(",")
val id: String = split(0)
val name: String = split(1)
val clazz: String = split(4)
(id, (name, clazz))
})
//关联学生信息表
val joinRDD: RDD[(String, (Double, (String, String)))] = filterScore.join(kvStudent)
//整理数据
val resultRDD: RDD[String] = joinRDD.map {
case (id: String, (sumScore: Double, (name: String, clazz: String))) =>
s"$id,$name,$clazz,$sumScore"
}
//保存数据
resultRDD.saveAsTextFile("spark/data/sumScore")
}
}
package com.shujia.spark
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo21Student {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setAppName("student")
.setMaster("local[4]")
val sc = new SparkContext(conf)
/**
*
*
* 3、统计每科都及格的学生 [学号,姓名,班级,科目,分数]
* 1、将所有都及格的分数和对应的学号取出来
* 2、统计每一个及格科目数
* 3、过滤没有都及格的学生
* 4、整理数据
*/
//1、读取数据
val scores: RDD[String] = sc.textFile("spark/data/score.txt")
val kvScore: RDD[(String, (String, Double))] = scores.map(line => {
val split: Array[String] = line.split(",")
val id: String = split(0)
val cId: String = split(1)
val sco: Double = split(2).toDouble
(cId, (id, sco))
})
//读取科目表
val cource: RDD[String] = sc.textFile("spark/data/cource.txt")
val kvCource: RDD[(String, Double)] = cource.map(line => {
val split: Array[String] = line.split(",")
val id: String = split(0)
val sumScore: Double = split(2).toDouble
(id, sumScore)
})
//关联分数表和科目表
val joinRDD: RDD[(String, ((String, Double), Double))] = kvScore.join(kvCource)
//过滤不及格的分数
val filterRDD: RDD[(String, ((String, Double), Double))] = joinRDD.filter {
//没有使用到的列可以用下划线代替
case (_: String, ((_: String, sco: Double), sumScore: Double)) =>
sco >= sumScore * 0.6
}
val idRDD: RDD[(String, Int)] = filterRDD.map {
case (_: String, ((id: String, _: Double), _: Double)) => {
(id, 1)
}
}
//统计每个学生及格的科目数
val value: RDD[(String, Int)] = idRDD.reduceByKey(_ + _)
val filterStudent: RDD[(String, Int)] = value.filter(_._2 == 6)
val ids: Array[String] = filterStudent.map(_._1).collect()
//整理数据
val studentScore: RDD[String] = scores.filter(line => ids.contains(line.split(",")(0)))
studentScore.foreach(println)
}
}
package com.shujia.spark
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo22Student {
def main(args: Array[String]): Unit = {
/**
* 4、统计偏科最严重的前100名学生 [学号,姓名,班级,科目,分数
* 偏科程度判断依据:通过方差判断偏科程度
*
*/
val conf: SparkConf = new SparkConf()
.setAppName("student")
.setMaster("local[4]")
val sc = new SparkContext(conf)
//读取分数表
val scores: RDD[String] = sc.textFile("spark/data/score.txt")
//科目表
val cource: RDD[String] = sc.textFile("spark/data/cource.txt")
val kvCource: RDD[(String, Double)] = cource
.map(_.split(","))
.map(arr => (arr(0), arr(2).toDouble))
//key 是科目编号,value是科目总分
val courceMap: collection.Map[String, Double] = kvCource.collectAsMap()
//广播集合
val bro: Broadcast[collection.Map[String, Double]] = sc.broadcast(courceMap)
/**
* map join , 将小表加载到内存在map端进行表关联
* 限制: 不能用于两大表的关联,
*
*/
//关联科目表
val scoreCou: RDD[(String, Double, Double)] = scores.map(line => {
val split: Array[String] = line.split(",")
val id: String = split(0)
val cId: String = split(1)
val sco: Double = split(2).toDouble
//通过科目编号获取科目总分
val sumScore: Double = bro.value.getOrElse(cId, 100)
(id, sco, sumScore)
})
//对分数进行归一化。将不同范围的分数拉到同一个级别上
val oneScore: RDD[(String, Double)] = scoreCou.map {
case (id: String, sco: Double, sumScsore: Double) =>
(id, sco / sumScsore)
}
//计算学生的平均值
val groupRDD: RDD[(String, Iterable[Double])] = oneScore.groupByKey()
val avgSco: RDD[(String, Double)] = groupRDD.map {
case (id: String, scos: Iterable[Double]) =>
(id, scos.sum / 6)
}
val joinRDD: RDD[(String, (Double, Double))] = oneScore.join(avgSco)
//计算分数和平均分的差值
val chapingRDD: RDD[(String, Double)] = joinRDD.map {
case (id: String, (sco: Double, acgSc: Double)) =>
(id, (sco - acgSc) * (sco - acgSc))
}
//差值的和
val fangRDD: RDD[(String, Double)] = chapingRDD.reduceByKey(_ + _)
//按照方差降序排名
val sortfangRDD: RDD[(String, Double)] = fangRDD.sortBy(_._2, ascending = false)
//取出前100
val top100Id: Array[String] = sortfangRDD.take(100).map(_._1)
//取出这些学生的分数
val filterScore: RDD[String] = scores.filter(line => top100Id.contains(line.split(",")(0)))
filterScore.foreach(println)
}
}
资源调度(yarn -client)
在本地启动一个Driver,本地打印日志。
优点:本地打印日志,可以查看详细运行日志,方便测试;
缺点:Driver会和集群中Executor进行大量通信,会导致本地服务器网卡流量剧增;
每一个sparkDriver都会占用一个端口,端口会不够用。
资源调度(yarn-cluster)
用于上产环境,本地不会启动Driver,会随即在集群中的某个节点上启动Driver。本地没有详细日志。
优点:Driver在集群中随机的一个节点,不会导致网卡流量剧增。
缺点:在本地看不到日志,不方便测试。
blockManager
executor中不光有线程池。还都会有一个blockmanager,用于管理executor中的数据:
- rdd的缓存数据。
- 广播变量和累加器。
- shuffle过程中的文件。
spark shuffle
package com.shujia.spark
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo19Partition {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
//会在yarn中显示的名称
conf.setAppName("wc")
//spark shuffle之后rdd默认的分区数据
conf.set("spark.default.parallelism", "3")
conf.setMaster("local")
val sc: SparkContext = new SparkContext(conf)
/**
* 第一个rdd的分区数据默认等于切片数,可以指定一个最小分区数,最小分区所默认是2
*
* 没有shuffle算子----->后面的rdd分区数默认等于父rdd分区数
*/
val linesRDD: RDD[String] = sc.textFile("spark/data/words", 10)
println("linesRDD:" + linesRDD.getNumPartitions)//10
val wordRDD: RDD[String] = linesRDD.flatMap(_.split(","))
println("wordRDD:" + wordRDD.getNumPartitions)//10
//变成KV格式的RDD
val kvRDD: RDD[(String, Int)] = wordRDD.map((_, 1))
println("kvRDD:" + kvRDD.getNumPartitions)//10
/**
* countRDD 分区数决定因素:
* 1、如果没有指定分区数据默认等于父rdd分区数
* 2、shuffle类的算子可以手动指定分区数据(reduce的数量)
*
* 分区数据决定优先级: 手动指定---> 默认分区数(spark.default.parallelism)---> 父rdd分区数
*/
val countRDD: RDD[(String, Int)] = kvRDD.reduceByKey(_ + _, 2)
println("countRDD:" + countRDD.getNumPartitions)//2
/**
* repartition: 重分区,改变rdd分区数据,会产生shuffle, 不做任何逻辑处理
*
* 可以用于提供并行度
*/
val repartitionRDD: RDD[(String, Int)] = countRDD.repartition(10)
println("repartitionRDD:" + repartitionRDD.getNumPartitions)//分区数变成10
repartitionRDD.foreach(println)
}
}
资源调度和任务调度
Application(spark程序)--->Job(一个action算子触发的任务)--->stage(一组并行计算的task)--->task(执行任务的最小单元)
大数据计算分两步
1、资源调度 yarn-client
1、通过spark-submit提交任务
2、在本地启动Driver val sc = new SparkContext(conf)
3、Driver发请求给RM 启动AM
4、RM分配资源启动AM
5、AM向RM申请资源启动Excutor
6、Excutor反响注册给Driver
7、开始任务调度(action算子触发)
2、任务调度
1、当遇到一个action算子,触发一个任务,开始任务调度
2、构建DAG有向无环图
3、DAGscheduler根据宽窄依赖切分stage
4、DAGscheduler将stage以taskSet的形式发送给taskScheduler
5、taskscheduler根据本地化算法将task发送到Execuotr中执行
6、taskscheduler接收task执行情况
如果task失败taskscheduler负责重试,默认重试3次 如果是因为shuffle file not found taskscheduler不再重试task,而是由DAGscheduler重试上一个stage,DAGscheduler默认重试stage4次
DAGscheduler 负责切分stage
taskscheduler 负责发送task到Exsecutor中执行
粗粒度资源调度 spark:
一次性将所需要的资源(executor数量,executor内存,executor core)全部申请下来,task执行不需要再申请资源,执行速度变快;当最后一个task执行完成之后才会释放资源
缺点:浪费资源,导致资源不能充分利用
细粒度资源调度 mr:
每一个task执行都需要单独申请资源,每次申请资源需要时间,导致task执行变慢--->job变慢---->application变慢
充分利用资源
Spark SQL
DataFrame : 在RDD的基础上增加了列名,相当于一张表
- SparkSession、DataFrame
package com.shujia.sql
//(2.4.5)
import org.apache.spark.SparkContext
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
object Demo1SparkSessio {
def main(args: Array[String]): Unit = {
/**
* SparkSession : spark 2.0提供的新的入口
*
* 新版本统一的入口。之前spark core里面没有,这是spark sql里面的
*/
val spark: SparkSession = SparkSession
.builder()
.master("local")
//指定spark sql shuffle 之后df 分区的数量,默认是200
//小于200就不会触发bypass机制,数据就会有序
.config("spark.sql.shuffle.partitions", 2)
.appName("spark")
.getOrCreate()
//获取sparkContet
//val sc: SparkContext = spark.sparkContext
/**
* DataFrame : 在RDD的基础上增加了列名
* 相当于一张表
*/
//spark sql 读取数据的方式 json格式,里面有列名,有列值
val studentDF: DataFrame = spark.read.json("spark/data/students.json")
//查看表结构
studentDF.printSchema()
/**
* show: 查看数据,相当于action算子
*/
studentDF.show()
studentDF.show(100)
studentDF.show(false) //完整显示数据,不会省略
/**
* DSL 语句: 类sql语句, 介于sql和代码中间的api
*/
studentDF
.where(" age > 22")
.select("id")
.show()
//统计班级的人数
studentDF
.groupBy("clazz")
.count()
.show()
/**
* 创建临时试图。将studentDF创建成一张视图
*/
studentDF.createOrReplaceTempView("student")
/**
* 通过spark 编写sql ,返回一个新的DataFrame
*/
val clazzCount: DataFrame = spark.sql(
"""
|select clazz,count(1) as c
|from student
|group by clazz
|
|
""".stripMargin)
/**
* 保存数据
*/
clazzCount
.write
.mode(SaveMode.Overwrite) //如果输出目录已存在自动覆盖
.json("spark/data/clazz_count")
}
}
- 读取各种文件类型的数据
package com.shujia.sql
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
object Demo2CreateDF {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession
.builder()
.master("local")
.appName("df")
.getOrCreate()
/**
* 1、 读取json文件
* json中自带列名,不需要指定列名
* 一般不用,json格式数据会冗余
*/
val jsonDF: DataFrame = spark
.read
.format("json")
.load("spark/data/students.json")
/**
* 2、读取文件格式的数据
*/
val csvDF: DataFrame = spark
.read
.format("csv")
.option("sep", ",") //默认是逗号分隔
//按照数据的顺序指定表结构
.schema("id STRING , name STRING , age INT , gender STRING , clazz STRING")
.load("spark/data/students.txt")
csvDF.printSchema()
//csvDF.show()
/**
* 3、读取数据库构建df
*/
val jdbcDF: DataFrame = spark
.read
.format("jdbc")
.option("url", "jdbc:mysql://master:3306")
.option("dbtable", "student.student")
.option("user", "root")
.option("password", "123456")
.load()
jdbcDF.show()
/**
* 保存一个parquet格式的文件
* parquet是一种带表结构的压缩格式
*/
csvDF.write.mode(SaveMode.Overwrite).parquet("spark/data/parquet")
/**
* 4、读取parquet 格式的文件
*/
val parquetDF: DataFrame = spark
.read
.format("parquet")
.load("spark/data/parquet")
parquetDF.printSchema()
parquetDF.show()
/**
* 保存一个orc格式的数据
*/
csvDF.write.mode(SaveMode.Overwrite).orc("spark/data/orc")
/**
* 5、读取orc格式的数据
* 是一种带表结构的压缩格式
*/
val orcDF: DataFrame = spark
.read
.format("orc")
.load("spark/data/orc")
orcDF.printSchema()
orcDF.show()
/**
* 6、基于RDD创建DF(需要导入隐士转换才能用toDF)
*/
val sc: SparkContext = spark.sparkContext
val rdd: RDD[String] = sc.textFile("spark/data/students.txt")
val studentRDD: RDD[(String, String, Int, String, String)] = rdd.map(line => {
val split: Array[String] = line.split(",")
(split(0), split(1), split(2).toInt, split(3), split(4))
})
//导入隐式转换
import spark.implicits._
//将rdd转换成DF ,指定列名
val studentDF: DataFrame = studentRDD.toDF("id", "name", "age", "gender", "clazz")
studentDF.printSchema()
studentDF.show()*/
while (true){
}
}
}
- DFApi
package com.shujia.sql
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}
object Demo3DFApi {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder()
.master("local")
.appName("api")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
//导入隐式转换
import spark.implicits._
val student: DataFrame = spark
.read
.format("csv")
.option("sep", ",") //默认是逗号分隔
//按照数据的顺序指定表结构
.schema("id STRING , name STRING , age INT , gender STrING , clazz STRING")
.load("spark/data/students.txt")
/**
* show :查看数据
*/
student.show()
/**
* where : 过滤数据
*/
//字符串表达式
student.where("age > 22").show()
//使用 列表达式的方式
student.where($"age" >= 22).show()
/**
* Dataset[Row] 和 DataFrame同一个东西
*/
val filterDS: Dataset[Row] = student.filter((row: Row) => {
val age: Int = row.getAs[Int]("age")
age >= 22
})
filterDS.show()
/**
* select : 选择数据
*/
student.select("name", "age").show()
//选择的时候也可以处理数据
student.select($"name", $"age" + 1 as "age").show()
/**
* group By: 分组
* 分组之后必须接一个聚合函数
*/
student.groupBy("clazz").max("age").show()
//导入sql中素有的函数
import org.apache.spark.sql.functions._
//计算每个班级最大的年龄
student
.groupBy("clazz")
.agg(max($"age") as "maxAge")
.show()
//计算每个班级平均年龄
student.groupBy("clazz")
.agg(avg("age") as "avgAge")
.show()
//统计班级性别的人数
student.groupBy($"clazz", $"gender")
.agg(countDistinct($"id") as "c")
.show()
/**
* join : 表关联
* 如果关联的列名一样直接指定关联列名
*/
val score: DataFrame = spark
.read
.format("csv")
.option("sep", ",") //默认是逗号分隔
//按照数据的顺序指定表结构
.schema("sid STRING , cId STRING , sco DOUBLE")
.load("spark/data/score.txt")
val joinDF: DataFrame = student.join(score, $"id" === $"sid", "inner")
//如果关联的列名一样直接指定关联列名
val joinDF: DataFrame = student.join(score, "id")
joinDF
.groupBy("name")
.agg(sum($"sco") as "sumSco")
.show()
}
}
package com.shujia.sql
//统计每一个城市人流量最多的前两个区县
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.{DataFrame, SparkSession}
object Demo4Dianxin {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder()
.master("local")
.appName("dianxin")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
//导入隐式转换
import spark.implicits._
//导入sql 所有的函数
import org.apache.spark.sql.functions._
//读取数据
val dianxin: DataFrame = spark
.read
.format("csv")
.option("sep", ",")
.schema("mdn STRING,grid STRING, cityId STRING , countyId STriNG , t INT , startTIme STRING, endTIme STRING ,day STRING")
.load("spark/data/dianxin_data")
.where($"cityId" =!= "\\N") //过滤脏数据
// DSL
dianxin
.groupBy($"cityId", $"countyId") //按照城市和曲线分组
.agg(countDistinct($"mdn") as "num") //统计人数
.select($"cityId", $"countyId", $"num", row_number().over(Window.partitionBy($"cityId").orderBy($"num".desc)) as "rank")
.where($"rank" <= 2) //取前2
.select($"cityId", $"countyId", $"num", $"rank") //整理数据
.show(1000)
//这种方式代码就是从前到后执行,不是按照sql的执行顺序
//sql语句
dianxin.createOrReplaceTempView("dianxin")
spark.sql(
"""
|
|
|select * from
|(
|select
|cityId,countyId,num , row_number() over(partition by cityId order by num desc ) as rank
|from
|(select
| cityId,countyId,count(distinct mdn) as num
|from
|dianxin
|group by cityId,countyId) as a
|) as b
|where rank <=2
|
|
""".stripMargin)
.show()
}
}
- explode,一行变多行,行专列
package com.shujia.sql
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.{Column, DataFrame, SparkSession}
object Demo5Burk {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder()
.master("local")
.appName("dianxin")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
//导入隐式转换
import spark.implicits._
//导入sql 所有的函数
import org.apache.spark.sql.functions._
/**
* explode: 相当于flatmap,一行变多行
*/
spark.sql(
"""
|select explode(array(1,2,3,4,5,6))
|
""".stripMargin)
// .show()
spark.sql(
"""
|select explode(map('01','张三','02','李四','03','王五'))
|
""".stripMargin)
//.show()
/**
* 1、统计每个公司每年按月累计收入
*
* 输出结果
* 公司代码,年度,月份,当月收入,累计收入
*/
val burk: DataFrame = spark.read
.format("csv")
.option("sep", ",")
.schema("burk STRING,year STRING,tsl01 DOUBLE,tsl02 DOUBLE,tsl03 DOUBLE,tsl04 DOUBLE,tsl05 DOUBLE,tsl06 DOUBLE,tsl07 DOUBLE,tsl08 DOUBLE,tsl09 DOUBLE,tsl10 DOUBLE,tsl11 DOUBLE,tsl12 DOUBLE")
.load("spark/data/burks.txt")
burk.createOrReplaceTempView("burk")
spark.sql(
"""
|
|select
|burk,year,month,pic
|from
|burk
|LATERAL VIEW explode(map( 1, tsl01,2 ,tsl02, 3 , tsl03,4,tsl04,5, tsl05,6 ,tsl06, 7 , tsl07,8,tsl08,9, tsl09,10 ,tsl10, 11 , tsl11,12,tsl12)) t as month,pic
|
""".stripMargin)
spark.sql(
"""
| //按月累加
|select burk,year,month ,pic ,sum(pic) over (partition by burk,year order by month) as sumPic
|from
|(select
|burk,year,explode(map( 1, tsl01,2 ,tsl02, 3 , tsl03,4,tsl04,5, tsl05,6 ,tsl06, 7 , tsl07,8,tsl08,9, tsl09,10 ,tsl10, 11 , tsl11,12,tsl12)) as (month,pic)
|from
|burk) as a
|
""".stripMargin)
//.show(1000)
/**
* DSL
*/
val monthMap: Column = map(
expr("1"), $"tsl01",
expr("2"), $"tsl02",
expr("3"), $"tsl03",
expr("4"), $"tsl04",
expr("5"), $"tsl05",
expr("6"), $"tsl06",
expr("7"), $"tsl07",
expr("8"), $"tsl08",
expr("9"), $"tsl09",
expr("10"), $"tsl10",
expr("11"), $"tsl11",
expr("12"), $"tsl12"
)
burk
.select($"burk", $"year", explode(monthMap) as Array("month", "pic"))
.select($"burk", $"year", $"month", $"pic", sum($"pic") over Window.partitionBy($"burk", $"year").orderBy($"month".asc) as "sumPic")
.show(1000)
/**
* 2、统计每个公司当月比上年同期增长率。lag函数
* 公司代码,年度,月度,增长率(当月收入/上年当月收入 - 1)
*/
/**
* coalesce : 如第一个为null 再取第二个
*/
burk
.select($"burk", $"year", explode(monthMap) as Array("month", "pic"))
.select($"burk", $"year", $"month", $"pic", lag($"pic", 1, 0.0) over Window.partitionBy($"burk", $"month").orderBy($"year") as "lastPic")
.select($"burk", $"year", $"month", $"pic", round(coalesce($"pic" / $"lastPic" - 1, expr("1.0")), 5) as "bi")
.show()
}
}
- spark sql 运行方式:
-
在ideal里面写sql,打包发送到集群上面运行。读写地址写hdfs上面的路径。spark-submit提交。用于生产
-
spark shell 。在命令行的里写一行代码,写一行运行一行。用于测试,简单的任务使用。
-
spark-sql。spark-sql --master yarn-client。进入sql命令行。可以整合hive,使用hive元数据。
这个需要在hive的配置文件中添加一些spark的配置。整合后在spark-sql 里面就可以使用hive的表了。
package com.shujia.sql
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
object Demo6Submit {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession
.builder()
.appName("submit")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
import spark.implicits._
import org.apache.spark.sql.functions._
val student: DataFrame = spark.read
.format("csv")
.option("sep", ",")
.schema("id STRING , name STRING ,age INT ,gender STRING ,clazz STRING")
.load("/data/student") //读取hdfs文件
val clazznuNum: DataFrame = student
.groupBy($"clazz")
.agg(count($"clazz") as "num")
//保存数据
clazznuNum.write
.format("csv")
.option("sep", "\t")
.mode(SaveMode.Overwrite)
.save("/data/clazz_num")
}
}
- spark 整合hive
package com.shujia.sql
import org.apache.spark.sql.{DataFrame, SparkSession}
object Demo7OnHIve {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession
.builder()
.appName("submit")
.config("spark.sql.shuffle.partitions", 2)
.enableHiveSupport() //开启读取hive的元数据
.getOrCreate()
/**
* 可以直接使用hive中表
*/
spark.sql(
"""
|show tables
|
""".stripMargin).show()
spark.sql(
"""
|
|select clazz,count(1) from student group by clazz
|
|
""".stripMargin).show()
//可以读取hive中的表构建df
val score: DataFrame = spark.table("score")
score.show()
}
}
//不能直接运行,因为本地模式没有整合hive。需要打包放到集群上运行。
Spark优化
- 代码层面优化:
- 避免创建重复的RDD
- 尽可能复用同一个RDD
- 对多次使用的RDD进行持久化
- 尽量避免使用shuffle类算子
- 使用map-side预聚合的shuffle操作
- 使用高性能的算子
- 广播大变量
- 使用Kryo优化序列化性能
- 优化数据结构
- 使用高性能的库fastutil
- 参数层面优化:
- 数据本地性
- JVM调优
- shuffle调优
- 调节Executor堆外内存
- 数据倾斜七种解决方案:
- 使用Hive ETL预处理数据
- 过滤少数导致倾斜的key
- 提高shuffle操作的并行度
- 双重聚合
- 将reduce join转为map join
- 采样倾斜key并分拆join操作
- 使用随机前缀和扩容RDD进行join
- 代码优化
- 对多次使用的RDD进行持久化
如何选择一种最合适的持久化策略
1、默认情况下,性能最高的当然是MEMORY_ONLY,但前提是你的内存必须足够足够大, 可以绰绰有余地存放下整个RDD的所有数据。因为不进行序列化与反序列化操作,就避 免了这部分的性能开销;对这个RDD的后续算子操作,都是基于纯内存中的数据的操作 ,不需要从磁盘文件中读取数据,性能也很高;而且不需要复制一份数据副本,并远程传 送到其他节点上。但是这里必须要注意的是,在实际的生产环境中,恐怕能够直接用这种 策略的场景还是有限的,如果RDD中数据比较多时(比如几十亿),直接用这种持久化 级别,会导致JVM的OOM内存溢出异常。
2、如果使用MEMORY_ONLY级别时发生了内存溢出,那么建议尝试使用 MEMORY_ONLY_SER级别。该级别会将RDD数据序列化后再保存在内存中,此时每个 partition仅仅是一个字节数组而已,大大减少了对象数量,并降低了内存占用。这种级别 比MEMORY_ONLY多出来的性能开销,主要就是序列化与反序列化的开销。但是后续算 子可以基于纯内存进行操作,因此性能总体还是比较高的。此外,可能发生的问题同上, 如果RDD中的数据量过多的话,还是可能会导致OOM内存溢出的异常。
3、如果纯内存的级别都无法使用,那么建议使用MEMORY_AND_DISK_SER策略,而不是 MEMORY_AND_DISK策略。因为既然到了这一步,就说明RDD的数据量很大,内存无 法完全放下。序列化后的数据比较少,可以节省内存和磁盘的空间开销。同时该策略会优 先尽量尝试将数据缓存在内存中,内存缓存不下才会写入磁盘。
4、通常不建议使用DISK_ONLY和后缀为_2的级别:因为完全基于磁盘文件进行数据的读写 ,会导致性能急剧降低,有时还不如重新计算一次所有RDD。后缀为_2的级别,必须将 所有数据都复制一份副本,并发送到其他节点上,数据复制以及网络传输会导致较大的性 能开销,除非是要求作业的高可用性,否则不建议使用。
- 使用高性能的算子
使用reduceByKey/aggregateByKey替代groupByKey
使用mapPartitions替代普通map Transformation算子
使用foreachPartitions替代foreach Action算子
使用filter之后进行coalesce操作
使用repartitionAndSortWithinPartitions替代repartition与sort类操 作 代码
repartition:coalesce(numPartitions,true) 增多分区使用这个
coalesce(numPartitions,false) 减少分区 没有shuffle只是合并 partition
- 如果需要将数据保存到外部数据库,使用foreachPartition 代替foreach。因为rdd没有save方法,而df有。
package com.shujia.optimize
import java.sql.{Connection, DriverManager, PreparedStatement}
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession
object Demo0ForeachPartition {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder()
.master("local")
.appName("foreach")
.getOrCreate()
val rdd1: RDD[String] = spark
.sparkContext
.textFile("spark/data/students.txt", 4)
println(rdd1.getNumPartitions)
/**
* 将rdd的数据保存到mysql中
*/
/*
rdd1.foreach(line => {
/**
* 建立jdbc连接
*
* 网络连接的对象不能序列化,也不能在网络中传输
*
*/
Class.forName("com.mysql.jdbc.Driver")
val con: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student?useUnicode=true&characterEncoding=utf-8", "root", "123456")
val stat: PreparedStatement = con.prepareStatement("insert into student(id,name,age,gender,clazz) values(?,?,?,?,?)")
val split: Array[String] = line.split(",")
stat.setString(1, split(0))
stat.setString(2, split(1))
stat.setInt(3, split(2).toInt)
stat.setString(4, split(3))
stat.setString(5, split(4))
//插入数据
stat.executeUpdate()
})
*/
/**
* foreachPartition : 遍历一个分区的数据
*
* iter : 一个分区的数据
*
*
* 如果需要将数据保存到外部数据库,使用foreachPartition 代替foreach
*
* foreachPartition 每一个分区只会创建一个连接
*/
rdd1.foreachPartition(iter => {
//这里的代码每一个分区指挥执行一次
//每一个分区只会建立一个连接
Class.forName("com.mysql.jdbc.Driver")
val con: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student?useUnicode=true&characterEncoding=utf-8", "root", "123456")
println("连接建立成功")
//遍历一个分区的数据
iter.foreach(line => {
val stat: PreparedStatement = con.prepareStatement("insert into student(id,name,age,gender,clazz) values(?,?,?,?,?)")
val split: Array[String] = line.split(",")
stat.setString(1, split(0))
stat.setString(2, split(1))
stat.setInt(3, split(2).toInt)
stat.setString(4, split(3))
stat.setString(5, split(4))
//插入数据
stat.executeUpdate()
})
//关闭连接
con.close()
})
/**
* mapPartitions : 遍历一个分区,返回一i个迭代器
*
* mapPartitionsWithIndex ; 在mapPartitions的基础上多了分区编号
*/
rdd1.mapPartitions(iter => {
//在这里写的代码每一个分区只会执行一次
iter.map(line => line)
})
rdd1.mapPartitionsWithIndex {
case (index, iter) => {
println(index)
iter
}
}.foreach(line => line)
}
}
package com.shujia.optimize
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Demo1Coalesce {
def main(args: Array[String]): Unit = {
/**
* coalesce 重分区
*
* 改变rdd的并行度, 如果资源充足,可以提高并行度提高任务执行速度
*
* 保证每一个task处理的数据再50m到1G不等
*/
val conf: SparkConf = new SparkConf()
.setAppName("app")
.setMaster("local[1]")
val sc: SparkContext = new SparkContext(conf)
// 产生小文件
/* sc
.textFile("spark/data/students.txt")
//重分区 会产生shuffle
.repartition(20)
.saveAsTextFile("spark/data/repartition")*/
//数据里面由很多小文件,导致rdd分区很多,每隔分区数据量很小
val rdd: RDD[String] = sc.textFile("spark/data/repartition")
println("rdd:" + rdd.getNumPartitions)
/**
* repartition 会产生shuffle
*/
val rdd2: RDD[String] = rdd.repartition(3)
println("rdd2:" + rdd2.getNumPartitions)
/**
* 增加分区必须产生shuffle
* 减少分区的时候可以不产生shuflle
*/
/**
* 合并小文件
*
* 保证每一个task处理的数据量在100M - 1G 左右
*/
//减少分区的时候可以不产生shuflle
val cRDD: RDD[String] = rdd2.coalesce(2, shuffle = false)
println("cRDD:" + cRDD.getNumPartitions)
cRDD.foreach(println)
//增加分区必须有shuffle
val rdd3: RDD[String] = rdd.coalesce(200, shuffle = true)
println(rdd3.getNumPartitions)
}
}
- 广播大变量
开发过程中,会遇到需要在算子函数中使用外部变量的场景(尤其是大变量,比如 100M以上的大集合),那么此时就应该使用Spark的广播(Broadcast)功能来提 升性能
函数中使用到外部变量时,默认情况下,Spark会将该变量复制多个副本,通过网络 传输到task中,此时每个task都有一个变量副本。如果变量本身比较大的话(比如 100M,甚至1G),那么大量的变量副本在网络中传输的性能开销,以及在各个节 点的Executor中占用过多内存导致的频繁GC(垃圾回收),都会极大地影响性能
如果使用的外部变量比较大,建议使用Spark的广播功能,对该变量进行广播。广播 后的变量,会保证每个Executor的内存中,只驻留一份变量副本,而Executor中的 task执行时共享该Executor中的那份变量副本。这样的话,可以大大减少变量副本 的数量,从而减少网络传输的性能开销,并减少对Executor内存的占用开销,降低 GC的频率
广播大变量发送方式:Executor一开始并没有广播变量,而是task运行需要用到广 播变量,会找executor的blockManager要,bloackManager找Driver里面的 blockManagerMaster要。
package com.shujia.optimize
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object MapJoin {
/**
* map join
*
* 将小表广播,大表使用map算子
*
* 1、小表不能太大, 不能超过2G
* 2、如果driver内存不足,需要手动设置 如果广播变量大小超过了driver内存大小,会出现oom
*/
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("app")
val sc: SparkContext = new SparkContext(conf)
//RDD 不能广播。集合才能广播
val studentRDD: RDD[String] = sc.textFile("spark/data/students.txt")
//将数据拉去到driver端,变成一个map集合
val stuMap: Map[String, String] = studentRDD
.collect() //将rdd的数据拉取Driver端变成一个数组
.map(s => (s.split(",")(0), s))
.toMap
//广播map集合
val broStu: Broadcast[Map[String, String]] = sc.broadcast(stuMap)
val scoreRDD: RDD[String] = sc.textFile("spark/data/score.txt")
//循环大表,通过key获取小表信息
scoreRDD.map(s => {
val sId: String = s.split(",")(0)
//从广播变量里面获取数据
val stuInfo: String = broStu.value.getOrElse(sId, "")
stuInfo + "," + s
}).foreach(println)
while (true) {
}
}
}
package com.shujia.optimize
import org.apache.spark.sql.{DataFrame, SparkSession}
object SparkSqlMapJoin {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder()
.master("local[4]")
.appName("join")
.config("spark.sql.shuffle.partitions", "2")
.getOrCreate()
import spark.implicits._
val student: DataFrame = spark.read
.format("csv")
.schema("id STRING , name STRING ,age INT ,gender STRING ,clazz STRING")
.load("spark/data/students.txt")
val score: DataFrame = spark.read
.format("csv")
.schema("sid STRING ,cId STRING , sco INT")
.load("spark/data/score.txt")
/**
* .hint("broadcast") : 广播表实现m ap join
*
*/ //方式1:
student.join(score.hint("broadcast"), $"sId" === $"id").show(10000000)
//方式2:
student.createOrReplaceTempView("student")
score.createOrReplaceTempView("score")
spark.sql(
"""
|
|select /*+broadcast(a) */ * from score as a join student as b on a.sId=b.id
|
""".stripMargin).show(1000000)
while (true) {
}
}
}
- 使用Kryo优化序列化性能
在Spark中,主要有三个地方涉及到了序列化:
- 在算子函数中使用到外部变量时,该变量会被序列化后进行网络传输;
- 将自定义的类型作为RDD的泛型类型时(比如JavaRDD,SXT是自定义类型),所有自 定义类型对象,都会进行序列化。因此这种情况下,也要求自定义的类必须实现 Serializable接口。
- 使用可序列化的持久化策略时(比如MEMORY_ONLY_SER),Spark会将RDD中的每个 partition都序列化成一个大的字节数组。
Kryo序列化器介绍:
Spark支持使用Kryo序列化机制。Kryo序列化机制,比默认的Java序列化机制,速度要快 ,序列化后的数据要更小,大概是Java序列化机制的1/10。所以Kryo序列化优化以后,可 以让网络传输的数据变少;在集群中耗费的内存资源大大减少。
对于这三种出现序列化的地方,我们都可以通过使用Kryo序列化类库,来优化序列化和 反序列化的性能。Spark默认使用的是Java的序列化机制,也就是 ObjectOutputStream/ObjectInputStream API来进行序列化和反序列化。但是Spark同 时支持使用Kryo序列化库,Kryo序列化类库的性能比Java序列化类库的性能要高很多。 官方介绍,Kryo序列化机制比Java序列化机制,性能高10倍左右。Spark之所以默认没有 使用Kryo作为序列化类库,是因为Kryo要求最好要注册所有需要进行序列化的自定义类 型,因此对于开发者来说,这种方式比较麻烦
package com.shujia.optimize
import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel
import org.apache.spark.{SparkConf, SparkContext}
object TestKryo {
/**
* 使用kryo序列化方式代替默认序列化方式(objectOutPutStream/objectInPutStream)
* 性能提高10倍
*
*
* spark 三个地方涉及到序列化
*
* 1、算子里面用到可外部变量
* 2、RDD 类型为自定义类型,同时使用checkpoint 或者 使用shuffle类算子的时候会产生序列化
* 3、 cache SER
*/
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("app")
//序列化方式
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
//指定注册序列化的类,自定义
.set("spark.kryo.registrator", "com.shujia.optimize.MyRegisterKryo")
val sc: SparkContext = new SparkContext(conf)
sc.setCheckpointDir("spark/data/checkpoint")
val data: RDD[String] = sc.textFile("spark/data/students.txt")
/**
* 自定义对象比字符串赵勇内存更多
* 因为自定义对象由对象头信息
*/
var stuRDD: RDD[Student] = data
.map(_.split(","))
.map(line => Student(line(0), line(1), line(2).toInt, line(3), line(4)))
///checkpoint 产生序列化
stuRDD.checkpoint()
//shuffle 类算子产生序列化
stuRDD.map(s => (s.id, s)).groupByKey().foreach(println)
//对RDD 持久化会产生序列化
stuRDD = stuRDD.persist(StorageLevel.MEMORY_AND_DISK_SER)
stuRDD.foreach(println)
while (true) {
}
}
}
case class Student(id: String, name: String, age: Int, gender: String, clazz: String)
- 序列化时用到的自定义的一个类
package com.shujia.optimize
import com.twitter.chill.Kryo
import org.apache.spark.serializer.KryoRegistrator
class MyRegisterKryo extends KryoRegistrator {
//注册类
override def registerClasses(kryo: Kryo): Unit = {
//注册Student类
//注册之后student类序列化的时候就会使用kryo
//classOf 获取类对象
kryo.register(classOf[Student])
kryo.register(classOf[Int])
kryo.register(classOf[String])
//可以同时注册多个
// kryo.register()
}
}
- 优化数据结构
Java中,有三种类型比较耗费内存:
1、对象,每个Java对象都有对象头、引用等额外的信息,因此比较占用内存空间。
2、字符串,每个字符串内部都有一个字符数组以及长度等额外信息。
3、集合类型,比如HashMap、LinkedList等,因为集合类型内部通常会使用一些内部类来 封装集合元素,比如Map.Entry。
因此Spark官方建议,在Spark编码实现中,特别是对于算子函数中的代码,尽 量不要使用上述三种数据结构,尽量使用字符串替代对象,使用原始类型(比如 Int、Long)替代字符串,使用数组替代集合类型,这样尽可能地减少内存占用 ,从而降低GC频率,提升性能。
- 使用高性能的库fastutil
fastutil介绍:
1、fastutil是扩展了Java标准集合框架(Map、List、Set;HashMap、ArrayList、 HashSet)的类库,提供了特殊类型的map、set、list和queue;
2、fastutil能够提供更小的内存占用,更快的存取速度;我们使用fastutil提供的集合类,来 替代自己平时使用的JDK的原生的Map、List、Set,好处在于,fastutil集合类,可以减 小内存的占用,并且在进行集合的遍历、根据索引(或者key)获取元素的值和设置元素 的值的时候,提供更快的存取速度;
3、fastutil最新版本要求Java 7以及以上版本;
4、fastutil的每一种集合类型,都实现了对应的Java中的标准接口(比如fastutil的map,实 现了Java的Map接口),因此可以直接放入已有系统的任何代码中。
5、fastutil的每一种集合类型,都实现了对应的Java中的标准接口(比如fastutil的 map,实现了Java的Map接口),因此可以直接放入已有系统的任何代码中。
- 参数优化
- 数据本地性
- Application任务执行流程:
在Spark Application提交后,Driver会根据action算子划分成一个个的job,然后对每一 个job划分成一个个的stage,stage内部实际上是由一系列并行计算的task组成的,然后 以TaskSet的形式提交给你TaskScheduler,TaskScheduler在进行分配之前都会计算出 每一个task最优计算位置。Spark的task的分配算法优先将task发布到数据所在的节点上 ,从而达到数据最优计算位置。
- 数据本地化级别:
- PROCESS_LOCAL
- NODE_LOCA
- NO_PREF
- RACK_LOCAL
- ANY
- 配置参数:
- spark.locality.wait
- spark.locality.wait.process
- spark.locality.wait.node
- spark.locality.wait.rack
- JVM调优
概述:
1、Spark task执行算子函数,可能会创建很多对象,这些对象,都是要放入JVM年轻代中
2、RDD的缓存数据也会放入到堆内存中
配置:
spark.storage.memoryFraction 默认是0.6
- shuffle调优
概述:
reduceByKey:要把分布在集群各个节点上的数据中的同一个key,对应的values,都给 集中到一个节点的一个executor的一个task中,对集合起来的value执行传入的函数进行 reduce操作,最后变成一个value
配置
spark.shuffle.manager, 默认是sort
spark.shuffle.consolidateFiles,默认是false
spark.shuffle.file.buffer,默认是32k
spark.shuffle.memoryFraction,默认是0.2
- 调节Executor堆外内存
问题原因:
1、Executor由于内存不足或者对外内存不足了,挂掉了,对应的Executor上面的block manager也挂掉了,找不到对应的shuffle map output文件,Reducer端不能够拉取数据
2、Executor并没有挂掉,而是在拉取数据的过程出现了问题
上述情况下,就可以去考虑调节一下executor的堆外内存。也许就可以避免报错;
一定要注意,spark-submit脚本里面,去用--conf的方式去添加配置。
解决办法:
1、yarn下:--conf spark.yarn.executor.memoryOverhead=2048 单位M
2、standlone下:--conf spark.executor.memoryOverhead=2048单位M
默认情况下,这个堆外内存上限默认是每一个executor的内存大小的10%;真正处理大数据的时候, 这里都会出现问题,导致spark作业反复崩溃,无法运行;此时就会去调节这个参数,到至少1G (1024M),甚至说2G、4G
调节等待时长 :
1、executor在进行shuffle write,优先从自己本地关联的BlockManager中获取某份数据如果本地 block manager没有的话,那么会通过TransferService,去远程连接其他节点上executor的block manager去获取,尝试建立远程的网络连接,并且去拉取数据
2、频繁的让JVM堆内存满溢,进行垃圾回收。正好碰到那个exeuctor的JVM在垃圾回收。处于垃圾回 收过程中,所有的工作线程全部停止;相当于只要一旦进行垃圾回收,spark / executor停止工作, 无法提供响应,spark默认的网络连接的超时时长,是60s;如果卡住60s都无法建立连接的话,那 么这个task就失败了。
解决?--conf spark.core.connection.ack.wait.timeout=300
- 参数调优模板:
spark-submit
--class com.shujia.*.*
--master yarn-client
--num-executors 100 // 资源大小,数据量
--executor-memory 8G // 广播变量,cache数据量
--executor-cores 4
--driver-memory 2G
--conf spark.default.parallelism=100 // shuffle之后rdd分区数
--conf spark.storage.memoryFraction=0.4 // rdd持久化内存占比
--conf spark.shuffle.memoryFraction0.4 //rddshuffle内存占比
--conf spark.locality.wait=10 //task在Executor中的等待的超时时间
--conf spark.shuffle.file.buffer=64k
--conf spark.yarn.executor.memoryOverhead=2048M
--conf spark.network.timeout=600s
任务使用的资源
400C 800G
- 数据倾斜的原因:
1、key 分布不均
2、产生了shuffle。
两个条件都要满足才会产生
- 1、使用Hive ETL预处理数据
方案适用场景:如果导致数据倾斜的是Hive表。如果该Hive表中的数据本身很不均匀(比如某个 key对应了100万数据,其他key才对应了10条数据),而且业务场景需要频繁使用Spark对Hive表 执行某个分析操作,那么比较适合使用这种技术方案。
方案实现思路:此时可以评估一下,是否可以通过Hive来进行数据预处理(即通过Hive ETL预先对 数据按照key进行聚合,或者是预先和其他表进行join),然后在Spark作业中针对的数据源就不是 原来的Hive表了,而是预处理后的Hive表。此时由于数据已经预先进行过聚合或join操作了,那么 在Spark作业中也就不需要使用原先的shuffle类算子执行这类操作了。
方案实现原理:这种方案从根源上解决了数据倾斜,因为彻底避免了在Spark中执行shuffle类算子 ,那么肯定就不会有数据倾斜的问题了。但是这里也要提醒一下大家,这种方式属于治标不治本。 因为毕竟数据本身就存在分布不均匀的问题,所以Hive ETL中进行group by或者join等shuffle操作 时,还是会出现数据倾斜,导致Hive ETL的速度很慢。我们只是把数据倾斜的发生提前到了Hive ETL中,避免Spark程序发生数据倾斜而已。
- 2、过滤少数导致倾斜的key
方案适用场景:如果发现导致倾斜的key就少数几个,而且对计算本身的影响并不大的话,那么很 适合使用这种方案。比如99%的key就对应10条数据,但是只有一个key对应了100万数据,从而导 致了数据倾斜。
方案实现思路:如果我们判断那少数几个数据量特别多的key,对作业的执行和计算结果不是特别 重要的话,那么干脆就直接过滤掉那少数几个key。比如,在Spark SQL中可以使用where子句过滤 掉这些key或者在Spark Core中对RDD执行filter算子过滤掉这些key。如果需要每次作业执行时, 动态判定哪些key的数据量最多然后再进行过滤,那么可以使用sample算子对RDD进行采样,然后 计算出每个key的数量,取数据量最多的key过滤掉即可。
方案实现原理:将导致数据倾斜的key给过滤掉之后,这些key就不会参与计算了,自然不可能产生 数据倾斜。
package com.shujia.optimize
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object FilterKey {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("app")
val sc: SparkContext = new SparkContext(conf)
val lines: RDD[String] = sc.textFile("spark/data/word")
println("第一个RDD分区数量:" + lines.getNumPartitions)
val countRDD: RDD[(String, Int)] = lines
.flatMap(_.split(","))
.map((_, 1))
.groupByKey()
.map(x => (x._1, x._2.toList.sum))
println("聚合之后RDD分区的数量" + countRDD.getNumPartitions)
//countRDD.foreach(println)
/**
* 采样key ,g过滤掉导致数据倾斜并且对业务影响不大的key
*/
val wordRDD: RDD[(String, Int)] = lines
.flatMap(_.split(","))
.map((_, 1))
val top1: Array[(String, Int)] = wordRDD
.sample(true, 0.1)
.reduceByKey(_ + _)
.sortBy(-_._2)
.take(1)
//导致数据倾斜额key
val key: String = top1(0)._1
//过滤导致倾斜的key
wordRDD
.filter(t => !key.equals(t._1))
.groupByKey()
.map(x => (x._1, x._2.toList.sum))
.foreach(println)
while (true) {
}
}
}
- 3、提高shuffle操作的并行度
方案实现思路:在对RDD执行shuffle算子时,给shuffle算子传入一个参数,比如 reduceByKey(1000),该参数就设置了这个shuffle算子执行时shuffle read task的数量。对于 Spark SQL中的shuffle类语句,比如group by、join等,需要设置一个参数,即 spark.sql.shuffle.partitions,该参数代表了shuffle read task的并行度,该值默认是200,对于很 多场景来说都有点过小。
方案实现原理:增加shuffle read task的数量,可以让原本分配给一个task的多个key分配给多个 task,从而让每个task处理比原来更少的数据。举例来说,如果原本有5个key,每个key对应10条 数据,这5个key都是分配给一个task的,那么这个task就要处理50条数据。而增加了shuffle read task以后,每个task就分配到一个key,即每个task就处理10条数据,那么自然每个task的执行时 间都会变短了。
- 4、双重聚合
方案适用场景:对RDD执行reduceByKey等聚合类shuffle算子或者在Spark SQL中使用group by 语句进行分组聚合时,比较适用这种方案。
方案实现思路:这个方案的核心实现思路就是进行两阶段聚合。第一次是局部聚合,先给每个key 都打上一个随机数,比如10以内的随机数,此时原先一样的key就变成不一样的了,比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就会变成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。接着 对打上随机数后的数据,执行reduceByKey等聚合操作,进行局部聚合,那么局部聚合结果,就会 变成了(1_hello, 2) (2_hello, 2)。然后将各个key的前缀给去掉,就会变成(hello,2)(hello,2),再次 进行全局聚合操作,就可以得到最终结果了,比如(hello, 4)。
方案实现原理:将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被 一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。 接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果
package com.shujia.optimize
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.util.Random
object DoubleReduce {
/**
* 双重聚合
* 一般适用于 业务不复杂的情况
*/
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("app")
val sc: SparkContext = new SparkContext(conf)
val lines: RDD[String] = sc.textFile("spark/data/word")
val wordRDD: RDD[String] = lines
.flatMap(_.split(","))
.filter(!_.equals(""))
// 对每一个key打上随机5以内前缀
wordRDD.map(word => {
val pix: Int = Random.nextInt(5)
(pix + "-" + word, 1)
})
.groupByKey() //第一次聚合
.map(t => (t._1, t._2.toList.sum))
.map(t => {
///去掉随机前缀
(t._1.split("-")(1), t._2)
})
.groupByKey() //第二次聚合
.map(t => (t._1, t._2.toList.sum))
.foreach(println)
while (true) {
}
}
}
- 5、将reduce join转为map join
方案适用场景:在对RDD使用join类操作,或者是在Spark SQL中使用join语句时,而且join操作中 的一个RDD或表的数据量比较小(比如几百M或者一两G),比较适用此方案。
方案实现思路:不使用join算子进行连接操作,而使用Broadcast变量与map类算子实现join操作, 进而完全规避掉shuffle类的操作,彻底避免数据倾斜的发生和出现。将较小RDD中的数据直接通过 collect算子拉取到Driver端的内存中来,然后对其创建一个Broadcast变量;接着对另外一个RDD 执行map类算子,在算子函数内,从Broadcast变量中获取较小RDD的全量数据,与当前RDD的每 一条数据按照连接key进行比对,如果连接key相同的话,那么就将两个RDD的数据用你需要的方式 连接起来。
方案实现原理:普通的join是会走shuffle过程的,而一旦shuffle,就相当于会将相同key的数据拉 取到一个shuffle read task中再进行join,此时就是reduce join。但是如果一个RDD是比较小的, 则可以采用广播小RDD全量数据+map算子来实现与join同样的效果,也就是map join,此时就不 会发生shuffle操作,也就不会发生数据倾斜
package com.shujia.optimize
import java.util
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.util.Random
object DoubleJoin {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("app").setMaster("local")
val sc = new SparkContext(conf)
val dataList1 = List(
("java", 1),
("shujia", 2),
("shujia", 3),
("shujia", 1),
("shujia", 1))
val dataList2 = List(
("java", 100),
("java", 99),
("shujia", 88),
("shujia", 66))
val RDD1: RDD[(String, Int)] = sc.parallelize(dataList1)
val RDD2: RDD[(String, Int)] = sc.parallelize(dataList2)
val sampleRDD: RDD[(String, Int)] = RDD1.sample(false, 1.0)
//skewedKey 导致数据倾斜的key shujia
val skewedKey: String = sampleRDD.map(x => (x._1, 1))
.reduceByKey(_ + _)
.map(x => (x._2, x._1))
.sortByKey(ascending = false)
.take(1)(0)._2
//导致数据倾斜key的RDD
val skewedRDD1: RDD[(String, Int)] = RDD1.filter(tuple => {
tuple._1.equals(skewedKey)
})
//没有倾斜的key
val commonRDD1: RDD[(String, Int)] = RDD1.filter(tuple => {
!tuple._1.equals(skewedKey)
})
val skewedRDD2: RDD[(String, Int)] = RDD2.filter(tuple => {
tuple._1.equals(skewedKey)
})
val commonRDD2: RDD[(String, Int)] = RDD2.filter(tuple => {
!tuple._1.equals(skewedKey)
})
val n = 2
//对产生数据倾斜的key 使用mapjoin
val skewedMap: Map[String, Int] = skewedRDD2.collect().toMap
val bro: Broadcast[Map[String, Int]] = sc.broadcast(skewedMap)
val resultRDD1: RDD[(String, (Int, Int))] = skewedRDD1.map(kv => {
val word: String = kv._1
val i: Int = bro.value.getOrElse(word, 0)
(word, (kv._2, i))
})
//没有数据倾斜的RDD 正常join
val resultRDD2: RDD[(String, (Int, Int))] = commonRDD1.join(commonRDD2)
//将两个结果拼接
resultRDD1.union(resultRDD2)
.foreach(println)
}
}
- 6、采样倾斜key并分拆join操作
方案适用场景:两个RDD/Hive表进行join的时候,如果数据量都比较大,无法采用“解决方案五 ”,那么此时可以看一下两个RDD/Hive表中的key分布情况。如果出现数据倾斜,是因为其中某一 个RDD/Hive表中的少数几个key的数据量过大,而另一个RDD/Hive表中的所有key都分布比较均 匀,那么采用这个解决方案是比较合适的。
方案实现思路:
对包含少数几个数据量过大的key的那个RDD,通过sample算子采样出一份样本来,然后统计一下每个 key的数量,计算出来数据量最大的是哪几个key。
然后将这几个key对应的数据从原来的RDD中拆分出来,形成一个单独的RDD,并给每个key都打上n以 内的随机数作为前缀,而不会导致倾斜的大部分key形成另外一个RDD。
接着将需要join的另一个RDD,也过滤出来那几个倾斜key对应的数据并形成一个单独的RDD,将每条数 据膨胀成n条数据,这n条数据都按顺序附加一个0~n的前缀,不会导致倾斜的大部分key也形成另外一个 RDD。
再将附加了随机前缀的独立RDD与另一个膨胀n倍的独立RDD进行join,此时就可以将原先相同的key打 散成n份,分散到多个task中去进行join了。
而另外两个普通的RDD就照常join即可。
最后将两次join的结果使用union算子合并起来即可,就是最终的join结果。
- 7、使用随机前缀和扩容RDD进行join
方案适用场景:如果在进行join操作时,RDD中有大量的key导致数据倾斜,那么进行分拆key也没 什么意义,此时就只能使用最后一种方案来解决问题了。
方案实现思路:
该方案的实现思路基本和“解决方案六”类似,首先查看RDD/Hive表中的数据分布情况,找到那个造成 数据倾斜的RDD/Hive表,比如有多个key都对应了超过1万条数据。
然后将该RDD的每条数据都打上一个n以内的随机前缀。
同时对另外一个正常的RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一 个0~n的前缀。
最后将两个处理后的RDD进行join即可。
方案实现原理:将原先一样的key通过附加随机前缀变成不一样的key,然后就可以将这些处理后的 “不同key”分散到多个task中去处理,而不是让一个task处理大量的相同key。该方案与“解决方 案六”的不同之处就在于,上一种方案是尽量只对少数倾斜key对应的数据进行特殊处理,由于处 理过程需要扩容RDD,因此上一种方案扩容RDD后对内存的占用并不大;而这一种方案是针对有大 量倾斜key的情况,没法将部分key拆分出来进行单独处理,因此只能对整个RDD进行数据扩容,对 内存资源要求很高。
Spark Streaming
无界流有一个开始但没有定义的结束;
有界流具有定义的开始和结束。
流处理:数据采集 --> MQ(消息队列) --> 计算引擎 --> DB
批处理:数据采集 --> MQ(消息队列) --> DB(hdfs/hive/hbase) --> 计算引擎
package com.shujia.stream
//每隔5秒的结果是独立的,结果不会累加
import org.apache.spark.sql.SQLContext
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.{Duration, Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object Demo1WordCOunt {
def main(args: Array[String]): Unit = {
/**
* 创建spark 执行环境
*/
val conf: SparkConf = new SparkConf()
.setMaster("local[2]")
.setAppName("stream")
val sc = new SparkContext(conf)
/**
* 创建 spark streaming 执行环境
*
* 指定处理数据的间隔时间
*/
val ssc = new StreamingContext(sc, Durations.seconds(5))
//读取实时数据,比如消息队列
//可以通过nc -lk 模拟一个消息队列。(再linux中安装yum install nc)
val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
linesDS
.flatMap(_.split(","))
.map((_, 1))
.reduceByKey(_ + _) //reduceByKey: 只会统计当前batch
.print() //目前每一次结果都是独立的,不会有累加
//启动spark streaming
ssc.start()
ssc.awaitTermination()//等待关闭
ssc.stop()
}
}
- 有状态算子(结果会累加)
package com.shujia.stream
//只能做一些简单的累加,统计
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
object Demo2UpdataStateByKey {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local[2]")
.setAppName("stream")
val sc = new SparkContext(conf)
val ssc = new StreamingContext(sc, Durations.seconds(5))
//指定checkpoint地址
ssc.checkpoint("spark/data/checkpoint")
val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
val wordsDS: DStream[String] = linesDS.flatMap(_.split(","))
val kvDS: DStream[(String, Int)] = wordsDS.map((_, 1))
/**
* updateStateByKey
* 有状态算子(状态: 之前计算的结果/之前统计的单词的数量)
*/
/**
* seq:当前batch每个单词后面的1
* opt: 计算每个单词的计算结果 (状态)
* 返回值: 返回新的单词数量
*
*
* 需要指定checkpoint的地址,用于保存计算的状态
*
*/
def udatteFun(seq: Seq[Int], opt: Option[Int]): Option[Int] = {
//统计当前batch的单词的数量
val currCount: Int = seq.sum
//获取之前单词统计的结果
val lastCount: Int = opt.getOrElse(0)
//返回最新单词的数量
Some(currCount + lastCount)
}
val countDS: DStream[(String, Int)] = kvDS.updateStateByKey(udatteFun)
//打印
countDS.print()
//启动
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
- 窗口
package com.shujia.stream
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Duration, Durations, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
object Demo3Window {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local[2]")
.setAppName("stream")
val sc = new SparkContext(conf)
val ssc = new StreamingContext(sc, Durations.seconds(5))
ssc.checkpoint("spark/data/checkpoint")
val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
val wordsDS: DStream[String] = linesDS.flatMap(_.split(","))
val kvDS: DStream[(String, Int)] = wordsDS.map((_, 1))
/** reduceByKeyAndWindow
* 窗口计算
* 统计最近15秒单词的数量,每个10秒统计一次 -- 滑动窗口
*
*/
/* val countDS: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow(
(x: Int, y: Int) => x + y,
Durations.seconds(15), //窗口大小
Durations.seconds(5) //滑动时间
)
countDS.print()*/
/**
* 对窗口计算进行优化,避免重复计算
*
* 需要设置checkpoint将上一个窗口计算结果保存起来
*
*/
val countDS: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow(
(x: Int, y: Int) => x + y, //累加
(x: Int, y: Int) => x - y, //减去比上一个窗口多余的数据
Durations.seconds(15), //窗口大小
Durations.seconds(5) //滑动时间
)
countDS.filter(_._2 != 0).print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
- 缉查布控
package com.shujia.stream
import java.sql.{Connection, DriverManager, PreparedStatement, ResultSet}
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import scala.collection.mutable.ListBuffer
object Demo4Bukong {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local[2]")
.setAppName("stream")
val sc = new SparkContext(conf)
val ssc = new StreamingContext(sc, Durations.seconds(5))
val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
/**
* transform 将ds 转换成rdd , 每一个batch都会执行一次
* transform 里面还是属于Driver端
*
*/
val filterDS: DStream[String] = linesDS.transform(rdd => {
//从数据库中查询布控列表
println("获取布控列表")
val mdns: List[String] = getList
//获取sparkContext
val context: SparkContext = rdd.sparkContext
/**
* 动态修改广播变量,每隔5秒广播一次
*/
//广播
val bro: Broadcast[List[String]] = context.broadcast(mdns)
//过滤数据
val filterRDD: RDD[String] = rdd.filter(line => {
val split: Array[String] = line.split(",")
val mdn: String = split(0)
bro.value.contains(mdn)
})
//返回一个rdd
filterRDD
})
/**
* foreachRDD : 将DS转换成rdd 没有返回值
*
*/
//保存数据到mysql
filterDS.foreachRDD(rdd => {
rdd.foreachPartition(iter => {
/**
* foreachPartition : 只为每一个分区创建一个链接
*
*/
//查询数据库获取布控列表
Class.forName("com.mysql.jdbc.Driver")
val con: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student", "root", "123456")
//循环一个分区的数据,将数据保存到mysql中
iter.foreach(line => {
val stat: PreparedStatement = con.prepareStatement("insert into t_result(line) values(?)")
stat.setString(1, line)
stat.executeUpdate()
})
con.close()
})
})
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
/**
* 获取布控列表的方法:
*/
def getList: List[String] = {
val mdns = new ListBuffer[String]
//查询数据库获取布控列表
Class.forName("com.mysql.jdbc.Driver")
val con: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student", "root", "123456")
val stat: PreparedStatement = con.prepareStatement("select mdn from t_mdns")
val resultSet: ResultSet = stat.executeQuery()
while (resultSet.next()) {
val mdn: String = resultSet.getString("mdn")
mdns += mdn
}
con.close()
//返回数据
mdns.toList
}
}
- StructuredStreaming
package com.shujia.stream
import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.streaming.OutputMode
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
object Demo5StructuredStreaming {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession
.builder()
.master("local[2]")
.appName("ssc")
.config("spark.sql.shuffle.partitions", 1)
.getOrCreate()
import spark.implicits._
import org.apache.spark.sql.functions._
val df: DataFrame = spark
.readStream //读取实时数据
.format("socket")
.option("host", "master")
.option("port", 8888)
.load()
val wordsDF: DataFrame = df.select(explode(split($"value", ",")) as "word")
/**
* DSL
*/
//统计单词的数量
/*val countDF: DataFrame = wordsDF
.groupBy($"word")
.agg(count($"word"))*/
/**
* 再流处理上写sql
*/
wordsDF.createOrReplaceTempView("word")
val countDF: DataFrame = spark.sql(
"""
|select word,count(1) as c from word group by word
|
""".stripMargin)
/**
* outputMode
* Update :每次只输出更新的数据
* Complete: 每次输出所有数据
* Append: 只适用于追加的数据,使用分组的算子不能再使用Append
*
*/
//在控制台打印
/*countDF
.writeStream
.format("console") //输出到控制台
.outputMode(OutputMode.Update())
.start() //启动
.awaitTermination() //等待关闭*/
//将结果保存到mysql
countDF
.writeStream
.outputMode(OutputMode.Complete())
.foreachBatch((df, l) => {
df.write
.format("jdbc")
.mode(SaveMode.Overwrite)
.option("url", "jdbc:mysql://master:3306")
.option("dbtable", "student.t_count")
.option("user", "root")
.option("password", "123456")
.save()
})
.start() //启动
.awaitTermination() //等待关闭
}
}
机器学习
机器学习,是人工智能一个基本条件,是建立大数据基础之上。从数据中提取出模型,并可以利用模型对未知的数据做出预测。
- 监督学习
定义:输入数据是由输入特征值和目标值所组成。函数的输出可以是一个连续的值(称为回归),或是输出是 有限个离散值(称作分类)。
算法:分类( k-近邻算法、贝叶斯分类、决策树与随机森林、逻辑回归、神经网络) 回归(线性回归、岭回归。
- 无监督学习
定义:输入数据是由输入特征值所组成。
算法:聚类(k-means)
- 人体指标
package com.shujia.mllib
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.ml.feature.LabeledPoint
import org.apache.spark.ml.linalg
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.mllib.regression
import org.apache.spark.mllib.util.MLUtils
import org.apache.spark.rdd.RDD
object Demo1Vertor {
def main(args: Array[String]): Unit = {
//向量: 有方向有大小。只能存double类型
//稠密向量
val dense: linalg.Vector = Vectors.dense(Array(1.0, 2.3, 4.0, 5.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0))
println(dense)
//稀疏向量
/**
* 稀疏向量再0 比较多的情况下占用的空间更小
*/
val sparse: linalg.Vector = Vectors.sparse(17, Array(0, 1, 2, 3, 7), Array(1.0, 2.3, 4.0, 5.0, 4.0))
println(sparse)
/**
* 相互转换
*/
println(sparse.toDense)
/**
* 标记数据, 一条训练数据
*/
val pos = LabeledPoint(1.0, Vectors.dense(1.0, 0.0, 3.0))
println(pos)
val neg = LabeledPoint(0.0, Vectors.sparse(3, Array(0, 2), Array(1.0, 3.0)))
println(neg)
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("svm")
val sc = new SparkContext(conf)
/**
* 读取SVM格式的数据
*/
val data: RDD[regression.LabeledPoint] = MLUtils.loadLibSVMFile(sc,"spark/data/人体指标.txt")
data.foreach(println)
}
}
package com.shujia.mllib
import org.apache.spark.ml.classification.{LogisticRegression, LogisticRegressionModel}
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}
object Demo2Person {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder()
.master("local")
.appName("person")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
import spark.implicits._
import org.apache.spark.sql.functions._
/**
* 1、特征工程
* 将原始数据转换成公式可以识别的数据
*
*
* 我们这里已经是被做完特征工程的数据了
*/
//通过spark sql 读取svm格式的数据
val personDF: DataFrame = spark
.read
.format("libsvm")
.load("spark/data/人体指标.txt")
personDF.show(false)
/**
* 2、切分训练集和测试集
* 训练集--- 训练模型
* 测试集-- 测试模型准确率
*
* 一般训练集0.7 测试集0.3
*
*/
val splitDF: Array[Dataset[Row]] = personDF.randomSplit(Array(0.7, 0.3))
//训练集
val trainDF: Dataset[Row] = splitDF(0)
//测试集
val testDF: Dataset[Row] = splitDF(1)
/**
* 2、选择算法
*
* 结果是离散值-- -分类
*
* 逻辑回归
*
*/
//构建算法执行参数
val logisticRegression: LogisticRegression = new LogisticRegression()
.setMaxIter(10) //最大迭代次数
.setFitIntercept(true) //是否有截距
/**
* 3、训练模型
* 将训练集带入算法训练模型 -- 确定k和b
*
* 模型包含公式和参数
*/
val model: LogisticRegressionModel = logisticRegression.fit(trainDF)
/**
* 4、模型评估
* 使用模型预测测试集的数据,判断和原始标记是否一直,计算准确率
*/
val frame: DataFrame = model.transform(testDF)
/**
* 计算准确率
*/
val result: DataFrame = frame.select(sum(when($"label" === $"prediction", 1).otherwise(0)) / count($"label"))
result.show()
/**
* 如果准确率还可以就保存模型
* 保存再hdfs
*/
model.save("spark/data/model")
}
}
package com.shujia.mllib
import org.apache.spark.ml.classification.LogisticRegressionModel
import org.apache.spark.ml.linalg
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.sql.SparkSession
object Demo3Model {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder()
.master("local")
.appName("person")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
/**
* 模型使用
*/
val model: LogisticRegressionModel = LogisticRegressionModel.load("spark/data/model")
/**
* 使用模型进行预测
*
* 1:4.2 2:3.0 3:2.4 4:97.3 5:57.7 6:58.5 7:89
*
* 1 1:5.7 2:4.3 3:3.5 4:130.1 5:85.9 6:84.0 7:65
*/
// val vector: linalg.Vector = Vectors.dense(Array(4.2, 3.0, 2.4, 97.3, 57.7, 58.5, 89))
val vector: linalg.Vector = Vectors.dense(Array(5.7, 4.3, 3.5, 130.1, 85.9, 84.0, 65))
//预测
val d: Double = model.predict(vector)
println(d)
}
}
- Image模型训练
package com.shujia.mllib
import org.apache.spark.ml.linalg
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
import org.apache.spark.ml.linalg.{SparseVector, Vectors}
object Demo4ReadImage {
def main(args: Array[String]): Unit = {
/**
* 特征工程
*/
val spark: SparkSession = SparkSession.builder()
.master("local[8]")
.appName("person")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
val images: DataFrame = spark
.read
.format("image")
.load("D:\\课件\\机器学习数据\\手写数字\\train")
images.printSchema()
import spark.implicits._
val data: DataFrame = images
.select($"image.origin", $"image.data")
.as[(String, Array[Byte])]
.map {
case (name: String, data: Array[Byte]) => {
val ints: Array[Int] = data.map(b => b.toInt)
//将数据归一化
val result: Array[Double] = ints.map(i => {
if (i < 0) {
1.0
} else {
0.0
}
})
//将数组转换成向量
val fea: linalg.Vector = Vectors.dense(result)
val filename: String = name.split("/").last
(filename, fea)
}
}.toDF("name", "features")
/**
* 读取标签数据
*/
val labelDF: DataFrame = spark
.read.format("csv")
.option("sep", " ")
.schema("name STRING, label DOUBLE")
.load("D:\\课件\\机器学习数据\\手写数字\\train.txt")
val trainDF: DataFrame = data
.join(labelDF, "name")
.select("label", "features")
trainDF
.write
.format("libsvm")
.mode(SaveMode.Overwrite)
.save("spark/data/images")
}
}
- 训练模型
package com.shujia.mllib
import org.apache.spark.ml.classification.{LogisticRegression, LogisticRegressionModel}
import org.apache.spark.sql.functions.{count, sum, when}
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}
object DEmo5TrainModel {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder()
.master("local[8]")
.appName("person")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
/**
* 1、读取数据
*/
val data: DataFrame = spark
.read
.format("libsvm")
.load("spark/data/images")
/**
* 2、切分训练集和测试集
*/
val split: Array[Dataset[Row]] = data.randomSplit(Array(0.7, 0.3))
val train: Dataset[Row] = split(0)
val test: Dataset[Row] = split(1)
//构建算法执行参数
val logisticRegression: LogisticRegression = new LogisticRegression()
.setMaxIter(10) //最大迭代次数
.setFitIntercept(true) //是否有截距
/**
* 训练模型
*
* 通过spark 进行分布式迭代计算
*/
val model: LogisticRegressionModel = logisticRegression.fit(train)
/**
* 4、模型评估
* 使用模型预测测试集的数据,判断和原始标记是否一直,计算准确率
*/
val frame: DataFrame = model.transform(test)
/**
*
* 计算准确率
*/
import spark.implicits._
import org.apache.spark.sql.functions._
val result: DataFrame = frame.select(sum(when($"label" === $"prediction", 1).otherwise(0)) / count($"label"))
result.show()
/**
* 保存模型
*/
model.save("spark/data/image_model")
}
}
- 识别图片
package com.shujia.mllib
import org.apache.spark.ml.classification.LogisticRegressionModel
import org.apache.spark.ml.linalg.{SparseVector, Vectors}
import org.apache.spark.sql.{DataFrame, SparkSession}
object Demo6ProImage {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder()
.master("local[8]")
.appName("person")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
val image: DataFrame = spark
.read
.format("image")
.load("D:\\课件\\机器学习数据\\手写数字\\131.jpg")
import spark.implicits._
val data: DataFrame = image
.select($"image.origin", $"image.data")
.as[(String, Array[Byte])]
.map {
case (name: String, data: Array[Byte]) => {
val ints: Array[Int] = data.map(b => b.toInt)
//将数据归一化
val result: Array[Double] = ints.map(i => {
if (i < 0) {
1.0
} else {
0.0
}
})
//将数组转换成向量
val fea: SparseVector = Vectors.dense(result).toSparse
val filename: String = name.split("/").last
(filename, fea)
}
}.toDF("name", "features")
/**
* 加载模型
*/
val model: LogisticRegressionModel = LogisticRegressionModel.load("spark/data/image_model")
//预测
val frame: DataFrame = model.transform(data)
frame.show()
}
}
package com.shujia.mllib
import org.apache.spark.sql.{DataFrame, Dataset, SaveMode, SparkSession}
import org.apache.spark.ml.clustering.{KMeans, KMeansModel}
object Demo7Kmeans {
def main(args: Array[String]): Unit = {
/**
* 据类算法-kmeana 无监督机器学习
*/
val spark: SparkSession = SparkSession.builder()
.master("local[8]")
.appName("person")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
import spark.implicits._
val pointDF: DataFrame = spark.read.format("csv")
.schema("x DOUBLE, y DOUBLE")
.load("spark/data/kmeans.txt")
val ds: Dataset[(Double, Double)] = pointDF.as[(Double, Double)]
//将每一行转换成一个数组
val train: DataFrame = ds.map(point => Array(point._1, point._2)).toDF("features")
//构建算法
val means: KMeans = new KMeans().setK(2)
//训练模型
val model: KMeansModel = means.fit(train)
//聚类
val frame: DataFrame = model.transform(train)
frame.show(10000)
}
}
- 文本分类
package com.shujia.mllib
import org.apache.spark.ml.classification.{NaiveBayes, NaiveBayesModel}
import org.apache.spark.ml.feature.{HashingTF, IDF, IDFModel, Tokenizer}
import org.apache.spark.sql.functions.{count, sum, when}
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}
object Demo8TextClass {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder()
.master("local[8]")
.appName("person")
.config("spark.sql.shuffle.partitions", 2)
.getOrCreate()
import spark.implicits._
val texts: DataFrame = spark.read
.format("csv")
.schema("label DOUBLE, text STRING")
.option("sep", "\t")
.load("spark/data/train.txt")
/**
* 通过ik分词器对文本进行分词
*/
val ikDF: DataFrame = texts.as[(Double, String)]
.map {
case (label: Double, text: String) =>
//对文本进行分词
val words: String = Demo9IK.fit(text).mkString(" ")
(label, words)
}
.filter(_._2.nonEmpty)
.toDF("label", "text")
/**
* 使用英文分词器对数据做一次转换---必须有
*/
val tokenizer: Tokenizer = new Tokenizer()
.setInputCol("text")
.setOutputCol("words")
val wordsData: DataFrame = tokenizer.transform(ikDF)
/**
* 增加TF
*/
val hashingTF: HashingTF = new HashingTF()
.setInputCol("words")
.setOutputCol("rawFeatures")
val featurizedData: DataFrame = hashingTF.transform(wordsData)
/**
* 增加idf
*/
val idf: IDF = new IDF()
.setInputCol("rawFeatures")
.setOutputCol("features")
//训练idf的模型
val idfModel: IDFModel = idf.fit(featurizedData)
//训练集
val trainDF: DataFrame = idfModel.transform(featurizedData)
/**
* 切分训练集和测试集
*/
val split: Array[Dataset[Row]] = trainDF.randomSplit(Array(0.7, 0.3))
val train: Dataset[Row] = split(0)
val test: Dataset[Row] = split(1)
/**
* 选择算法
*
* 贝叶斯分类---一般用于文本分类
*/
val naiveBayes = new NaiveBayes()
//训练模型
val model: NaiveBayesModel = naiveBayes.fit(train)
/**
* 模型评估
*/
val frame: DataFrame = model.transform(test)
val result: DataFrame = frame.select(sum(when($"label" === $"prediction", 1).otherwise(0)) / count($"label"))
result.show()
/**
* 保存模型
*/
idfModel.save("spark/data/idfmodel")
model.save("spark/data/NaiveBayesModel")
}
}
package com.shujia.mllib
import java.io.StringReader
import org.wltea.analyzer.core.{IKSegmenter, Lexeme}
import scala.collection.mutable.ListBuffer
object Demo9IK {
def main(args: Array[String]): Unit = {
val text = "数加学院牛逼"
println(fit(text))
}
def fit(text: String): List[String] = {
val words = new ListBuffer[String]
val sr = new StringReader(text)
val ik = new IKSegmenter(sr, true)
//取第一个次
var lexeme: Lexeme = ik.next()
while (lexeme != null) {
val word: String = lexeme.getLexemeText
words += word
//取下一个
lexeme = ik.next()
}
words.toList
}
}