spark
一、了解spark
1.离线分析
-
把一段时间的数据经过收集,整理,分析,得出一定的结论
-
这个结论会帮助人们做一些决策
-
不要求时限性
-
对数据的处理方式:批处理
2.实时分析
-
必须要求时效性,数据实时产生,实时处理
-
对数据的处理方式:流处理
3.spark
-
对于大规模数据处理的一个快如闪电的统一的分析引擎(计算框架)
-
作者:美国加州大学伯克利分校AMP(A:算法,M:机器,P:人)实验室
-
通过对算法,机器和人的大规模整合来展现大数据的应用
(2) 特点:
-
速度快:基于内存spark比MR快100X,即使用磁盘快10X
原因:基于内存;DAG有向无环图
-
易用性:
支持scala,java,python,R,SQL语言(spark源码scala)
支持scala,python,R和SQL的shell操作
提供了超过80种的高级操作(算子)
-
可用性:
可以在一个应用程序中无缝整合所有的组件
sparkCore:spark在没有特别指定的前提下,就是sparkCore,是spark的核心计算引擎
sparkSQL:使用SQL处理结构化数据的组件
sparkStreaming:处理实时的流式数据,进行实时分析的组件
伪实时的计算框架,使用的是微批次处理的方式
sparkR:支持R语言的组件
MLib:Machine Learning library 机器学习算法库
GraphX:支持图计算
-
运行在任何地方
可以在任何环境下执行:local(windows/linux) standalone onYarn
可以接收各种数据源
4.RDD
-
弹性的分布式的数据集。是spark的核心。是计算过程中产生的瞬时结果。
-
弹性的:局部发生一些变化但不会对整体产生影响
-
分布式:数据集本身与分布式没有关系
是数据集内部的数据可以存储和计算
-
数据集:数据的集合
(1) 特点:
五个特性:
-
一组分组的集合
-
一个函数作用于所有的切片
-
rdd依赖于其他的rdds
-
可以重新分区
-
数据的本地性,本地数据优先在本地计算
总结特性:
-
是不可变的
-
可以并行计算
-
每个rdd的输入是上一个rdd的输出
-
spark的代码本质都是一个DAG有向无环图
-
rdd是一个抽象的概念
5.容错机制
-
如果某个位置出现错误,首先根据DAG有向无环图查找依赖关系
-
根据依赖关系向父级依赖查找数据,如果父级rdd做了持久化
-
可以直接从父级rdd获取数据,计算父级RDD以下的计算过程
-
如果父级RDD没做持久化,继续向上查找,直到找到有数据的RDD为止
-
最坏的结果就是从HDFS重新读取数据重新计算,
-
如果达到了一定的时间阈值还没有计算出结果,spark会重新启动一个任务
-
原任务不停止,哪个先算出来,用哪个结果
6.算子
官方网站:高级操作
俗称:算子
本质:函数
区别:
普通的集合.调用的就是函数
RDD.调用的就是算子
分类:
转换算子(transformation)
用代码描述整个业务逻辑,但是不提交任务,不执行
可以从sc读取原数据创建出一个RDD或者从已有RDD转成另一个RDD
a.创建算子
从sc读取原数据创建出一个RDD的算子
textFile
wholeTextFiles
parallelize
makeRdd
b.缓存算子
对某个RDD做持久化
cache
persist
checkpoint
行动算子(action)
逻辑描述完毕,提交任务,执行任务,得到结果。
一个任务内只能有一个action算子,不允许重复调用
常见的算子
1.行动算子
collectAsMap:相当于map操作,去掉键相同的键值对
//如果RDD中同一个Key中存在多个Value,那么后面的Value将会把前面的Value覆盖, //最终得到的结果就是Key唯一,而且对应一个Value。
count:计数,返回rdd中的元素数量
countByKey:用来统计RDD[k,v]中每个k的数量
countByValue:统计出集合中每个元素的个数
first:返回RDD中的第一个元素,不排序。
reduce:对集合中的数依次进行相关计算。比如说连加,阶乘
saveAsTextFile:
val list = List(1,3,5,2,4) val rdd = sc.parallelize(list,3) //将数据集的元素,以textfile的形式保存到本地文件系统hdfs或者任何其他hadoop支持的文件系统, //spark将会调用每个元素的toString方法, //并将它转换为文件中的一行文本 rdd.saveAsTextFile("D://test2") //使用java的序列化方法保存到本地文件,可以被sparkContext.objectFile()加载 rdd.saveAsObjectFile("D://test1")
take:用于获取RDD中从0到num-1下标的元素,不排序。
takeSample:有无放回抽样,可控制返回元素个数
详细见:RDD算子之sample、takeSample源码详解_木凡空的博客-CSDN博客_rdd sample
top:用于从RDD中,按照默认(降序)或者指定的排序规则,返回前num个元素。
takeOrdeed:和top类似,只不过和top相反的顺序返回元素
2.转换算子
cartesian:两个RDD进行笛卡尔积合并
coalesce:用于将RDD进行重分区,使用HashPartitioner
且该RDD的分区个数等于numPartitions个数
如果shuffle设置为true,则会进行shuffle
cogroup:对多个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合。
与reduceByKey不同的是针对两个RDD中相同的key的元素进行合并。
distinct:将原始RDD中重复出现的元素进行过滤,返回一个新生成的RDD
filter:对元素进行过滤,对每个元素应用f函数,返回值为true的元素在RDD中保留,返回为false的将过滤掉
flatMap: map的变换操作是对原RDD中的每个元素进行一对一的操作,生成的RDD中元素的数量与原RDD
中元素数量相同,但flatMap可以将每个元素进行一对多的变换操作
groupByKey:将Key/Value型的RDD中的元素按照Key值进行汇聚,Key值相同的Value值会合并为一个序列。
intersection:返回两个RDD的交集
map:将原来RDD的每个数据项通过map中的用户自定义函数f映射转变为一个新的元素
mapValues:同基本转换操作中的map,针对[K,V]中的V值进行map操作。
reduceByKey:针对RDD中相同的key的元素进行合并
sample:
val list = List(1,2,3,4,5,6,7,8,9,10) val rdd = sc.parallelize(list) rdd.sample(true,0.5,System.currentTimeMillis).foreach(println) val rdd1 = sc.parallelize(Range(1,101).toList) //对RDD进行抽样,其中参数withReplacement为true时表示抽样之后还放回,可以被多次抽样, //false表示不放回;fraction表示抽样比例;seed为随机数种子,比如当前时间戳 rdd1.sample(true, 0.1,System.currentTimeMillis()).foreach(println)
sortByKey:通过key进行排序,false为降序,ture为升序
subtract:返回RDD中出现,并且不在otherRDD中出现的元素,不去重。
subtractByKey:返回key在RDD中出现,并且不在otherRDD中出现的元素,不去重。
union:合并两个RDD,不去重
value:获取所有的value
zip:用于将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,
否则会抛出异常。
7.并行度
(1) textFile
可以读取单独的文件数据,也可以读取整个路径下的所有文件数据
把所有文件一一个文件的形式进行处理。
如果指定并行度,直接使用
如果没有指定并行度,走默认的最小分区数
最小分区数 = math.min(默认并行度,2)
默认并行度:
a.如果设置了 spark.default.parallelism 直接读取
b.如果没设置
①本地模式:当前节点的cpu总核心数
②独立模式:适用父类(分布式)的方式
③分布式模式:当前集群中所有节点的所有cpu的核心数与2的最大值
分区策略与hadoop的FileInputFormat一致
(2) wholeTextFiles
可以读取单独的文件的数据,也可以读取整个路径的所有文件数据
每个文件以一个键值对(二元组)的形式保持一致
key为文件的路径
value为文件的内容
分区数量与textFile的形式保持一致
分区策略与hadoop的CombineFileInputFormat一致
(3) parallelize
把已知集合创建成RDD类型
可以指定分区的数量
如果没有指定分区数量,走默认并行度
(4) makeRdd
如果参数与parallelize一样,执行的就是paralleize方法
如果参数是Seq[(T,Seq[String])]这个泛型,这个集合中的每个元素分别为一个独立分区。
8.持久化
-
在java中,把数据写入数据库的过程
-
在spark中,把RDD临时永久地写入到节点地某个位置(磁盘或内存)
永久:避免当前RDD被当作垃圾回收掉,对其永久保存
临时:即使做了持久化,在内存中也不是绝对地安全
a.由于某种特殊情况,会被当作垃圾回收掉
b.没有意外情况下,根据LRU也会被当作垃圾回收掉
-
容错机制
如果持久化的数据丢失了,spark会自动从HDFS上找数据重新计算到这个位置,自动持久化。
-
使用
缓存算子:
cache persist
这两个算子完全等价
相当于使用了默认的的仅用内存的持久化策略
RDD.cache() / persist()
RDD.persist(StorageLevel.XXX)
移除持久化:
a.根据LRU自动取消持久化
b.RDD.unpersist()
-
验证:
a.webUI http://ip:4040 -> storage
b.RDD.getStorageLevel 以StorageLevel对象的类型返回
RDD.getStorageLevel.describe 以字符串类型返回
策略
(1) MEMORY_ONLY 与 MEMORY_AND_DISK对比
MEMORY_ONLY:先可内存存储,存不下就不存了。读取数据的时候先读内存,没有持久化的部分从HDFS重
新计算
MEMORY_AND_DISK:先可内存存储,如果存不下,就存磁盘
总结:
作者认为,即使从hdfs重新计算也比多做一次磁盘IO速度快
使用场景:
-
如果没有特殊情况,直接使用默认的存储等级
这种情况可以使用CPU发挥最大的性能
-
如果内存不太充足,可以选择SER,节省一定的空间
由于还是内存计算,速度也会快,但是读取的时候需要反序列化
-
除非计算很复杂不要使用DISK
重新计算的耗时与做磁盘IO差不多甚至更多,可以选择此方式
-
如果想做快速恢复可以使用带副本的形式
虽然可以自动容错,但是重新计算需要时间
checkPoint()
-
把当前RDD可以标记成一个检查点
-
把当前RDD的最终状态保存到一个目录中,目录由sc.setCheckPointDir设置
-
会移除当前RDD的所有父级依赖的引用,它成为了顶级依赖
-
这是个转换算子,必须在行动算子之前调用
-
强烈建议在检查点之前先设置persist,避免重复计算
9.宽窄依赖
(1) 窄依赖(父到子一对一)narrowDependency:
父级RDD里的每一个partition对应子级RDD里的唯一一个partition的关系
(2) 宽依赖(父到子一对多)shuffleDependency:
父级RDD里的每一个partition对应子级RDD里的多个partition的关系,一定要执行shuffle操作
10.shuffle
详细文档:Spark中的Spark Shuffle详解 - 大葱拌豆腐 - 博客园
(1) HashShuffle(1.2版本之前默认使用)
普通机制:产生的文件数量是 M*R,小文件太多
合并机制:产生的文件数量是 Core*R,比普通机制少,但是也是很多
(2) SortShuffle(1.2版本之后默认使用)
普通机制:每个task产生一个磁盘文件
由于数据都在里面,另有一个索引文件。
需要排序
byPass机制:如果task数量小于等于阈值(默认200),使用此机制
不需排序,节省了性能
二、sparkSQL
-
处理结构化数据的组件
-
计算过程是DataSet与DataFrame之间的转换(与RDD类似)
-
DS/DF可以创建出来,也可以由其他DS/DF转换而来
-
DS:数据集合
-
DF:数据框架(比DS多了schema)
1.使用
(1) 创建sparkSession
val spark:SparkSession = SparkSession.builder()
.master(xxx)
.appName(xxx)
.config(key,value)
......
.getOrCreate()
(2) 读写数据
读:
//没有格式化的情况,默认使用parquet格式
spark.read.format(type).load(path)
//读数据的简化形式
spark.read.type(path)
写:
//没有格式化的情况,默认使用parquet格式
df.write. format(type).save(path)
//写数据的简化情况
df.write.type(path)
(3)保存模式
a.ErrorIfExists 默认情况,如果存在就报错
b.Append 追加 向目标目录中添加一个新文件
c.Overwrite 覆盖
d.Ignore 忽略,如果存在不报错
两种实现方式
a.df.write.mode("")
b.df.write.mode(SaveMode.xxx)
(4) 数据分析
a.DataSetAPI
DataSet可以通过调用函数的形式对数据进行分析
只要sql中可以支持的操作,API都有相应的与之对应
API可以进行链式调用(多个条件)
可以使用$“字段”获取相应内容,前提必须引入隐式转换
b.SQL
把所有的操作都交给sql完成
sql语法不与任何一种sql语法完全一样
四种创建临时视图的方式:
1) createTempView 当前session有效,存在报错
2) createOrReplaceTempView 当前session有效,存在替换
3) createGlobalTempView 所有session有效,存在报错
4) createOrReplaceGlobalTempView 所有session有效,存在替换
创建持久化表
相当于写文件与创作表的整合
df.write.option("path","xxx").saveAsTable(tableName)
可以直接读取文件数据
spark.sql("select * from type.‘path’)
(5)几种转换关系
①普通集合转DS/DF
语法:
集合.toDS()
集合.toDF()
集合.toDF(列名*)
注意:
使用任何一种形式都必须先引入隐士转换
toDS和toDF无参两种情况结果一样但是类型不一样
如果需要表头,必须使用toDF(xxx),列名的个数和顺序必须与集合中的数据一致
如果想创建一个多列的数据,需要使用元组的形式
②RDD转成DS/DF
-
语法与普通集合转DS/DF一致
-
RDD比普通集合更灵活一些,可以把每条数据转换成元组或类的实例化对象
-
通过反射机制推断出schema(表名和类型)
③RDD转成DS/DF,人为设计schema
可以由开发人员自己控制每个字段的各种属性
三个步骤:
1)创建原始RDD,并转成row类型
2)设计每个字段,形成schema
3)最后进行合并,创建DF
④DF/DS转成RDD
DS/D.rdd
RDD和DF/DS之间可以随意转换,无缝整合
注意:DF/DS转成rdd之后是ROW类型的数据
(6) 读取hive仓库的数据
-
sparkSQL可以读取hive仓库的数据
-
如果使用hive,必须在sparkSession中添加hive的支持
-
语法与hive语法一致
-
warehouse和元数据库的三种形式:
没有任何设置,在当前项目路径下创建derby元数据库和数据仓库
可以指定数据仓库位置
可以通过hive-site.xml改变元数据库
spark on hive 和 hive on spark 的区别:
spark on hive :
hive只作为存储角色,spark 负责sql解析优化,底层运行的还是sparkRDD
具体可以理解为spark通过sparkSQL使用hive语句操作hive表,底层运行的还是sparkRDD,步骤如下:
1.通过sparkSQL,加载Hive的配置文件,获取Hive的元数据信息
2.获取到Hive的元数据信息之后可以拿到Hive表的数据
3.通过sparkSQL来操作Hive表中的数据
hive on spark:
hive既作为存储又负责sql的解析优化,spark负责执行
这里Hive的执行引擎变成了spark,不再是MR。
这个实现较为麻烦,必须重新编译spark并导入相关jar包
目前大部分使用spark on hive
(7) jdbc
(1) 读:
普通方式:
spark.read.option("url").option("dbtable").option("user").option("password").load()
简便方式:
spark.read.jdbc(url,dbtable,prop)
(2) 写:
普通方式:
spark.write.option("url").option("dbtable").option("user").option("password").save()
简便写法:
spark.write.jdbc(url,dbtable,prop)
注意:
-
写数据得时候可以使用savemode
-
简便写法的properties可以共用,比较简单
-
不管是读还是写,都需要指定到表级别
-
关于写入数据的几种情况
如果新数据的字段是原数据字段的子集,可以正常追加
如果新数据的字段不是原数据字段的子集,不能追加
任何情况,都可以以覆盖的形式写入(重新建表)
(8)分区,分桶,排序
分区:以目录级别进行划分
分桶:以文件级别进行划分
分桶和排序必须使用saveAsTable,分区可以使用save和savaAsTable
(9)scheme合并
在parquet文件中,可以先定义少部分schema,
根据实际需求,可以添加更多的schema
但是需要开启 mergeSchema -> true
三、sparkStreaming
-
处理实时流数据的组件
-
伪实时的计算框架,使用的是微批次处理方式
1.数据处理的过程
a.获取实时的流失数据(源源不断产生的数据源)
b.接收到的实时数据实际上是按照时间间隔为一个批次进行切割
每个微批次的数据本质就是一个RDD
c.由sparkCore引擎执行每个RDD的内容
d.把最终的结果数据写入到某个位置
2.DStream
-
一个离散化的数据流,是一个高级抽象,是RDD的集合
-
可以由原数据创建而来,也可以通过算子转换成另一个DStream
-
DStream之间的转换实际上就是里面的每个RDD之间的转换
3.三个组件的对比
组件名 | 操作对象 | 中间过程 |
---|---|---|
sparkCore | sparkContext | RDD |
sparkSQL | sparkSession | DF/DS |
sparkStream | streamingContext | Dstream |
4.编码过程
①创建ssc(可以通过conf,也可以通过sc)
②读取原数据,创建Dstream
③使用transformation算子和output算子转换和输出DStream
④ssc.start() 开启接受数据和处理数据
⑤ssc.awaitTermination() 等待程序的终止(手动关闭或程序错误)
⑥实时处理一般不关闭服务,如果需要关闭,可以使用 ssc.stop()
注意:
-
同一时间内一个JVM只能运行一个ssc,且一旦关闭,不会自动重启
-
如果关闭了ssc.sc也一起被关闭
如果不想关闭sc,可以通过stop(stopSparkContext = false)
-
sparkStreaming需要至少两个core来处理业务(一个接收数据,其余处理数据)
5.算子
(1) transformation算子
transform
①对数据中的每个RDD进行处理,RDD不需要调用action算子
②直接以RDD类型返回。最终还是以DStream形式接收
③与map算子的区别
map也是DStream级别调用,但是函数是以数据级别进行操作
updateStateByKey
状态:到某个时间点为止的综合所有数据的最终状态
有状态:计数,求和,统计。。。。
无状态:来了就走,跟其他批次数据没有任何关系。
window
对一定时间范围的数据进行整合处理
两个参数:
①窗口长度:以时间为长度,是时间间隔的整数倍
②滑动间隔:以时间为长度,是时间间隔的整数倍
控制每隔多长时间读取一次窗口的数据,允许有数据的交叉
(2)join
output算子
foreachRDD:
对每个RDD级别做处理,是output算子,没有返回值
与transform算子做对比:
也是对每个RDD级别做处理,是transformation算子,有返回值
在内部的RDD以action算子输出结果
DataFrame&SQL算子
-
可以读取实时数据,然后对RDD级别进行操作
-
可以把RDD转换成DS/DF执行执行sparkSQL操作
-
实现了各组件之间的无缝整合