一.sparkSQL和Spark streaming概念
1.Spark SQL和Spark Streaming都是Apache Spark提供的重要组件,用于不同的数据处理和分析需求。
2.Spark SQL是Apache Spark中用于处理结构化数据的模块,它提供了用于数据查询、数据处理和数据分析的高级API。Spark SQL支持使用SQL查询、DataFrame API进行数据操作。Spark Streaming是Apache Spark中用于实时流数据处理的组件,它提供了高级别的API来处理实时数据流,允许以微批次的方式处理连续流数据。
二.Spark sql核心数据结构
Spark sql: SparkSession 核心数据结构:DataFrame
object Demo1WordCount {
/**
* spark sql 处理数据的步骤
* 1.读取数据源
* 2.将读取到的DF注册成一个临时视图
* 3.使用sparkSession的sql函数,编写sql语句的操作临时视图,返回的依旧是一个DataFrame
* 4.将结果写到hdfs上
*/
def main(args: Array[String]): Unit = {
val sparkSession: SparkSession = SparkSession.builder()
.master("local")
.appName("sql语法风格编写WordCount")
.getOrCreate()
/**
* spark sql 是spark core的上层api,如果要想使用rdd的编程
* 可以直接通过sparkSession获取sparkConText对象
*/
// val context: SparkContext = sparkSession.sparkContext
// spark sql的核心数据类型是DataFrame
val df1: DataFrame = sparkSession.read
.format("csv") // 读取csv格式的文件,但是实际上这种做法可以读取任意分隔符的文本文件
.option("sep", "\n") //指定读取数据的列与列之间的分隔符
.schema("line STRING") // 指定表的列字段 包括列名和列数据类型
.load("spark/data/wcs/words.txt")
/**
* sql语句是无法直接作用在dataFrame上面的
* 需要提前将使用的sql分析的dataFrame注册成一张表(临时视图)
*/
df1.createOrReplaceTempView("wcs")
三.sparkSQL
3.1、DSLWordCount
object sparkDemo2DSLWordCount {
def main(args: Array[String]): Unit = {
//创建SparkSession对象
val sparkSession: SparkSession = SparkSession.builder()
.master("local")
.appName("DSL语法编写sparkSQL")
.getOrCreate()
val df1: DataFrame = sparkSession.read
.format("csv")
.schema("line STRING")
.option("sep", "\n")
.load("spark/data/wcs/words.txt")
/**
* 如果要想使用DSL语法编写spark sql的话,需要导入两个隐式转换
*/
import org.apache.spark.sql.functions._
import sparkSession.implicits._
//老版本聚合操作
// df1.select(explode(split($"line","\\|")) as "word")
// .groupBy($"word")
// .count().show()
//新版本聚合操作
val resDF: DataFrame = df1.select(explode(split($"line", "\\|")) as "word")
.groupBy($"word")
.agg(count($"word") as "counts")
resDF.repartition(1)
.write
.format("csv")
.option("sep","\t")
.mode(SaveMode.Overwrite)
.save("spark/data/sqlOut2")
}
}
3.2、DSL语法函数
object Demo3DSLApi {
def main(args: Array[String]): Unit = {
//创建SparkSession对象
val sparkSession: SparkSession = SparkSession.builder()
.config("spark.sql.shuffle.partitions", "1")
.master("local")
.appName("DSL语法风格编写spark sql")
.getOrCreate()
/**
* 如果要想使用DSL语法编写spark sql的话,需要导入两个隐式转换
*/
//将sql中的函数,封装成spark程序中的一个个的函数直接调用,以传参的方式调用
import org.apache.spark.sql.functions._
//主要作用是,将来可以在调用的函数中,使用$函数,将列名字符串类型转成一个ColumnName类型
//而ColumnName是继承自Column类的
import sparkSession.implicits._
/**
* 读取json数据文件,转成DF
* 读取json数据的时候,是不需要指定表结构,可以自动根据json的键值来构建DataFrame
*/
//df1.show(30,truncate = false)
/**
* DSL语法的第一个函数 select
* 类似于纯sql语法中的select关键字,传入要查询的列
*/
select name,clazz from xxx;
df1.select("name","clazz").show()
df1.select($"name",$"age",$"gender").show()
//查询每个学生的姓名,原本的年龄,年龄+1
/**
* 与select功能差不多的查询函数
* 如果要以传字符串的形式给到select的话,并且还想对列进行表达式处理的话
* 可以使用selectExpr函数
*/
// df1.selectExpr("name","age","age+1 as new_age").show()
//如果要想使用select函数查询的时候对列做操作的话,可以使用$函数将列变成一个对象
df1.select($"name",$"age",$"age"+1 as "xde_age").show()
/**
* DSL语法函数:where
* === : 类似于sql中的= 等于某个值
* =!= : 类似于sql中的!= 或者<> 不等于某个值
*/
//df1.where("gender='男‘").show()
//df1.where("gender='男' and substring(clazz,0,2)='理科'").show()
//建议使用隐式转换中的功能进行处理过滤
//过滤出男生且理科的
df1.where($"gender"==='男' and substring($"clazz",0,2)==="理科").show()
//过滤出女生且文科的
df1.where($"gender"=!='男' and substring($"clazz",0,2)==="文科").show()
/**
* DSL语法函数:groupBy
*
* 非分组字段是无法出现在select查询语句中的
*/
//查询每个班级的人数
df1.groupBy("clazz")
.agg(count("clazz")as "counts")
.show()
/**
* DSL语法函数:orderBy
*/
df1.groupBy("clazz")
.agg(count("clazz") as "counts")
.orderBy($"counts".desc)
.show(3)
/**
* DSL语法函数: join
*/
val df2: DataFrame = sparkSession.read
.format("csv")
.option("sep", ",")
.schema("sid STRING,subject_id STRING,score INT")
.load("spark/data/score.txt")
// 关联的字段名不一样的情况
df2.join(df1,$"id"===$"sid","inner")
.select("id","name","age","gender","clazz","subject_id","score")
.show(10)
// 关联的字段名一样的情况
val df3: DataFrame = sparkSession.read
.format("csv")
.option("sep", ",")
.schema("id STRING,subject_id STRING,score INT")
.load("spark/data/score.txt")
df3.join(df1,"id")
.select("id","name","age","gender","clazz","subject_id","score")
.show(20)
//如果关联的字段名一样且想使用其他连接方式的话,可以将字段名字用Seq()传入,同时可以传连接方式
df3.join(df1,Seq("id"),"right")
.select("id", "name", "age", "gender", "clazz", "subject_id", "score")
.show(10)
/**
* DSL语法函数: 开窗
* 无论是在纯sql中还是在DSL语法中,开窗是不会改变原表条数
*/
//DSL语法实现
//计算每个班级总分前3的学生
df3.groupBy("id")
.agg(sum("score")as "sumScore")
.join(df1,"id")
.select($"id",$"name",$"clazz",$"sumScore",row_number() over Window.partitionBy("clazz").orderBy($"sumScore".desc) as "rn")
.where($"rn"<=3)
.write
.format("csv")
.mode(SaveMode.Overwrite)
.save("spark/data/sqlOut3")
}
}
3.3、sparkSQL读写数据
object Demo4SourceApi {
def main(args: Array[String]): Unit = {
val sparkSession: SparkSession = SparkSession.builder()
.master("local")
.appName("sourceAPI读写")
.config("spark.sql.shuffle.partitions", 1)
.getOrCreate()
//调用隐式转换
import org.apache.spark.sql.functions._
import sparkSession.implicits._
"=================读写csv格式数据===================="
//如果直接调用csv函数读取数据的话,无法做表结构设置
// sparkSession.read
// .csv("spark/data/test1.csv")//_c0 ,_c1, -c2
//使用format的形式读取数据的同时可以设置表结构
// val df1: DataFrame = sparkSession.read
// .format("csv")
// .schema("id STRING,name STRING,age INT")
// .load("spark/data/test1.csv")
// df1.show()
val df1: DataFrame = sparkSession.read
.format("csv")
.schema("id STRING,name STRING,age INT,gender STRING,clazz STRING")
.option("sep", ",")
.load("spark/data/students.txt")
df1.createOrReplaceTempView("students")
val resDF1: DataFrame = sparkSession.sql(
"""
|select
|clazz,
|count(1) as counts
|from
|students
|group by clazz
|""".stripMargin
)
// resDF1.show()
// 以csv格式写出到磁盘文件夹中
resDF1.write
.format("csv")
.mode(SaveMode.Overwrite)
.save("spark/data/sqlOut4")
/**
* ====================读写json格式的数据======================
*/
val df2: DataFrame = sparkSession.read
.json("spark/data/students.json")
df2.groupBy("age")
.agg(count("age") as "counts")
.write
.json("spark/data/sqlOut5")
/**
* =================读写parquet格式的数据====================
*
* parquet格式的文件存储,是由[信息熵]决定的
*/
val df3: DataFrame = sparkSession.read
.json("spark/data/students2.json")
//以parquet格式写出去
df3.write
.parquet("spark/data/sqlOut6")
/**
* ====================读写orc格式的数据=====================
*/
val df4: DataFrame = sparkSession.read
.json("spark/data/students2.json")
df4.write
.orc("spark/data/sqlOut7")
sparkSession.read
.orc("spark/data/sqlOut7/part-00000-c2fe2d2b-710d-4498-831f-7627e3057b26-c000.snappy.orc")
.show()
/**
* ==========================读写jdbc格式的数据======================
*
*/
sparkSession.read
.format("jdbc")
.option("url","jdbc:mysql://192.168.236.100:3306/bigdata_30?useUnicode=true&characterEncoding=UTF-8&useSSL=false")
.option("dbtable","bigdata_30.tmp")
.option("user","root")
.option("password","123456")
.load()
.show(10,truncate = false)
}
}
3.4、RDD与DataFrame转换
def main(args: Array[String]): Unit = {
val sparkSession: SparkSession = SparkSession.builder()
.master("local")
.appName("rdd与df之间的转换")
.config("spark.sql.shuffle.partitions", "1")
.getOrCreate()
//通过SparkSession获取sparkContext对象
val sparkContext: SparkContext = sparkSession.sparkContext
import sparkSession.implicits._
val linesRDD: RDD[String]
=sparkContext.textFile("spark/data/students.txt")
val stuRDD: RDD[(String, String, String, String, String)] = linesRDD.map((line: String) => {
line.split(",") match {
case Array(id: String, name: String, age: String, gender: String, clazz: String) =>
(id, name, age, gender, clazz)
}
})
val resRDD1: RDD[(String, Int)] = stuRDD.groupBy(_._5)
.map((kv: (String, Iterable[(String, String, String, String, String)])) => {
(kv._1, kv._2.size)
})
val df1: DataFrame = resRDD1.toDF
val df2: DataFrame = df1.select($"_1" as "clazz", $"_2" as "counts")
// DataFrame->RDD .rdd
val resRDD2: RDD[Row] = df2.rdd
resRDD2.map{
case Row(clazz:String,counts:Int)=>
s"班级:$clazz,人数:$counts"
}.foreach(println)
}
}
3.5、开窗函数
/** * 开窗:over * 聚合开窗函数:sum count lag(取上一条) lead(取后一条) * 排序开窗函数:row_number rank dense_rank */
//统计总分年级排名前十学生各科的分数
val resDS1: Dataset[Row] = scoresDF.join(studentsDF, "id")
.withColumn("sumScore", sum("score") over Window.partitionBy("id"))
.withColumn("rn", dense_rank() over Window.partitionBy(substring($"clazz", 0, 2)).orderBy($"sumScore".desc))
.where($"rn" <= 10)
.limit(20)
3.6、spark作业提交到Yarn集群
def main(args: Array[String]): Unit = {
val sparkSession: SparkSession = SparkSession.builder()
// .master("local")//不是本地运行模式注释
.appName("提交到yarn 计算每个班级的人数")
//参数设置的优先级:代码优先级 > 命令优先级 > 配置文件优先级
.config("spark.sql.shuffle.partitions", "1")
.getOrCreate()
import org.apache.spark.sql.functions._
import sparkSession.implicits._
val df1: DataFrame = sparkSession.read
.format("csv")
.schema("id STRING,name STRING,age INT,gender STRING,clazz STRING")
.load("/bigdata30/spark_in/students.csv")
//.load(args(0))
val df2: DataFrame = df1.groupBy($"clazz")
.agg(count($"id") as "counts")
df2.write
.csv("/bigdata30/spark_out1")
// .csv(args(1))
/**
* 1.spark-submit --master yarn --deploy-mode client --class com.shujia.sql.Demo8SubmitYarn --conf spark.sql.shuffle.partitions=100 spark-1.0.jar
* 2.spark-submit --master yarn --deploy-mode client --class com.shujia.sql.Demo8SubmitYarn spark-1.0.jar /bigdata30/spark_in/students.csv /bigdata30/spark_out2/
* 3.spark-submit --master yarn --deploy-mode client --class com.shujia.sql.Demo8SubmitYarn -- conf spark.sql.shuffle.partitions=100 spark-1.0.jar /bigdata30/spark_in/students.csv /bigdata30/spark_out3/
*/
}
}
3.7、spark调用hive
object Demo10SparkOnHive {
def main(args: Array[String]): Unit = {
val sparkSession: SparkSession = SparkSession.builder()
.master("local")
.appName("spark调用hive")
//参数设置的优先级:代码优先级 > 命令优先级 > 配置文件优先级
.config("spark.sql.shuffle.partitions", "1")
.enableHiveSupport() // 开启hive的配置,函数可以将字符串表达式转换为Spark SQL中的列或表达式对象,
.getOrCreate()
sparkSession.sql("use bigdata30")
sparkSession.sql("select * from sqoop_students1 limit 10").show(truncate = false)
}
}
3.8、udf自定义函数
object Demo12UDF {
def main(args: Array[String]): Unit = {
val sparkSession: SparkSession = SparkSession.builder()
.master("local")
.appName("udf自定义函数")
.getOrCreate()
import org.apache.spark.sql.functions._
import sparkSession.implicits._
//当你配置了hdfs等一些配置文件,那么默认读取路径是hadoop的,否则是本地
val df1: DataFrame = sparkSession.read
.format("csv")
.schema("id STRING,name STRING,age INT,gender STRING,clazz STRING")
.option("sep", ",")
.load("/bigdata30/students.csv")//读取路径是hadoop
// df1.select($"id",$"name",concat(expr("'数加:'"),$"name") as "new_name").show()
// concat(expr("'数加:'"),$"name")//对姓名做拼接
// val 唐三: UserDefinedFunction = udf("数加:" + _)
// df1.select($"id", $"name", 唐三($"name")).show()
val huang: UserDefinedFunction = udf("数加:" + _)//调用函数实现拼接
df1.select($"id", $"name", huang($"name"))//自定义函数
//直接在udf创建一个变量,可以在dsl语法中直接使用
/**
* +----------+------+-----------+
* | id| name| UDF(name)|
* +----------+------+-----------+
* |1500100001|施笑槐|数加:施笑槐|
* |1500100002|吕金鹏|数加:吕金鹏|
* |1500100003|单乐蕊|数加:单乐蕊|
*/
df1.createOrReplaceTempView("students")
//一张表写sql语句要想使用自定函数的话,要
//将自定义的函数变量注册成sql语句中的函数
sparkSession.udf.register("sh",huang)
sparkSession.sql(
"""
|select
|id,
|name,
|sh(name) as new_name
|from
|students
|""".stripMargin).show()
}}
/*==========================================================*/
class Demo13Str extends UDF{
def evaluate(line:String):String="大威天龙"+line
}
/**
* 1、自定义类继承UDF类,重写evaluate方法
* 2、打包,spark-1.0.jar 将jar包放到spark目录下的jars目录下
* 3、在spark-sql命令行中注册函数
* create function dwtl as "com.shujia.sql.Demo13Str"
* */
四、spark Streaming
4.1、Spark streaming单词统计
object Demo1WordCount {
def main(args: Array[String]): Unit = {
/**
* Spark core: SparkContext(主要入口) 核心数据结构:RDD
* Spark sql: SparkSession 核心数据结构:DataFrame
* Spark streaming: StreamingContext 核心数据结构:DStream(底层封装了RDD)
*/
val conf = new SparkConf()
conf.setMaster("local[2]")
conf.setAppName("spark Streaming 单词统计")
val sparkContext = new SparkContext(conf)
//创建Spark Streaming的运行环境,和前两个模块是不一样的
//Spark Streaming是依赖于Spark core的环境的
//this(sparkContext: SparkContext, batchDuration: Duration)
//Spark Streaming处理之前,是有一个接收数据的过程
//batchDuration,表示接收多少时间段内的数据
val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))
//Spark Streaming程序理论上是一旦启动,就不会停止,除非报错,人为停止,停电等其他突然场景导致程序终止
//监控一个端口号中的数据,手动向端口号中打数据
//nc -lk xxx用于在Linux系统中进行网络连接和端口监听
/**
* -l:表示监听模式,即让nc在指定的端口上等待连接请求。
* -k:保持监听模式,即使连接被关闭也继续保持监听状态。
* 12345:指定了监听的端口号为12345。
*/
val rids: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 12345)
val resDS: DStream[(String, Int)] = rids.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
println("------------------------------")
resDS.print()
println("------------------------------")
/**
* sparkStreaming启动的方式和前两个模块启动方式不一样
*
* sparkStreaming数据不停止,stop是当前期间的停止和开始
*/
streamingContext.start()
streamingContext.awaitTermination()
streamingContext.stop()//每个批次的开始,每个批次的结束
}
}
/** * updateStateByKey[S: ClassTag](updateFunc: (Seq[V], Option[S]) => Option[S]): DStream[(K, S)] * Seq: 序列,表示历史键对应的值组成的序列 (hello, seq:[1,1,1]) * Option: 当前批次输入键对应的value值,如果历史中没有该键,这个值就是None, 如果历史中出现了这个键,这个值就是Some(值) * * 有状态算子使用注意事项: * 1、有状态算子ByKey算子只适用于k-v类型的DStream * 2、有状态算子使用的时候,需要提前设置checkpoint的路径,因为需要将历史批次的结果存储下来 */
4.2、spark streaming 窗口案例
/** * 如果只是为了计算当前批次接受的数据,直接调用reduceByKey * 如果想要将历史数据和当前最新批次的数据结合处理,需要调用状态算子updateStateByKey * 如果想要实现滑动窗口和滚动窗口,需要使用窗口类算子reduceByKeyAndWindow */ /** * def reduceByKeyAndWindow(reduceFunc: (V, V) => V,windowDuration: Duration,slideDuration: Duration): DStream[(K, V)] * reduceFunc 编写处理相同的键对应的value值进行处理 * windowDuration 设置窗口大小 * slideDuration 设置滑动大小 */
def main(args: Array[String]): Unit = {
/**
* 创建spark streaming环境
* 旧版本创建方式
*/
// val conf: SparkConf = new SparkConf().setMaster("local[2]")
// .setAppName("窗口案例")
// val sparkContext = new SparkContext(conf)
// val sc = new StreamingContext(sparkContext, Durations.seconds(5))
/**
* 新版本创建方式
*/
val context: SparkContext = SparkSession.builder()
.master("local[2]")
.appName("窗口案例")
.config("spark.sql.shuffle.partitions", 1)
.getOrCreate().sparkContext
val sc = new StreamingContext(context, Durations.seconds(5))
//端口号:1000 ~ 65535
val infoDS: ReceiverInputDStream[String] = sc.socketTextStream("master", 12315)
val kvDS: DStream[(String, Int)] = infoDS.flatMap(_.split(" "))
.map((_, 1))
//每间隔slideDuration大小的时间计算一次数据,计算时间的范围是最近windowDuration大小时间的数据
val resDS1: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow((v1: Int, v2: Int) => v1 + v2, Durations.seconds(15), Durations.seconds(5))
/**
* 当窗口大小与滑动大小一致的时候,那么就会从滑动窗口转变成滚动窗口的效果
*/
val resDS2: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow((v1: Int, v2: Int) => v1 + v2, Durations.seconds(10), Durations.seconds(10))
resDS2.print()
sc.start()
sc.awaitTermination()
sc.stop()
}
}
4.3、RDD与stream的关系
RDD和Stream在Apache Spark中扮演了不同的角色和功能,RDD是静态数据集的抽象和处理模型,而Stream是实时流数据的抽象和处理模型。Spark Streaming通过将实时流数据转换成RDD序列来实现对流式数据的处理,因此可以说Stream(Spark Streaming)是基于RDD的一种实时处理扩展。
def main(args: Array[String]): Unit = {
//使用DataFrame的语法
val sparkSession: SparkSession = SparkSession.builder()
.master("local[2]")
.appName("rdd与stream的关系")
.config("spark.sql.shuffle.partitions", "1")
.getOrCreate()
import org.apache.spark.sql.functions._
import sparkSession.implicits._
//使用RDD的语法
val sparkContext: SparkContext = sparkSession.sparkContext
//使用DStream的语法
val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))
val infoDS: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 12345)
//如果DS不是键值形式的话,可以单独调用window函数进行设置窗口的形式
val kvDS: DStream[String] = infoDS.window(Durations.seconds(10), Durations.seconds(5))
/**
* foreachRDD:在DS中使用rdd语法造作数据
* 缺点:该函数时没有返回值的
* 需求:我们在使用DS自动RDD同时,想要使用结束后,得到一个新的DS
*/
kvDS.foreachRDD((rdd:RDD[String])=>{
val resRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
resRDD.foreach(println)
//rdd和df之间可以转换
val df1: DataFrame = rdd.toDF.select($"value" as "info")
df1.createOrReplaceTempView("words")
val resDF: DataFrame = sparkSession.sql(
"""
|select
|t1.wds as word,
|count(1) as counts
|from
|(select
|explode(split(info,' ')) as wds
|from words)t1
|group by t1.wds
|""".stripMargin)
resDF.show()
})
}
4.4、transform语法(转换操作)
/** * 面试题:foreachRDD与transform的区别 * 1.返回值:foreachRDD 返回类型为 void,因为它用于执行输出操作,例如写入数据到外部系统,而不生成新的 DStream。 * transform 可以返回一个新的 DStream,因为它允许对每个 RDD 进行转换操作。 * 2.使用场景:foreachRDD 适合用于将 DStream 中的数据发送到外部系统进行持久化,如将数据存储到数据库、写入文件或发送消息。 * transform 适合用于需要在每个微批次中对数据进行复杂转换的场景,例如数据清洗、特征提取等。 * 3.执行时机:foreachRDD 在每个微批次完成后执行,对生成的 RDD 进行操作。 * transform 在每个微批次中对 DStream 进行转换操作,生成新的 DStream, * 这些操作是在 Spark Streaming 内部实现的。 */
object Demo5Transform {
def main(args: Array[String]): Unit = {
//使用DataFrame的语法
val sparkSession: SparkSession = SparkSession.builder()
.master("local[2]")
.appName("transform转换操作")
.config("spark.sql.shuffle.partitions", "1")
.getOrCreate()
import org.apache.spark.sql.functions._
import sparkSession.implicits._
//使用RDD的语法
val sparkContext: SparkContext = sparkSession.sparkContext
//使用DStream的语法
val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))
val infoDS: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 10086)
val resDS: DStream[(String, Int)] = infoDS.transform((rdd: RDD[String]) => {
//直接对rdd进行处理,返回新的rdd
// val resRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" "))
// .map((_, 1))
// .reduceByKey(_ + _)
// resRDD
//将rdd转df,使用sql做分析
//rdd和df之间可以转换
val df1: DataFrame = rdd.toDF.select($"value" as "info")
df1.createOrReplaceTempView("words")
val resDF: DataFrame = sparkSession.sql(
"""
|select
|t1.wds as word,
|count(1) as counts
|from
|(
|select
|explode(split(info,' ')) as wds
|from words) t1
|group by t1.wds
|""".stripMargin)
val resRDD: RDD[(String, Int)] = resDF.rdd.map((row: Row) => (row.getAs[String](0), row.getAs[Int](1)))
resRDD
})
resDS.print()
streamingContext.start()
streamingContext.awaitTermination()
streamingContext.stop()
}
}
4.5、sparkStreaming YarnSubmit
object Demo6YarnSubmit {
def main(args: Array[String]): Unit = {
//使用DataFrame的语法
val sparkSession: SparkSession = SparkSession.builder()
// .master("local[2]")
.appName("yarn集群提交案例")
.config("spark.sql.shuffle.partitions", "1")
.getOrCreate()
import org.apache.spark.sql.functions._
import sparkSession.implicits._
//使用RDD的语法
val sparkContext: SparkContext = sparkSession.sparkContext
//使用DStream的语法
val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))
val infoDS: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 10086)
val resDS: DStream[(String, Int)] = infoDS.transform((rdd: RDD[String]) => {
//直接对rdd进行处理,返回新的rdd
// val resRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" "))
// .map((_, 1))
// .reduceByKey(_ + _)
// resRDD
//将rdd转df,使用sql做分析
//rdd和df之间可以转换
val df1: DataFrame = rdd.toDF.select($"value" as "info")
df1.createOrReplaceTempView("words")
val resDF: DataFrame = sparkSession.sql(
"""
|select
|t1.wds as word,
|count(1) as counts
|from
|(
|select
|explode(split(info,' ')) as wds
|from words) t1
|group by t1.wds
|""".stripMargin)
val resRDD: RDD[(String, Int)] = resDF.rdd.map((row: Row) => (row.getAs[String](0), row.getAs[Int](1)))
resRDD
})
resDS.print()
streamingContext.start()
streamingContext.awaitTermination()
streamingContext.stop()
}
4.6、spark DS 存储文件
def main(args: Array[String]): Unit = {
//使用DataFrame的语法
val sparkSession: SparkSession = SparkSession.builder()
.master("local[2]")
.appName("SaveFile")
.config("spark.sql.shuffle.partitions", "1")
.getOrCreate()
import org.apache.spark.sql.functions._
import sparkSession.implicits._
//使用RDD的语法
val sparkContext: SparkContext = sparkSession.sparkContext
//使用DStream的语法
val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))
val infoDS: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 10086)
val resDS: DStream[(String, Int)] = infoDS.transform((rdd: RDD[String]) => {
//将rdd转df,使用sql做分析
//rdd和df之间可以转换
val df1: DataFrame = rdd.toDF.select($"value" as "info")
df1.createOrReplaceTempView("words")
val resDF: DataFrame = sparkSession.sql(
"""
|select
|t1.wds as word,
|count(1) as counts
|from
|(
|select
|explode(split(info,' ')) as wds
|from words) t1
|group by t1.wds
|""".stripMargin)
val resRDD: RDD[(String, Int)] = resDF.rdd.map((row: Row) => (row.getAs[String](0), row.getAs[Int](1)))
resRDD
})
// resDS.print()
//将结果存储到磁盘中
//只能设置文件夹的名字和文件的后缀
//每一批次运行,都会产生新的小文件夹,文件夹中有结果数据文件
resDS.saveAsTextFiles("spark/data/streamOut/stream","txt")
streamingContext.start()
streamingContext.awaitTermination()
streamingContext.stop()
}
}
4.7、sparkDS调用Mysql
def main(args: Array[String]): Unit = {
//使用DataFrame的语法
val sparkSession: SparkSession = SparkSession.builder()
.master("local[2]")
.appName("DS调用mysql")
.config("spark.sql.shuffle.partitions", "1")
.getOrCreate()
import org.apache.spark.sql.functions._
import sparkSession.implicits._
//使用RDD的语法
val sparkContext: SparkContext = sparkSession.sparkContext
//使用DStream的语法
val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))
val infoDS: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 10086)
infoDS.foreachRDD((rdd:RDD[String])=>{
println("======================= 正在处理一批数据 ==========================")
//处理rdd中每一条数据
rdd.foreach((line:String)=>{
//如果将创建连接的代码写在这里,这样的话,每条数据都会创建一次连接
/**
* 创建与数据库连接对象
*/
//注册驱动
Class.forName("com.mysql.jdbc.Driver")
//创建数据库连接对象
val conn: Connection = DriverManager.getConnection(
"jdbc:mysql://master:3306/bigdata30?useUnicode=true&characterEncoding=UTF-8&useSSL=false",
"root",
"123456"
)
//创建预编译对象
val statement: PreparedStatement = conn.prepareStatement("insert into students values(?,?,?,?,?)")
val info: Array[String] = line.split(",")
statement.setInt(1,info(0).toInt)
statement.setString(2,info(1))
statement.setInt(3,info(2).toInt)
statement.setString(4,info(3))
statement.setString(5,info(4))
//执行sql语句
statement.executeUpdate()
//释放资源
statement.close()
conn.close()
})
})
/**
* rdd中有一个算子foreachPartition
* rdd本质是由一系列分区构成的,如果我们可以将分区数设置为1,且每个分区创建一个连接不就好了么
*/
infoDS.foreachRDD((rdd: RDD[String]) => {
println("======================= 接收到 5s 一批次数据 ==========================")
rdd.repartition(2)
println(s" DS封装的RDD中的分区数为:${rdd.getNumPartitions} ")
/**
* foreachPartition,处理一个分区的数据
* 将一个分区的数据,封装成了一个迭代器
*/
rdd.foreachPartition((itr: Iterator[String]) => {
println("======================= 正在处理一个分区的数据 ==========================")
//如果将创建连接的代码写在这里,这样的话,每条数据都会创建一次连接
/**
* 创建与数据库连接对象
*/
//注册驱动
Class.forName("com.mysql.jdbc.Driver")
//创建数据库连接对象
val conn: Connection = DriverManager.getConnection(
"jdbc:mysql://master:3306/bigdata30?useUnicode=true&characterEncoding=UTF-8&useSSL=false",
"root",
"123456"
)
//创建预编译对象
val statement: PreparedStatement = conn.prepareStatement("insert into students values(?,?,?,?,?)")
println("========================= 创建了一次连接 =========================")
itr.foreach((line: String) => {
val info: Array[String] = line.split(",")
statement.setInt(1, info(0).toInt)
statement.setString(2, info(1))
statement.setInt(3, info(2).toInt)
statement.setString(4, info(3))
statement.setString(5, info(4))
//执行sql语句
statement.executeUpdate()
})
statement.close()
conn.close()
})
})
streamingContext.start()
streamingContext.awaitTermination()
streamingContext.stop()
}
}
五、spark调优
/** * 避免创建重复的RDD * 尽可能复用同一个RDD * 对多次使用的RDD进行持久化 * 尽量避免使用shuffle类算子 * 使用map-side预聚合的shuffle操作 * 使用高性能的算子 * 广播大变量 * 使用Kryo优化序列化性能 * 优化数据结构 * 使用高性能的库fastutil */
5.1、缓存优化
object Demo1Cache {
def main(args: Array[String]): Unit = {
val sparkSession: SparkSession = SparkSession.builder()
.master("local[2]")
.appName("缓存优化")
.config("spark.sql.shuffle.partitions", 1)
.getOrCreate()
val sc: SparkContext = sparkSession.sparkContext
val stuRDD: RDD[String] = sc.textFile("spark/data/students.txt")
/**
* 缓存的目的是避免每一次job作业执行的时候,都需要从第一个rdd算起
* 对重复使用RDD进行缓存
* cache 设置不了缓存级别
* persist 可以设置缓存级别
*/
// stuRDD.cache() // 默认的缓存级别是MEMORY_ONLY
stuRDD.persist(StorageLevel.MEMORY_ONLY_SER)
/**
* 计算每个班级的人数
*/
val resClazzRDD: RDD[(String, Int)] = stuRDD.map(_.split(",") match {
case Array(_, _, _, _, clazz: String) =>
(clazz, 1)
}).reduceByKey(_ + _)
resClazzRDD.foreach(println)
/**
* 计算每个性别的人数
*/
val resGenderRDD: RDD[(String, Int)] = stuRDD.map(_.split(",") match {
case Array(_, _, _, gender: String, _) =>
(gender, 1)
}).reduceByKey(_ + _)
resGenderRDD.foreach(println)
// while (true){
//
// }
}
}
5.2、使用高性能算子
使用reduceByKey/aggregateByKey替代groupByKey
def main(args: Array[String]): Unit = {
//使用reduceByKey/aggregateByKey替代groupByKey
val sparkSession: SparkSession = SparkSession.builder()
.config("spark.sql.shuffle.partitions", "1")
.master("local[2]")
.appName("aggregateByKey替代groupByKey")
.getOrCreate()
val sparkContext: SparkContext = sparkSession.sparkContext
val stuRDD: RDD[String] = sparkContext.textFile("spark/data/students.txt")
val clazzKVRDD: RDD[(String, Int)] = stuRDD.map(_.split(",") match {
case Array(_, _, _, _, clazz: String) =>
(clazz, 1)
})
//groupByKey 不做聚合,只做前一个RDD中的分区中数据相同的键到后一个RDD中同一个分区 (尽量使用reduceByKey去替代)
val resRDD2: RDD[(String, Int)] = clazzKVRDD.groupByKey()
.map((kv: (String, Iterable[Int])) => {
(kv._1, kv._2.sum)
})
resRDD2.foreach(println)
//reduceByKey的使用,分组之后,直接使用聚合
//理解为普通的MapReduce中的根据相同的键进入到同一个reduce, 然后在reduce端聚合
//实际上这里对应的是前一个RDD中的分区中数据相同的键到后一个RDD中同一个分区,在后一个RDD分区中的聚合
// val resRDD: RDD[(String, Int)] = clazzKVRDD.reduceByKey(_ + _)
// resRDD.foreach(println)
//aggregateByKey
//aggregateByKey(zeroValue: U)(seqOp: (U, V) => U, combOp: (U, U) => U)
//zeroValue: 初始值,这个参数只会被后面第一个参数函数所使用
//seqOp: 相当于map端预聚合的逻辑
//combOp: 相当于reduce端的聚合逻辑
val resRDD: RDD[(String, Int)] = clazzKVRDD.aggregateByKey(0)(
//相当于map端预聚合的逻辑
(a1: Int, a2: Int) => a1 + a2,
//相当于reduce端的聚合逻辑
(b1: Int, b2: Int) => b1 + b2
)
resRDD.foreach(println)
}
}
使用mapPartitions替代普通map Transformation算子
object Demo3MapPartitions {
def main(args: Array[String]): Unit = {
//使用mapPartitions替代普通map Transformation算子
//使用reduceByKey/aggregateByKey替代groupByKey
val sparkSession: SparkSession = SparkSession.builder()
.master("local")
.appName("mapPartitions代替map")
.config("spark.sql.shuffle.partitions", 11)
.getOrCreate()
val context: SparkContext = sparkSession.sparkContext
val linesRDD: RDD[String] = context.textFile("spark/data/ant_user_low_carbon.txt")
println(s"linesRDD的分区数为:${linesRDD.getNumPartitions}")
/**
* map算子主要作用是,遍历RDD中的每一条数据,进行处理返回新的一条数据
* 如果在处理过程中,需要创建工具对象的话,那么使用map不太好,原因是因为每一条数据都需要new一下
* 可能会造成内存溢出
*/
val resRDD1: RDD[(String, String, String)] = linesRDD.map((line: String) => {
println("======================创建一次对象====================")
val info: Array[String] = line.split("\t")
val t1: String = info(1)
val df1 = new SimpleDateFormat("yyyy/MM/dd")
val date: Date = df1.parse(t1)
val df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val t2: String = df2.format(date)
(info(0), t2, info(2))
})
resRDD1.foreach(println)
/**
* 实际上针对上面的案例,我们可以针对rdd中的每一个分区创建一个工具对象,在每条数据上使用
* mapPartitions,将每一个分区中的数据封装成了一个迭代器
*/
val resRDD2: RDD[(String, String, String)] = linesRDD.mapPartitions((itr: Iterator[String]) => {
println("======================创建一次对象====================")
val df1 = new SimpleDateFormat("yyyy/MM/dd")
val df2 = new SimpleDateFormat("YYYY-MM-DD,HH:MM:SS")
itr.map((linesRDD: String) => {
val info: Array[String] = linesRDD.split(",")
val t1: String = info(1)
val date: Date = df1.parse(t1)
val t2: String = df2.format(date)
(info(0), t2, info(2))
})
})
resRDD2.foreach(println)
}
}
5.3、重分区(repartition,coalesce)
重分区是优化 Apache Spark 作业性能和效率的重要手段之一,特别是在处理大数据时,合理的分区策略能够显著提升整体的数据处理速度和效率。
重分区的主要好处包括:1. 数据均衡,2. 提高并行度,3. 数据局部性优化,4. 数据压缩和优化存储,5. 算子性能优化
object Demo4Coalesce1 {
def main(args: Array[String]): Unit = {
//repartition:coalesce(numPartitions,true) 增多分区使用这个
//coalesce(numPartitions,false) 减少分区 没有shuffle只是合并 partition
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("重分区")
val sc: SparkContext = new SparkContext(conf)
val linesRDD: RDD[String] = sc.textFile("spark/data/students.txt")
println(s"lineRDD的分区数:${linesRDD.getNumPartitions}")
/**
* 使用repartition
*/
//增大分区数,使用repartition,返回一个新的rdd,会产生shuffle
val resRDD1: RDD[String] = linesRDD.repartition(10)
println(s"resRDD1的分区为:${resRDD1.getNumPartitions}")
resRDD1.foreach(println)
//减少分区数,使用repartition,返回一个新的rdd,会产生shuffle
val resRDD2: RDD[String] = linesRDD.repartition(1)
println(s"resRDD2的分区为:${resRDD2.getNumPartitions}")
resRDD2.foreach(println)
/**
* coalesce
*
* 1、默认增大分区是不会产生shuffle的
* 2、合并分区直接给分区数,不会产生shuffle
*
* 可以手动指定增大分区 linesRDD.coalesce(10,true),从而产生shuffle
* 可以手动指定减小分区 linesRDD.coalesce(1,true),从而产生shuffle
*/
val resRDD3: RDD[String] = linesRDD.coalesce(10)
println(s"resRDD3的分区数:${resRDD3.getNumPartitions}")
resRDD3.foreach(println)
val resRDD4: RDD[String] = linesRDD.coalesce(1,true)
println(s"resRDD4的分区数:${resRDD4.getNumPartitions}")
resRDD4.foreach(println)
while (true){
}
}
}
def main(args: Array[String]): Unit = {
//repartition:coalesce(numPartitions,true) 增多分区使用这个
//coalesce(numPartitions,false) 减少分区 没有shuffle只是合并 partition
val conf: SparkConf = new SparkConf()
.setMaster("local")
.setAppName("重分区")
val sc = new SparkContext(conf)
val lineRDD: RDD[String] = sc.textFile("spark/data/stu1/*")//stu/*包括stu1.txt,stu2.txt,stu3.txt
println(s"lineRDD的分区数:${lineRDD.getNumPartitions}")
/**
* Coalesce算子通常是用在合并小文件时候使用
* 对应的spark core中的话,通常使用该算子进行合并分区
*/
val lineRDD2: RDD[String] = lineRDD.coalesce(1)
lineRDD2.foreach(println)
println(s"lineRDD2的分区数:${lineRDD2.getNumPartitions}")
}
}
5.4、广播大变量

1.开发过程中,会遇到需要在算子函数中使用外部变量的场景(尤其是大变量,比如 100M以上的大集合),那么此时就应该使用Spark的广播(Broadcast)功能来提 升性能
2.如果使用的外部变量比较大,建议使用Spark的广播功能,对该变量进行广播。广播 后的变量,会保证每个Executor的内存中,只驻留一份变量副本,而Executor中的 task执行时共享该Executor中的那份变量副本。这样的话,可以大大减少变量副本 的数量,从而减少网络传输的性能开销,并减少对Executor内存的占用开销,降低 GC的频率
object Demo6Join {
def main(args: Array[String]): Unit = {
val sparkSession: SparkSession = SparkSession.builder()
.master("local")
.appName("spark sql使用广播变量")
.config("spark.sql.shuffle.partitions", "1")
.getOrCreate()
import org.apache.spark.sql.functions._
import sparkSession.implicits._
val stuDF: DataFrame = sparkSession.read
.format("csv")
.option("seq", ",")
.schema("id STRING,name STRING,age INT,gender STRING,clazz STRING")
.load("spark/data/students.txt")
val scoresDF: DataFrame = sparkSession.read
.format("csv")
.option("seq", ",")
.schema("id STRING,subject_id STRING,score INT")
.load("spark/data/score.txt")
/**
* 如果在spark sql中是两个DF进行join关联的话,并且运行模式是local模式的话,会自动地将关联的DF进行广播
* 如果不是local模式,不会自动进行,需要手动将要广播的DF给广播出去
*
* 广播大变量,1G的变量
*
* 会进行两次job作业
* 第一次是将关联的DF广播
* 第二次是使用广播的DF进行关联
*/
val resDF: DataFrame = stuDF.join(scoresDF.hint("broadcast"), "id")
resDF.show()
while (true){
}
}
}
5.5、使用Kryo优化序列化性能
Spark支持使用Kryo序列化机制。Kryo序列化机制,比默认的Java序列化机制,速度要快 ,序列化后的数据要更小,大概是Java序列化机制的1/10。所以Kryo序列化优化以后,可 以让网络传输的数据变少;在集群中耗费的内存资源大大减少
def main(args: Array[String]): Unit = {
val sparkSession: SparkSession = SparkSession.builder()
.config("spark.sql.shuffle.partitions", "1")
//将序列化方式设置为Kryo的序列化方式
.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
//自定义一个序列化类,指定要序列化的东西
.config("spark.kryo.registrator", "com.shujia.opt.Demo8Kryo")
.master("local[2]")
.appName("缓存优化")
.getOrCreate()
val sparkContext: SparkContext = sparkSession.sparkContext
val studentsRDD: RDD[Student] = sparkContext.textFile("spark/data/students.txt")
.map(_.split(",") match {
case Array(id: String, name: String, age: String, gender: String, clazz: String) =>
Student(id, name, age.toInt, gender, clazz)
})
/**
* 第二次job作业使用的数据大小
* 未使用序列化进行缓存:238.3 KiB
* 使用是默认的序列化方式:65.4 KiB
* 使用kryo序列化:43.0 KiB
*/
// studentsRDD.cache() // 默认的缓存级别是MEMORY_ONLY
studentsRDD.persist(StorageLevel.MEMORY_ONLY_SER)
/**
* 计算每个班级的人数
*/
val resRDD: RDD[(String, Int)] = studentsRDD.map((stu:Student)=>(stu.clazz,1)).reduceByKey(_ + _)
resRDD.foreach(println)
/**
* 计算每个性别的人数
*/
val resRDD2: RDD[(String, Int)] = studentsRDD.map((stu:Student)=>(stu.gender,1)).reduceByKey(_ + _)
resRDD2.foreach(println)
while (true) {
}
}
}
case class Student(id:String,name:String,age:Int,gender:String,clazz:String)
class Demo8Kryo extends KryoRegistrator{
override def registerClasses(kryo: Kryo): Unit = {
//告诉spark程序,使用kryo序列化,具体是什么要进行kryo序列化
kryo.register(classOf[Student])
}
}
2953

被折叠的 条评论
为什么被折叠?



