一:算子统计
flatmap
map
mapValues
一:Spark简介
park和Hadoop的根本差异是多个作业之间的数据通信问题 : Spark多个作业之间数据通信是基于内存,而Hadoop是基于磁盘。
Spark的缓存机制比HDFS的缓存机制高效。
二:wordCount()分析 (flatmap() 与 map())
flatmap与map我的理解:
读取数据是一行一行读的,(如果每一行的数据源是 (Hello World Hello Spark)
)
补充:
用任何一个框架都需要一个环境对象 sc就是SparkContext的环境对象(环境配置对象参数)
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
flatMap()将整体拆分成为个体。
val wordGroup: RDD[(String, Iterable[(String, Int)])] = fileDataRDD.flatMap(_.split(" ")) .map((_, 1)) .groupBy(_._1) wordGroup.mapValues( list => { list.map(_._2) list.map(_._2).sum list.map(_._2).reduce(_+_) list.map(_._2).size } ) val wordCount: RDD[(String, Int)] = wordGroup.mapValues(_.map(_._2).reduce(_ + _)) wordGroup.collect().foreach(println)
// flatMap, map, groupBy, mapValues, collect方法都是Spark框架提供的方法,而不是Scala集合的方法 // 为了降低学习的成本,使用的难度,让Spark的API和Scala方法变得类似。 val words = fileDatas.flatMap(_.split(" ")) val wordToOne = words.map((_, 1)) // reduceByKey : 相同的key,对value进行两两聚合 // (word, 1), (word, 1), (word, 1) // (word, (1,1,1,1,1)) val wordCount = wordToOne.reduceByKey(_+_) wordCount.collect().foreach(println)
三:spark环境
四:Spark运行架构
YARN中ApplicationMaster能做到资源和计算的解耦合,Driver和Executor使用的计算框架是Spark,因此也可以使用其它的框架,而YARN只做资源的调度使用
Spark应用程序提交到Yarn环境中执行的时候,一般会有两种部署执行的方式:Client和Cluster。两种模式主要区别在于:Driver程序的运行节点位置。
五:Spark核心编程
首先,装饰者模式只会进行功能组合,不会执行,另外RDD的装饰者模式与IO的装饰者模式的区别在于,RDD不保留数据,只对数据进行处理,而IO中有缓冲区可以保留数据。
5.1:执行原理
六:内存磁盘如何分区及存储原理
6.1:算子的创建:
因为简明知意,makeRDD用的比较多
分区数量是如何计算的(只要牵涉的到分区就会出现数据倾斜现象)
七:算子来啦(转换与行为算子)
转换算子就是将旧的RDD转换成为新的RDD,因为要组合功能。
flatMap()保留,map()不保留
不在一个分区是无法进行比较的
map() VS mapPartitions()
mapPartitions()是批量处理的 因此入参为迭代器 出参也为迭代器,map处理一个返回一个,mapPartitions处理一批返回一批
迭代器Iterator也是集合
7.1:groupBy()
groupBy()引出shuffle
shuffle一定会落盘,因为RDD不保留数据,因此,在shuffle阶段,一定会存在Write(shuffle左边)和read(shuffle右边)
所有含有shuffle的算子都有改变分区的能力
因为隐式转换所以字符串可以直接()写下标
7.2:补充零拷贝(NIO实现)页缓存
7.3:flatMap()
7.4:glom
7.5:filter
val fileRDD = sc.textFile("data/apache.log") // filter算子返回的结果为按照规则保留的数据本身 fileRDD.filter( line => { val datas = line.split(" ") val time = datas(3) time.startsWith("17/05/2015") } ).map( line => { val datas = line.split(" ") datas(6) } ).collect.foreach(println)
7.6: sample
7.7: distinct
scala使用的是单点集合(缺点,单点的资源有限),而RDD使用的是集群去重
7.8:coalesce(缩小分区) 与 repartition(扩大分区)
7.9:sortBy()
八:双值类型数据集的算子(交集并集差集拉链)
单Value(单一数据集)数据操作,双Value(多个数据集)数据操作
九:Key - Value类型算子(隐式转换rddToPairRDDFunction)
9.1:partitionBy()
repartition : 重分区(分区数量)
partitionBy : 重分区(数据的位置 数据进入到哪个分区), 数据路由(Hash定位),分区器 如果想要让数据重新进行分区,那么需要传递分区规则对象(分区器)
Spark中默认常用的分区器:RangePartitioner(要求数据必须能排序) & HashPartitioner(默认)
思考一个问题:如果重分区的分区器和当前RDD的分区器一样怎么办?
此时不做任何处理,还是当前分区,不需要重新shuffle
9.2:reduceByKey groupByKey
TODO reduceByKey算子用于将数据集中相同的key的value数据聚合在一起实现两两聚合
spark的算子有很多是以byKey结尾的,就是说将K-V独立使用
TODO reduceByKey算子可以实现 WordCount
9.3:aggregateByKey foldByKey
需求 数据集中相同的Key的分区内取最大值,分区间求和
reduceByKey groupByKey只考虑key不考虑分区而reduceByKey 在逻辑上,
分区内和分区间是一样的都是聚合 因此reduceByKey groupByKey都不能处理该需求。
需求 如果分区内和分区间的计算规则不一定相同时,Spark提供了一个算子来实现。aggregateByKey
aggregateByKey算子存在函数柯里化,有多个参数列表
第一个参数列表中有一个参数
第一个参数表示计算初始值
第二个参数列表中有二个参数
第一个参数表示 : 分区内计算规则
第二个参数表示 : 分区间计算规则
9.4:combineByKey
![]()
reduceByKey,aggregateByKey ,foldByKey, combineByKey本质区别
9.5:sortByKey
9.6:Join leftOuterJoin rightOuterJoin fullOuterJoin
9.7 : cogroup
join算子体现的是数据的关系
join算子将两个数据集中相同的key的数据,连接在一起
zip是将两个数据集位置相同的数据,连接起来与key无关
join 可能存在笛卡尔积 也 存在shuffle()
笛卡尔积 在落盘的时候 如果存在大量相同的key的时候数据量会激增
如果能够使用其它算子实现的功能,那么不推荐使用join
rdd1.join(rdd2).collect().foreach(println)
rdd1.leftOuterJoin(rdd2).collect().foreach(println) rdd1.rightOuterJoin(rdd2).collect().foreach(println) rdd1.fullOuterJoin(rdd2).collect().foreach(println)
cogroup = connect(多个数据集) + group(单一数据集)
9.6:案例实操
方式一:
// 统计出每一个省份每个广告被点击数量排行的Top3 val fileDatas: RDD[String] = sc.textFile("data/agent.log") val groupDatas: RDD[(String, Iterable[String])] = fileDatas.groupBy( line => { val datas: Array[String] = line.split(" ") datas(1) } ) val top3: RDD[(String, List[(String, Int)])] = groupDatas.mapValues( //数据集是List(line, line, line) list => { //数据集是line val adToCount: Map[String, Int] = list.map( line => { val datas: Array[String] = line.split(" ") (datas(4), 1) } //数据集是List((省份,广告)) 按照省份进行分组统计 ).groupBy(_._1).mapValues(_.size) //Map[String, Int] [广告,点击次数] // 在 groupDatas.mapValues( 的架子中都是对value进行处理的 //元组天生的排序 数据类型[广告,点击次数] 前三条 adToCount.toList.sortBy(_._2)(Ordering.Int.reverse).take(3) } ) top3.collect().foreach(println) sc.stop() }
方式二:
val fileDatas: RDD[String] = sc.textFile("data/agent.log") // 1. 将广告进行统计分析 (word, cnt) // ((省份1,广告1), 1),((省份2,广告2), 1)((省份1,广告1), 1) // ((省份1,广告1), sum),((省份1,广告2), sum) val reduceDatas: RDD[((String, String), Int)] = fileDatas.map( line => { val datas: Array[String] = line.split(" ") ((datas(1), datas(4)), 1) } ).reduceByKey(_ + _) // 1.5 将统计结果进行格式转换 // (省份1,(广告1, sum)),(省份1,(广告2, sum)) val mapDatas: RDD[(String, (String, Int))] = reduceDatas.map { case ((prv, ad), sum) => { (prv, (ad, sum)) } } // 2. 将统计结果按照省份进行分组 // (省份1, (广告1, sum),(广告2, sum)) val prvGroupDatas: RDD[(String, Iterable[(String, Int)])] = mapDatas.groupByKey() // 3. 将分组后的数据按照点击数量进行排序(降序),取前3名 val top3: RDD[(String, List[(String, Int)])] = prvGroupDatas.mapValues( iter => { iter.toList.sortBy(_._2)(Ordering.Int.reverse).take(3) } ) top3.collect().foreach(println)
十:action行动算子(是对结果进行处理的)
TODO : 所谓的行动(Action)算子,其实就是RDD用于触发作业执行的方法,类似于IO中的read方法
转换算子的返回值为RDD 行动算子的返回值为具体的结果
行动算子和作业的关系 :1 对 1
10.2:reduce
reduce算子可以触发作业的执行,分区内先两两计算,分区间再两两计算
分区内计算都是在Executor。
分区间计算都是在Driver
10.3:count first take takeOrdered
10.4: aggregate fold
10.5: countByKey countByValue
10.5: save相关的算子
rdd.saveAsTextFile("output1")
rdd.saveAsObjectFile("output2")
rdd.saveAsSequenceFile("output3")
10.5: collect foreach
10.6: 序列化闭包检测
一个函数使用了外部的数据,并且改变了这个数据的生命周期 将数据包含到函数内部,形成了一个闭合的环境,这个环境称之为闭包环境,简称闭包
Spark在运行作业之前,会判断程序逻辑中是否包含闭包
如果存在闭包操作,那么需要检测闭包中使用的数据是否能够序列化,如果不能,那么发生错误
这个操作称之为闭包检测
十一:RDD阶段的划分
前一个阶段不执行完,后面的阶段是无法执行的。
十二:依赖关系和血缘关系(宽依赖窄依赖)
十三:持久化
(持久化只针对于当前应用程序,资源使用完之后释放资源,不会给其它节点使用。因此诞生了检查点)
将计算结果缓存起来。
数据的持久化默认将数据保存到内存中,重复使用
wordToOne.cache()
持久化操作也可以保存到磁盘中
wordToOne.persist()保存级别设置
十三:RDD CheckPoint检查点
java中的finalize方法是Object类中提供的一个方法,在GC准备释放对象所占用的内存空间之前,它将首先调用finalize()方法。
持久化只针对于当前应用程序,资源使用完之后释放资源,不会给其它节点使用。
持久化只针对于当前应用程序
跨应用程序的数据共享需要采用 检查点机制设置的方式
① :sc.setCheckpointDir("cp") 该检查点的目录一般为分布式目录以便各个节点使用
②:wordToOne.checkpoint()
持久化操作会在血缘关系中增加依赖
检查点一般会将数据保存到分布式文件系统中(HDFS)(新的数据源)
检查点会切断血缘即减少血缘依赖,因为可以把HDFS中相对可靠的数据作为新的数据源。
十四:自定义分区 RDD文件读取与保存
Hash分区:对于给定的key,计算其hashCode,并除以分区个数取余
Range分区:将一定范围内的数据映射到一个分区中,尽量保证每个分区数据均匀,而且分区间有序
十四:累加器(数据模型)(功能比较单一没有shuffle性能相对高)
累加器的现象
分析:
如果数据和RDD有关系,那么可以从Driver端发送到Executor端执行
如果执行完毕,那么可以将结果拉回到Driver端
如果数据和RDD没有关系,因为计算中存在闭包操作,所以RDD会将数据发送到Executor
但是无法将数据从Executor端拉回到Driver端。如果想要将和RDD无关的数据拉回到Driver端,RDD模型处理不了,需要采用新的数据模型——累加器
doubleAccumulator
collectionAccumulator 累加器
def main(args: Array[String]): Unit = { // TODO Spark 环境 val conf = new SparkConf().setMaster("local[*]").setAppName("WordCount") val sc = new SparkContext(conf) // TODO 1. 创建累加器 val wordCountAcc = new MyAcculumator // TODO 2. 将累加器注册到Spark中 sc.register(wordCountAcc, "WordCount") val rdd = sc.makeRDD( List( "Hello", "Hello", "Hello", "World", "Hello", ),2 ) rdd.foreach( word => { // TODO 3. 使用累加器 wordCountAcc.add(word) } ) // TODO 4. 获取累加器的结果 println(wordCountAcc.value) sc.stop() } // TODO 自定义数据累加器(WordCount) // 1. 继承AccumulatorV2 // 2. 定义数据的泛型 // IN : String // OUT : mutable.Map[String, Int] // 3. 重写方法(3(计算) + 3(状态) = 6) class MyAcculumator extends AccumulatorV2[String, mutable.Map[String, Int]]{ private val wordCountMap = mutable.Map[String, Int]() // TODO 判断当前累加器是否为初始状态 override def isZero: Boolean = { wordCountMap.isEmpty } // TODO 复制累加器 override def copy(): AccumulatorV2[String, mutable.Map[String, Int]] = { new MyAcculumator() } // TODO 重置累加器 override def reset(): Unit = { wordCountMap.clear() } // TODO 将外部的数据增加到累加器中 override def add(word: String): Unit = { val oldCnt: Int = wordCountMap.getOrElse(word, 0) wordCountMap.update(word, oldCnt + 1) } // TODO 将多个累加器进行合并 override def merge(other: AccumulatorV2[String, mutable.Map[String, Int]]): Unit = { other.value.foreach { case (word, cnt) => { val oldCnt: Int = wordCountMap.getOrElse(word, 0) wordCountMap.update(word, oldCnt + cnt) } } } // TODO 获取累加器的结果 override def value: mutable.Map[String, Int] = { wordCountMap } }
十五:Join现象引出广播变量
def main(args: Array[String]): Unit = { // TODO Spark 环境 val conf = new SparkConf().setMaster("local[*]").setAppName("WordCount") val sc = new SparkContext(conf) val rdd1 = sc.makeRDD( List( ("a", 1), ("b", 2), ) ) val rdd2 = sc.makeRDD( List( ("a", 3), ("b", 4), ) ) // ("a", 1) // ( a,(1,3)) //rdd1.join(rdd2).collect().foreach(println) // 计算的对象是以Task为单位进行数据发送的,所以可能会导致大量的数据冗余 // 所以如果计算对象占用比较大的资源,性能会急剧下降 // 为了提供数据访问效率,可以将数据发送给Executor,而不是发给Task。 // RDD数据模型只能给Task发。只能采用其他的数据模型:TODO 广播变量 val map = mutable.Map[String, Int]( ("a", 3), ("b", 4) ) // TODO 使用广播变量 val bc: Broadcast[mutable.Map[String, Int]] = sc.broadcast(map) rdd1.map { case (k, v1) => { // TODO 获取广播变量的值 var v2 = bc.value.getOrElse(k, 0) (k, (v1, v2)) } }.collect.foreach(println) sc.stop() }
计算的对象是以Task为单位进行数据发送的,所以可能会导致大量的数据冗余 且 如果计算对象占用比较大的资源,性能会急剧下降
为了提供数据访问效率,可以将数据发送给Executor,而不是发给Task。
但是RDD数据模型只能给Task发。只能采用其他的数据模型: 广播变量(可以将数据发送给Executor 使得同一个节点上的Task可以共享一份资源,并且如果有Executor节点与Driver通信比较慢的时候 可以从就近的Executor上获得需要的资源)
十五:SparkSQL(DataFrame DataSet RDD 转换)
Spark + Hive => Shark
Spark SQL是Spark用于结构化数据(structured data)处理的Spark模块。
1.2 SparkSQL特点:
无缝的整合了 SQL 查询和 Spark 编程
SparkSQL可以简化RDD的开发,提高开发效率
15.1:DataFrame 与 DataSet 与 RDD概述
SQL语言本身没有类型
工厂模式生产的对象是有相同的功能的,因为从同一条生产线下来的。
Builder模式要生产某个对象需要经过复杂的过程,但是builder模式会把这些过程顺序确定下来。
Scala中的构造方法有两种,主构造方法和辅助构造方法,辅助构造方法的函数名一定为this。
15.2:DataFrame 与 DataSet 与 RDD
DataFrame : 是SparkSQL中的核心数据模型,是弱类型的模型,不关心数据的类型操作。
因为SQL的局限性,因此在处理数据的时候,DataFrame提供了一种DSL语法(面向对象的语法)
SparkSQL 与 RDD的关系与转换
SparkSQL模块等同于是对SparkCore一个封装,专门用于结构化数据处理的场景
Dataset : 就是使用面向对象的方式操作数据, 类,属性 => 结构 就是
在DataFrame的基础上,增加了类型信息
SparkSQL中存在大量的隐式转换操作,但是一般会导入使用 import时,使用的spark并不是包名,是环境对象的名称。
DataFrame 与 DataSet的区别?
DataFrame是特定类型的DataSet[Row]
说明DataSet兼容DataFrame()是将来用的比较多的
注意:如果从内存中获取数据,spark可以知道数据类型具体是什么。如果是数字,默认作为Int处理;但是从文件中读取的数字,不能确定是什么类型,所以用bigint接收,可以和Long类型转换,但是和Int不能进行转换
SparkSQL技术选型:
15.2:UDF 与 UDAF
package com.atguigu.bigdata.spark.sql01 import org.apache.spark.sql.expressions.Aggregator import org.apache.spark.sql.{DataFrame, Encoder, Encoders, SparkSession, functions} /* 1: 创建环境 2:读取数据源 3: dataFrame 创建临时视图("user) 4:// 自定义的函数在SQL文使用 */ object SparkSQL09_DataFrame_UDAF { def main(args: Array[String]): Unit = { // TODO SparkSQL 环境\ val spark: SparkSession = SparkSession.builder .master("local[*]") .appName("SQL") .config("spark.testing.memory", "471859200") .getOrCreate() val df = spark.read.json("data/user.json") df.createOrReplaceTempView("user") // TODO UDAF使用时,需要采用特殊的方式实现自定义操作 // TODO 强类型操作不能应用于弱类型操作,如果想用,那么需要转换functions.udaf // Spark3.0以后,可以通过转换,让强类型应用在弱类型操作 spark.udf.register("avgAge",functions.udaf(new AvgAgeUDF())) // TODO : UDF // 自定义的函数在SQL文使用 spark.sql("select avgAge(age) from user").show() spark.stop() } case class CalcBuffer(var total : Long , var cnt : Long) // TODO SparkSQL中UDAF一般情况下都采用自定义函数类 // 1. 继承org.apache.spark.sql.expressions.Aggregator // 2. 定义泛型 // IN : Long // BUFF : CalcBuffer // OUT : Long // 3. 重写方法 (4 + 2(固定) = 6) class AvgAgeUDF extends Aggregator[Long,CalcBuffer,Long]{ // TODO 缓冲区的初始化 override def zero: CalcBuffer = { CalcBuffer(0l,0l) } // TODO 将输入的数据和缓冲区的数据进行聚合操作 override def reduce(buff: CalcBuffer, a: Long): CalcBuffer ={ buff.total += a buff.cnt += 1 buff } // TODO 多个缓冲区的合并操作 override def merge(b1: CalcBuffer, b2: CalcBuffer): CalcBuffer = { b1.total += b2.total b1.cnt += b2.cnt b1 } // TODO 计算数据 override def finish(reduction: CalcBuffer): Long = { reduction.total / reduction.cnt } override def bufferEncoder: Encoder[CalcBuffer] = Encoders.product override def outputEncoder: Encoder[Long] = Encoders.scalaLong } }
UDAF老版本:
def main(args: Array[String]): Unit = { // TODO SparkSQL 环境\ val spark: SparkSession = SparkSession.builder .master("local[*]") .appName("SQL") .config("spark.testing.memory", "471859200") .getOrCreate() val df = spark.read.json("data/user.json") df.createOrReplaceTempView("user") // TODO UDAF使用时,需要采用特殊的方式实现自定义操作 // TODO 强类型操作不能应用于弱类型操作,如果想用,那么需要转换functions.udaf // Spark3.0以后,可以通过转换,让强类型应用在弱类型操作 spark.udf.register("avgAge",new AvgAgeUDAF()) // TODO : UDF // 自定义的函数在SQL文使用 spark.sql("select avgAge(age) from user").show() spark.stop() } // TODO SparkSQL中UDAF一般情况下都采用自定义函数类 // 使用早期Spark版本的UDAF(弱类型) // 1. 继承UserDefinedAggregateFunction // 2. 重写方法(8) class AvgAgeUDAF extends UserDefinedAggregateFunction{ // TODO 输入数据的结构 override def inputSchema: StructType = { StructType( Array( StructField("age",LongType) ) ) } // TODO 缓冲区数据的结构 override def bufferSchema: StructType = { StructType( Array( StructField("total",LongType), StructField("cnt",LongType) ) ) } // TODO 输出数据的类型 override def dataType: DataType = LongType // TODO 计算稳定性 override def deterministic: Boolean = true // TODO 缓冲区的初始化 override def initialize(buffer: MutableAggregationBuffer): Unit = { buffer.update(0,0l) buffer.update(1,0l) } // TODO 将输入的数据和缓冲区的数据进行聚合操作 override def update(buffer: MutableAggregationBuffer, input: Row): Unit = { buffer.update(0,buffer.getLong(0) + input.getLong(0)) buffer.update(1,buffer.getLong(1)+1) } // TODO 多个缓冲区的合并操作 override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = { buffer1.update(0,buffer1.getLong(0) + buffer2.getLong(0)) buffer1.update(1,buffer1.getLong(1) + buffer2.getLong(1)) } override def evaluate(buffer: Row): Any = { buffer.getLong(0) / buffer.getLong(1) } }
UDAF_Class老版本:
def main(args: Array[String]): Unit = { // TODO SparkSQL 环境\ val spark: SparkSession = SparkSession.builder .master("local[*]") .appName("SQL") .config("spark.testing.memory", "471859200") .getOrCreate() val df = spark.read.json("data/user.json") // TODO Spark3.0版本前,强类型的UDAF不能应用于弱类型的SQL中 import spark.implicits._ val udaf: AvgAgeUDAF = new AvgAgeUDAF val ds: Dataset[User] = df.as[User] ds.select(udaf.toColumn).show() spark.stop() } case class User(id : Long , name : String , age : Long) case class CalacBuffer(var total : Long , var cnt : Long) // TODO SparkSQL中UDAF一般情况下都采用自定义函数类 // 使用早期Spark版本的UDAF(强类型) // 1. 继承Aggregator // 2. 定义泛型 // IN : User // BUFF : CalcBuffer // OUT : Long // 3. 重写方法(6) class AvgAgeUDAF extends Aggregator[User,CalacBuffer,Long]{ override def zero: CalacBuffer = { CalacBuffer(0l,0L) } override def reduce(b: CalacBuffer, user: User): CalacBuffer = { b.total += user.age b.cnt += 1 b } override def merge(b1: CalacBuffer, b2: CalacBuffer): CalacBuffer = { b1.total += b2.total b1.cnt += b2.cnt b1 } override def finish(buffer: CalacBuffer): Long = { buffer.total / buffer.cnt } override def bufferEncoder: Encoder[CalacBuffer] = Encoders.product override def outputEncoder: Encoder[Long] = Encoders.scalaLong }
15.3:通用读取方式和默认读取方式
user.json is not a Parquet file. expected magic number at tail [80, 65, 82, 49] but found [32, 53, 48, 125]
SparkSQL通用读取的方法,读取的文件为Parquet格式(列式存储格式)
val df = spark.read.load("data/users.parquet")
如果想要读取其他格式的文件也可以,需要额外增加参数进行解析
df.write.mode("append").format("json").save("output")
sparkSQL基于SparkCore
SparkCore读取数据是依赖于Hadoop
Hadoop读取数据是按行读取
SparkSQL读取JSON格式的文件,要求每一行数据必须符合JSON格式
读入数据:
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkSQL") //创建SparkSession对象 val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate() import spark.implicits._ //方式1:通用的load方法读取 spark.read.format("jdbc") .option("url", "jdbc:mysql://linux1:3306/spark-sql") .option("driver", "com.mysql.jdbc.Driver") .option("user", "root") .option("password", "123123") .option("dbtable", "user") .load().show //方式2:通用的load方法读取 参数另一种形式 spark.read.format("jdbc") .options(Map("url"->"jdbc:mysql://linux1:3306/spark-sql?user=root&password=123123", "dbtable"->"user","driver"->"com.mysql.jdbc.Driver")).load().show //方式3:使用jdbc方法读取 val props: Properties = new Properties() props.setProperty("user", "root") props.setProperty("password", "123123") val df: DataFrame = spark.read.jdbc("jdbc:mysql://linux1:3306/spark-sql", "user", props) df.show //释放资源 spark.stop()
写入数据:
case class User2(name: String, age: Long) 。。。 val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkSQL") //创建SparkSession对象 val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate() import spark.implicits._ val rdd: RDD[User2] = spark.sparkContext.makeRDD(List(User2("lisi", 20), User2("zs", 30))) val ds: Dataset[User2] = rdd.toDS //方式1:通用的方式 format指定写出类型 //方式2:通过jdbc方法 val props: Properties = new Properties() props.setProperty("user", "root") props.setProperty("password", "123123") ds.write.mode(SaveMode.Append).jdbc("jdbc:mysql://linux1:3306/spark-sql", "user", props) //释放资源 spark.stop()
十六:SparkStreaming(数据模型是离散流DStrem)
实时 离线 批量 流式
Spark是一个离线的批量数据处理的框架
SparkStreaming是一个准实时,微批次的处理框架
16.1:SparkStreaming知识点
Discretized Stream(离散流)是Spark Streaming的基础抽象,代表持续性的数据流和经过各种Spark原语操作后的结果数据流。在内部实现上,DStream是一系列连续的RDD来表示。每个RDD含有一段时间间隔内的数据。
16.2:DStream
blockingQueue队列:阻塞式队列,有反压机制,可以避免数据挤压。
采集数据的方式:DStream监控目录变化,将RDD放入到离散化流DStream中,自定义数据源。
object SparkStreaming05_DIY { def main(args: Array[String]): Unit = { //TODO SparkStreaming 环境 val conf: SparkConf = new SparkConf() .setMaster("local[*]") .setAppName("Streaming") .set("spark.testing.memory", "471859200") //StreamingContext独享的第二个构造参数表示数据采集周期 // val ssc: StreamingContext = new StreamingContext(conf,new Duration(3 * 1000)) val ssc: StreamingContext = new StreamingContext(conf, Seconds(3)) val uuidDS: ReceiverInputDStream[String] = ssc.receiverStream(new MyReceiver) uuidDS.print() //启动采集器 ssc.start() // 等待采集器结束 ssc.awaitTermination() } class MyReceiver extends Receiver[String](StorageLevel.MEMORY_ONLY){ private var flg = true override def onStart(): Unit = { //采集数据 while (flg) { //采集数据 val uuid: String = UUID.randomUUID().toString //存储数据 store(uuid) Thread.sleep(1000) } } override def onStop(): Unit = { //释放资源 flg = false } } }
十七:DStream转换(有无状态转换)
离散化流无法完成排序操作 ,离散化流其实就是每个时间段RDD的封装 ,离散化流可以转换为RDD实现操作
transform用于将离散化流实现不了的功能进行调用,transform方法是DStream的方法,称之为原语,RDD的方法称之为算子 如果想要在Driver端周期性执行一些逻辑的场合,需要使用transform。
将两个窗口key相同的连接起来
使用updateStateByKey需要对检查点目录进行配置,会使用检查点来保存状态。
reduceByKey原语不会保留中间计算结果,一旦采集周期结束,数据丢弃 ,如果想要保留中间计算结果,需要有状态操作
从检查点中恢复数据
def main(args: Array[String]): Unit = { //从检查点中恢复数据 val outerSSC = StreamingContext.getOrCreate("cp",() => { //TODO SparkStreaming 环境 val conf: SparkConf = new SparkConf() .setMaster("local[*]") .setAppName("Streaming") .set("spark.testing.memory", "471859200") //StreamingContext独享的第二个构造参数表示数据采集周期 // val ssc: StreamingContext = new StreamingContext(conf,new Duration(3 * 1000)) val ssc: StreamingContext = new StreamingContext(conf, Seconds(3)) ssc.checkpoint("cp") val socketDS: ReceiverInputDStream[String] = ssc.socketTextStream("localhost", 8888) val wordDS: DStream[String] = socketDS.flatMap(_.split(" ")) val wordToOneDS: DStream[(String, Int)] = wordDS.map((_, 1)) wordToOneDS.updateStateByKey( (seq : Seq[Int] , buffer : Option[Int]) => { Option(seq.sum + buffer.getOrElse(0)) } ).print() ssc }) //SparkStreaming (reduceByKey) + Redis内存数据库 = 有状态 outerSSC.start() outerSSC.awaitTermination() }
十八:DStream窗口
窗口划过的规律是:数据由无到有再到无(先多后少后变没)
DStream离散流有window方法:wordToOneDS.window(Seconds(9),Seconds(3))可以设置窗口的大小。
window操作中,窗口范围和滑动的幅度应该为采集周期的整数倍
如果窗口范围和滑动的幅度相同, 那么就是滚动窗口,没有重复数据 默认情况下,不使用窗口,也会有窗口操作,窗口范围取值为1个采集周期,滑动幅度也是一个采集周期。
如果窗口范围和滑动的幅度不相同, 那么就是滑动窗口
1>窗口范围大于滑动的幅度, 存在重复数据
2>窗口范围小于滑动的幅度, 存在丢失数据的可能
二十:优雅的关闭
对于流式数据,线程的启动和关闭应该是分开的,属于两个线程。
优雅的关闭,不要强制关闭,关闭前,不再接受数据,而是把手头上的数据处理完成之后再关闭。
二十一:DStream输出
如果想要将DStream进行特殊处理,那么可以转换为底层RDD实现