目录
一、
1、spark是什么
2、spark四大特性
速度快
易用性
通用性
兼容性
3、简述spark与mapreduce的区别?
基于内存与磁盘
进程与线程
二、
1、rdd的概念
2、rdd的五大属性
3、rdd的创建方式
4、rdd的算子操作分类
1、transformation(转换)
2、action (动作)
5、RDD常见的算子操作说明重点需要掌握
三、
1、RDD的算子操作案例
2、RDD的依赖关系
窄依赖
宽依赖
Lineage(即血统)
3、RDD的缓存机制
1、什么是rdd的缓存机制、好处是什么?
2、如何对rdd设置缓存? cache和persist方法的区别是什么?
3、什么时候设置缓存?
4、如何清除缓存?
四、
1、sparksql简介
2、sparksql特性
3、DataFrame简介
4、DataFrame和RDD对比
RDD
DataFrame
5、DataFrame常用的操作
6、通过IDEA开发程序实现把RDD转换DataFrame
五、
1、sparksql操作hivesql
2、sparksql操作jdbc数据源
3、sparksql中自定义函数
4、sparksql整合hive
spark是针对于大规模数据处理的统一分析引擎,它是基于内存计算框架,计算速度非常之快,但是它仅仅只是涉及到计算,并没有涉及到数据的存储,后期需要使用spark对接外部的数据源,比如hdfs。
job的输出结果可以保存在内存
spark任务以线程的方式运行在进程中
可以快速去编写spark程序通过 java/scala/python/R/SQL等不同语言
一个==生态系统==,包含了很多模块,
sparksql:通过sql去开发spark程序做一些离线分析
sparkStreaming:主要是用来解决公司有实时计算的这种场景
Mlib:它封装了一些机器学习的算法库
Graphx:图计算
spark程序就是一个计算逻辑程序,这个任务要运行就需要计算资源(内存、cpu、磁盘),
哪里可以给当前这个任务提供计算资源,就可以把spark程序提交到哪里去运行
standAlone(后期使用较多)
它是spark自带的独立运行模式,整个任务的资源分配由spark集群的老大Master负责
yarn(后期使用较多)
可以把spark程序提交到yarn中运行,整个任务的资源分配由yarn中的老大ResourceManager负责
mesos
它也是apache开源的一个类似于yarn的资源调度平台
spark处理速度为什么比mapreduce要快
(1)mapreduce任务后期再计算的时候,每一个job的输出结果会落地到磁盘,后续有其他的job需要依赖于前面job的输出结果,这个时候就需要进行大量的磁盘io操作。性能就比较低。
(2)spark任务后期再计算的时候,job的输出结果可以保存在内存中,后续有其他的job需要依赖于前面job的输出结果,这个时候就直接从内存中获取得到,避免了磁盘io操作,性能比较高
对于spark程序和mapreduce程序都会产生shuffle阶段,在shuffle阶段中它们产生的数据都会落地到磁盘。
(1)mapreduce任务以进程的方式运行在yarn集群中,比如程序中有100个MapTask,一个task就需要一个进程,这些task要运行就需要开启100个进程。
(2)spark任务以线程的方式运行在进程中,比如程序中有100个MapTask,后期一个task就对应一个线程,这里就不在是进程,这些task需要运行,
这里可以极端一点:只需要开启1个进程,在这个进程中启动100个线程就可以了。进程中可以启动很多个线程,而开启一个进程与开启一个线程需要的时间和调度代价是不一样。 开启一个进程需要的时间远远大于开启一个线程。
RDD(Resilient Distributed Dataset)叫做弹性 分布式 数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合.
Dataset: 就是一个集合,存储很多数据.
Distributed:它内部的元素进行了分布式存储,方便于后期进行分布式计算.
Resilient: 表示弹性,rdd的数据是可以保存在内存或者是磁盘中.
(1)A list of partitions
一个分区列表,数据集的基本组成单位。
(2)A function for computing each split
一个计算每个分区的函数
(3)A list of dependencies on other RDDs
一个rdd会依赖于其他多个rdd
通过lineage血统记录下rdd与rdd之间的依赖关系
好处
就在于后期某个rdd的部分分区数据丢失的时候,可以通过血统进行重新计算恢复得到
这也是spark任务自身的一个容错机制
(4)Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
(可选项)一个Partitioner,即RDD的分区函数
(5)Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)
一个列表,存储每个Partition的优先位置(可选项)
1、通过已经存在的scala集合去构建
val rdd1=sc.parallelize(List(1,2,3,4,5))
val rdd2=sc.parallelize(Array("hadoop","hive","spark"))
val rdd3=sc.makeRDD(List(1,2,3,4))
2、加载外部的数据源去构建
val rdd1=sc.textFile("/words.txt")
3、从已经存在的rdd进行转换生成一个新的rdd
val rdd2=rdd1.flatMap(_.split(" "))
val rdd3=rdd2.map((_,1))
根据已经存在的rdd转换生成一个新的rdd, 它是延迟加载,它不会立即执行
map / flatMap / reduceByKey 等
它会真正触发任务的运行,将rdd的计算的结果数据返回给Driver端,或者是保存结果数据到外部存储介质中
collect / saveAsTextFile 等
map / mapPartitions foreach / foreachPartition算子区别操作?
map:用于遍历RDD,将函数f应用于每一个元素,返回新的RDD
mapPartitions:用于遍历操作RDD中的每一个分区,返回生成一个新的RDD。
如果在映射的过程中,需要频繁创建额外的对象,使用mapPartitions要比map高效。
比如,将RDD中的所有数据通过JDBC连接写入数据库,如果使用map函数,可能要为每一个元素都创建一个connection,这样开销很大,如果使用mapPartitions,那么只需要针对每一个分区建立一个connection。
foreach: 用于遍历RDD, 将函数f应用于每一个元素,无返回值。
foreachPartition: 用于遍历操作RDD中的每一个分区,无返回值。:
一般使用mapPartitions或者foreachPartition算子比map和foreach更加高效,推荐使用。
coalesce: 合并分区/减少分区 默认不shuffle
默认 coalesce 不能扩大分区数量。除非添加true的参数,或者使用repartition。
repartition: 重新分区, 有shuffle
repartition(numPartitions)其本质是调用了coalesce(numPartitions,true)方法, 第二个参数默认是true,表示会产生shuffle。
适用场景:
1、如果要shuffle,都用 repartition
2、不需要shuffle,仅仅是做分区的合并,coalesce
3、repartition常用于扩大分区。
foreachPartition
//todo:利用spark实现点击流日志分析--TopN(求页面访问次数最多的前N位)
object TopN {
def main(args: Array[String]): Unit = {
//1、构建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("TopN").setMaster("local[2]")
//2、构建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、读取数据文件
val data: RDD[String] = sc.textFile("E:\\data\\access.log")
//4、切分每一行,过滤出丢失的字段数据,获取页面地址
val filterRDD: RDD[String] = data.filter(x=>x.split(" ").length>10)
val urlAndOne: RDD[(String, Int)] = filterRDD.map(x=>x.split(" ")(10)).map((_,1))
//5、相同url出现的1累加
val result: RDD[(String, Int)] = urlAndOne.reduceByKey(_+_)
//6、按照次数降序
val sortedRDD: RDD[(String, Int)] = result.sortBy(_._2,false)
//7、取出url出现次数最多的前5位
val top5: Array[(String, Int)] = sortedRDD.take(5)
top5.foreach(println)
sc.stop()
}
}
object Data2MysqlForeachPartitions {
def main(args: Array[String]): Unit = {
//1、构建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("Data2MysqlForeachPartitions").setMaster("local[2]")
//2、构建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、读取数据文件
val data: RDD[String] = sc.textFile("E:\\data\\person.txt")
//4、切分每一行 // id name age
val personRDD: RDD[(String, String, Int)] = data.map(x => x.split(",")).map(x => (x(0), x(1), x(2).toInt))
//5、把数据保存到mysql表中
//使用foreachPartition每个分区建立一次链接,减少与mysql链接次数
personRDD.foreachPartition( iter =>{
//把数据插入到mysql表操作
//1、获取连接
val connection: Connection = DriverManager.getConnection("jdbc:mysql://node03:3306/spark","root","123456")
//2、定义插入数据的sql语句
val sql="insert into person(id,name,age) values(?,?,?)"
//3、获取PreParedStatement
try {
val ps: PreparedStatement = connection.prepareStatement(sql)
//4、获取数据,给?号 赋值
iter.foreach(line =>{
ps.setString(1, line._1)
ps.setString(2, line._2)
ps.setInt(3, line._3)
//设置批量提交
ps.addBatch()
})
//执行批量提交
ps.executeBatch()
} catch {
case e:Exception => e.printStackTrace()
} finally {
if(connection !=null){
connection.close()
}
}
}
}
}
RDD和它依赖的父RDD的关系有两种不同的类型: 窄依赖(narrow dependency)和宽依赖(wide dependency)
可以把一个rdd的数据缓存起来,后续有其他的job需要用到该rdd的结果数据,可以直接从缓存中获取得到,避免了重复计算。缓存是加快后续对该数据的访问操作。
RDD通过persist方法或cache方法可以将前面的计算结果缓存。
但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
1、自动清除 :一个application应用程序结束之后,对应的缓存数据也就自动清除
2、手动清除 :调用rdd的unpersist方法
Spark SQL is Apache Spark’s module for working with structured data.
SparkSQL是apache Spark用来处理结构化数据的一个模块
大数据技术宏观上进行分类:
(1)数据存储
HDFS HBASE
(2)数据计算
a. 离线计算
MR 、Hive 、RDD(spark-core)、sparksql
b. 实时计算
sparkStreaming 、Flink
将SQL查询与Spark程序无缝混合
可以使用不同的语言进行代码开发(java、scala、python、R)
以相同的方式连接到任何数据源
sparksql兼容hivesql
支持标准的数据库连接JDBC或者ODBC
在Spark中,DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库的二维表格
DataFrame带有Schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型,但底层做了更多的优化
RDD可以把它内部元素看成是一个java对象
DataFrame可以把内部是一个Row对象,它表示一行一行的数据
优点
开发会进行类型检查,在编译的时候及时发现错误
缺点
优点
缺点
/加载数据
val rdd1=sc.textFile("/person.txt").map(x=>x.split(" "))
//定义一个样例类
case class Person(id:String,name:String,age:Int)
//把rdd与样例类进行关联
val personRDD=rdd1.map(x=>Person(x(0),x(1),x(2).toInt))
//把rdd转换成DataFrame
val personDF=personRDD.toDF
//打印schema信息
personDF.printSchema
//展示数据
personDF.show
//查询指定的字段
personDF.select("name").show
personDF.select($"name").show
personDF.select(col("name").show
//实现age+1
personDF.select($"name",$"age",$"age"+1)).show
//实现age大于30过滤
personDF.filter($"age" > 30).show
//按照age分组统计次数
personDF.groupBy("age").count.show
//按照age分组统计次数降序
personDF.groupBy("age").count().sort($"count".desc)show
2、SQL风格语法
//DataFrame注册成表
personDF.createTempView("person")
//使用SparkSession调用sql方法统计查询
spark.sql("select * from person").show
spark.sql("select name from person").show
spark.sql("select name,age from person").show
spark.sql("select * from person where age >30").show
spark.sql("select count(*) from person where age >30").show
spark.sql("select age,count(*) from person group by age").show
spark.sql("select age,count(*) as count from person group by age").show
spark.sql("select * from person order by age desc").show
//1、构建SparkSession对象
val spark: SparkSession = SparkSession.builder().appName("StructTypeSchema").master("local[2]").getOrCreate()
//2、获取sparkContext对象
val sc: SparkContext = spark.sparkContext
sc.setLogLevel("warn")
//3、读取文件数据
val data: RDD[Array[String]] = sc.textFile("E:\\person.txt").map(x=>x.split(" "))
//4、将rdd与Row对象进行关联
val rowRDD: RDD[Row] = data.map(x=>Row(x(0),x(1),x(2).toInt))
//5、指定dataFrame的schema信息
//这里指定的字段个数和类型必须要跟Row对象保持一致
val schema=StructType(
StructField("id",StringType)::
StructField("name",StringType)::
StructField("age",IntegerType)::Nil
)
val dataFrame: DataFrame = spark.createDataFrame(rowRDD,schema)
dataFrame.printSchema()
dataFrame.show()
dataFrame.createTempView("user")
spark.sql("select * from user").show()
spark.stop()
}
def main(args: Array[String]): Unit = {
//1、构建SparkSession对象
val spark: SparkSession = SparkSession.builder()
.appName("HiveSupport")
.master("local[2]")
.enableHiveSupport() //-----开启对hive的支持
.getOrCreate()
//2、直接使用sparkSession去操作hivesql语句
//2.1 创建一张hive表
spark.sql("create table people(id string,name string,age int) row format delimited fields terminated by ','")
//2.2 加载数据到hive表中
spark.sql("load data local inpath './data/kaikeba.txt' into table people ")
//2.3 查询
spark.sql("select * from people").show()
spark.stop()
}
}
//todo:通过sparksql把结果数据写入到mysql表中
object Data2Mysql {
def main(args: Array[String]): Unit = {
//1、创建SparkSession
val spark: SparkSession = SparkSession
.builder()
.appName("Data2Mysql")
.master("local[2]")
.getOrCreate()
//2、读取mysql表中数据
//2.1 定义url连接
val url="jdbc:mysql://node03:3306/spark"
//2.2 定义表名
val table="user"
//2.3 定义属性
val properties=new Properties()
properties.setProperty("user","root")
properties.setProperty("password","123456")
val mysqlDF: DataFrame = spark.read.jdbc(url,table,properties)
//把dataFrame注册成一张表
mysqlDF.createTempView("user")
//通过sparkSession调用sql方法
//需要统计经度和维度出现的人口总数大于1000的记录 保存到mysql表中
val result: DataFrame = spark.sql("select * from user where age > 30")
//保存结果数据到mysql表中
result.write.mode("append").jdbc(url,"kaikeba",properties)
//result.write.mode(args(0)).jdbc(url,args(1),properties
//mode:指定数据的插入模式
//overwrite: 表示覆盖,如果表不存在,事先帮我们创建
//append :表示追加, 如果表不存在,事先帮我们创建
//ignore :表示忽略,如果表事先存在,就不进行任何操作
//error :如果表事先存在就报错(默认选项)
//关闭
spark.stop()
}
}
打包—提交到集群
spark-submit \
--master spark://node01:7077 \
--class com.kaikeba.sql.Data2Mysql \
--executor-memory 1g \
--total-executor-cores 4 \
--driver-class-path /home/hadoop/mysql-connector-java-5.1.38.jar \
--jars /home/hadoop/mysql-connector-java-5.1.38.jar \
spark_class02-1.0-SNAPSHOT.jar \
append kaikeba
def main(args: Array[String]): Unit = {
//1、创建SparkSession
val sparkSession: SparkSession = SparkSession.builder().appName("SparkSQLFunction").master("local[2]").getOrCreate()
//2、构建数据源生成DataFrame
val dataFrame: DataFrame = sparkSession.read.text("E:\\data\\test_udf_data.txt")
//3、注册成表
dataFrame.createTempView("t_udf")
//4、实现自定义的UDF函数
//小写转大写
sparkSession.udf.register("low2Up",new UDF1[String,String]() {
override def call(t1: String): String = {
t1.toUpperCase
}
},StringType)
//大写转小写
sparkSession.udf.register("up2low",(x:String)=>x.toLowerCase)
//4、把数据文件中的单词统一转换成大小写
sparkSession.sql("select value from t_udf").show()
sparkSession.sql("select low2Up(value) from t_udf").show()
sparkSession.sql("select up2low(value) from t_udf").show()
sparkSession.stop()
}
步骤
spark-sql \
--master spark://node01:7077 \
--executor-memory 1g \
--total-executor-cores 4 \
--conf spark.sql.warehouse.dir=hdfs://node01:8020/user/hive/warehouse
#!/bin/sh
#定义sparksql提交脚本的头信息
SUBMITINFO="spark-sql --master spark://node01:7077 --executor-memory 1g --total-executor-cores 4 --conf spark.sql.warehouse.dir=hdfs://node01:8020/user/hive/warehouse"
#定义一个sql语句
SQL="select * from default.hive_source;"
#执行sql语句 类似于 hive -e sql语句
echo "$SUBMITINFO"
echo "$SQL"
$SUBMITINFO -e "$SQL"