目录
2、如何对rdd设置缓存? cache和persist方法的区别是什么?
一、
1、spark是什么
spark是针对于大规模数据处理的统一分析引擎,它是基于内存计算框架,计算速度非常之快,但是它仅仅只是涉及到计算,并没有涉及到数据的存储,后期需要使用spark对接外部的数据源,比如hdfs。
2、spark四大特性
-
速度快
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的资源调度平台
3、简述spark与mapreduce的区别?
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个线程就可以了。进程中可以启动很多个线程,而开启一个进程与开启一个线程需要的时间和调度代价是不一样。 开启一个进程需要的时间远远大于开启一个线程。
二、
1、rdd的概念
RDD(Resilient Distributed Dataset)叫做弹性 分布式 数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合.
Dataset: 就是一个集合,存储很多数据.
Distributed:它内部的元素进行了分布式存储,方便于后期进行分布式计算.
Resilient: 表示弹性,rdd的数据是可以保存在内存或者是磁盘中.
2、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的优先位置(可选项)
3、rdd的创建方式
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))
4、rdd的算子操作分类
1、transformation(转换)
根据已经存在的rdd转换生成一个新的rdd, 它是延迟加载,它不会立即执行
map / flatMap / reduceByKey 等
2、action (动作)
它会真正触发任务的运行,将rdd的计算的结果数据返回给Driver端,或者是保存结果数据到外部存储介质中
collect / saveAsTextFile 等
5、RDD常见的算子操作说明
重点需要掌握
map / mapPartitions foreach / foreachPartition算子区别操作?
- 1) map / mapPartitions (transformation算子)
map:用于遍历RDD,将函数f应用于每一个元素,返回新的RDD
mapPartitions:用于遍历操作RDD中的每一个分区,返回生成一个新的RDD。
如果在映射的过程中,需要频繁创建额外的对象,使用mapPartitions要比map高效。
比如,将RDD中的所有数据通过JDBC连接写入数据库,如果使用map函数,可能要为每一个元素都创建一个connection,这样开销很大,如果使用mapPartitions,那么只需要针对每一个分区建立一个connection。
- 2) foreach / foreachPartition (action算子)
foreach: 用于遍历RDD, 将函数f应用于每一个元素,无返回值。
foreachPartition: 用于遍历操作RDD中的每一个分区,无返回值。:
一般使用mapPartitions或者foreachPartition算子比map和foreach更加高效,推荐使用。
- 3) coalesce/ repartition 算子
coalesce: 合并分区/减少分区 默认不shuffle
默认 coalesce 不能扩大分区数量。除非添加true的参数,或者使用repartition。
repartition: 重新分区, 有shuffle
repartition(numPartitions)其本质是调用了coalesce(numPartitions,true)方法, 第二个参数默认是true,表示会产生shuffle。
适用场景:
1、如果要shuffle,都用 repartition
2、不需要shuffle,仅仅是做分区的合并,coalesce
3、repartition常用于扩大分区。
三、
1、RDD的算子操作案例
- 重点掌握rdd常见的一些算子操作
- flatMap
- map
- reduceByKey
- sortBy
- distinct
- count
- mapPartitions
- foreach
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()
}
}
}
}
}
2、RDD的依赖关系
RDD和它依赖的父RDD的关系有两种不同的类型: 窄依赖(narrow dependency)和宽依赖(wide dependency)
窄依赖
- 窄依赖指的是每一个父RDD的Partition, 最多被子RDD的一个Partition使用,
- 所有的窄依赖不会产生shuffle: map/flatMap/filter/union等
- 总结:窄依赖我们形象的比喻为独生子女
宽依赖
- 宽依赖指的是多个子RDD的Partition, 会依赖同一个父RDD的Partition,
- 所有的宽依赖会产生shuffle: reduceByKey/sortByKey/groupBy/groupByKey/join等等
- 总结:宽依赖我们形象的比喻为超生
- join分为宽依赖和窄依赖,如果RDD有相同的partitioner,那么将不会引起shuffle,这种join是窄依赖,反之就是宽依赖
Lineage(即血统)
- RDD只支持粗粒度转换, 即只记录单个块上执行的单个操作。
- 将创建RDD的一系列Lineage(即血统)记录下来,以便恢复丢失的分区
- RDD的Lineage会记录RDD的元数据信息和转换行为,lineage保存了RDD的依赖关系,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。
3、RDD的缓存机制
1、什么是rdd的缓存机制、好处是什么?
可以把一个rdd的数据缓存起来,后续有其他的job需要用到该rdd的结果数据,可以直接从缓存中获取得到,避免了重复计算。缓存是加快后续对该数据的访问操作。
2、如何对rdd设置缓存? cache和persist方法的区别是什么?
RDD通过persist方法或cache方法可以将前面的计算结果缓存。
但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
3、什么时候设置缓存?
- 1、某个rdd的数据后期被使用了多次
- 公共rdd进行持久化,避免后续需要,再次重新计算,提升效率。
- 2、rdd的数据来之不易时
- 为了获取得到一个rdd的结果数据,经过了大量的算子操作或者是计算逻辑比较复杂
4、如何清除缓存?
1、自动清除 :一个application应用程序结束之后,对应的缓存数据也就自动清除
2、手动清除 :调用rdd的unpersist方法
四、
1、sparksql简介
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
2、sparksql特性
- 1、易整合
将SQL查询与Spark程序无缝混合
可以使用不同的语言进行代码开发(java、scala、python、R)
- 2、统一的数据源访问
以相同的方式连接到任何数据源
- 3、兼容hive
sparksql兼容hivesql
- 4、标准的数据库连接
支持标准的数据库连接JDBC或者ODBC
3、DataFrame简介
在Spark中,DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库的二维表格
DataFrame带有Schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型,但底层做了更多的优化
4、DataFrame和RDD对比
RDD可以把它内部元素看成是一个java对象
DataFrame可以把内部是一个Row对象,它表示一行一行的数据
RDD
优点
- 1、编译时类型安全
开发会进行类型检查,在编译的时候及时发现错误
- 2、具有面向对象编程的风格
缺点
- 1、构建大量的java对象,占用了大量heap堆空间,导致频繁的GC
- 2、数据的序列化和反序列性能开销很大
DataFrame
- DataFrame引入了schema元信息和off-heap(堆外内存)
优点
- DataFrame引入了schema元信息,解决了rdd数据的序列化和反序列性能开销很大这个缺点。
- DataFrame引入了off-heap,解决了rdd构建大量的java对象 占用了大量heap堆空间,导致频繁的GC这个缺点。
缺点
- 1、编译时类型不安全
- 2、不在具有面向对象编程的风格
5、DataFrame常用的操作
- 1、DSL风格语法 : spark自身提供了一套Api
/加载数据
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注册成一张表,通过sparkSession.sql(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
6、通过IDEA开发程序实现把RDD转换DataFrame
- 1、利用反射机制
- 事先可以确定DataFrame的schema信息
- 定义一个样例类,样例类中的属性,通过反射之后生成DataFrame的schema信息
- 2、通过StructType动态指定schema信息
- 事先不确定DataFrame的schema信息,在开发代码的过程中动态指定
- 其本质调用底层方法
//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()
}
五、
1、sparksql操作hivesql
- 主要是理解sparksql的四大特性中的
- 第三点 sparksql兼容hivesql
- .enableHiveSupport() //-----开启对hive的支持
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() } }
2、sparksql操作jdbc数据源
- 1、sparksql通过 JDBC加载mysql表的数据
- 2、sparksql处理完成的数据,保存到mysql表中
//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
3、sparksql中自定义函数
- 自定义udf函数
- 核心代码
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() }
4、sparksql整合hive
- .enableHiveSupport() //-----开启对hive的支持
- 第三点 sparksql兼容hivesql
步骤
- 1、需要把hive安装目录下的配置文件hive-site.xml, 拷贝到每一个spark安装目录下对应的conf文件夹中
- 2、需要一个连接mysql驱动的jar包,拷贝到spark安装目录下对应的jars文件夹中
- 3、可以使用spark-sql脚本 后期执行sql相关的任务
- 使用方式
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"