spark学习

1、RDD概述
1、RDD是什么?
RDD是弹性分布式数据集,表示的是弹性、可分区、里面的元素可并行计算、不可变的集合
弹性:
1、存储的弹性: RDD中间结果数据会保存在内存中,如果内存不足会自动溢写磁盘
2、容错的弹性: 如果RDD数据丢失可以根据依赖关系重新计算得到数据
3、计算的弹性: 如果计算出错会重试
4、分区的弹性: 读取HDFS文件的时候,分区是根据文件的切片来分
不可变: RDD中封装的是数据处理逻辑,如果想要重新改变数据需要创建新的RDD
不存储数据: RDD中不存数据
可并行计算: RDD多个分区之间的处理逻辑是一样的,不一样只是处理的数据,分区与分区之间的计算是并行
RDD是惰性计算的,只有在调用collect这类算子的时候才会触发任务的计算
2、RDD的五大特性
1、一组分区列表: RDD在读取HDFS文件的时候,会一个分区对应一个切片
2、作用在每个分区上的计算函数: RDD多个分区之间的处理逻辑是一样的,不一样只是处理的数据
3、依赖关系: RDD不存储数据,如果计算出错需要根据RDD的依赖关系重新计算得到数据
4、分区器[可选的]: 在shuffle的时候通过分区器决定数据放入RDD哪个分区中,要求RDD中元素类型必须是KV键值对
5、优先位置[可选的]: Spark在分配task的时候会优先将TASK分配到数据所在的位置,避免网络拉取数据影响效率
stage的个数 = shuffle个数+1
一个stage中task的个数 = 该stage最后一个RDD的分区数
2、RDD的编程
1、RDD的创建
1、根据集合创建RDD
1、 sc.makeRDD(集合)
2、 sc.parallelize(集合)
2、读取文件创建RDD: sc.textFile(path)
1、如果spark中有配置HADOOP_CONF_DIR
此时默认读取的是HDFS文件
1、读取HDFS
sc.textFile(“/…/…/…”)
sc.textFile(“hdfs:///…/…”)
sc.textFile(hdfs://hadoop102:8020/…/…“”)
2、读取本地文件
sc.textFile(“file:///…/…”)
2、如果spark中没有配置HADOOP_CONF_DIR
此时默认读取的是本地文件
1、读取HDFS文件: sc.textFile(hdfs://hadoop102:8020/…/…“”)
2、读取本地文件
sc.textFile(“/…/…/…”)
sc.textFile(“file:///…/…”)
3、根据其他RDD衍生: val rdd2 = rdd1.map(…)/flatMap()/…
2、RDD的分区数
1、根据集合创建RDD: sc.parallelize(集合[,numSlices=defaultParallelism])
1、如果有指定numSlices的值,此时分区数 = numSlices
2、如果没有指定numSlices的值,此时分区数 = defaultParallelism
defaultParallelism的值为:
1、如果有在SparkConf中配置spark.default.parallelism参数, defaultParallelism = spark.default.parallelism参数值
2、如果没有在SparkConf中配置spark.default.parallelism参数
1、master=local, defaultParallelism=1
2、master=local[N], defaultParallelism=N
3、master=local[*], defaultParallelism = cpu个数
4、master=spark://…/… ,defaultParallelism = Math.max( 所有executor总核数,2 )
2、根据读取文件创建RDD: sc.textFile(path[,minPartitions=defaultMinPartitions])
读取文件创建RDD的分区数 >= minPartitions
minPartitions的默认值为defaultMinPartitions, defaultMinPartitions=Math.min( defaultParallelism , 2 )
读取文件创建RDD的分区数最终由文件切片决定,有多少切片就有多少分区
3、根据其他RDD衍生: val rdd2 = rdd1.map(…)/flatMap()/…
RDD分区数 = 依赖的第一个RDD的分区数
3、spark算子
Spark算子分为两大类: transformation算子、action算子
transformation算子: 只是封装数据的处理逻辑,不会触发任务的计算,都会生成新的RDD
map(func: RDD元素类型=> B ): 映射[元素转换成元素] *********
map里面的函数是针对每个元素操作,RDD元素有多少个,函数就会执行多少次
map的应用场景: 一般用于数据类型/值的转换[一对一]
mapPartitions(func: Iterator[RDD元素类型]=>Iterator[B]) : 映射[分区映射分区] *********
mapPartitions里面的函数是针对每个分区操作,分区有多少个,函数就调用多少次
mapPartitions的应用场景: 一般用于数据转换[比如读取mysql数据转成成详情数据]
mapPartitions与map的区别: *********
1、函数针对的对象不一样
map里面的函数是针对每个元素操作,RDD元素有多少个,函数就会执行多少次
mapPartitions里面的函数是针对每个分区操作,分区有多少个,函数就调用多少次
2、函数的返回值不一样
map的函数是一个元素生成一个结果,所以map生成的新RDD元素个数 = 原RDD元素个数
mapPartitions的函数是将原RDD分区数据的迭代器转成新的迭代器,所以mapPartitions生成的新RDD元素个数不一定原RDD元素个数
3、数据垃圾回收的时机不一样
map里面函数操作完元素之后就可以进行垃圾回收
mapPartitions里面函数必须操作完整个迭代器之后才能进行垃圾回收,所以如果分区中数据特别多,可能出现内存溢出,此时可以使用map解决
mapPartitionsWithIndex(func: (Int,Iterator[RDD元素类型])=>Iterator[B]): 映射[分区映射分区] *********
mapPartitionsWithIndex里面的函数是针对每个分区操作,分区有多少个,函数就调用多少次
mapPartitionsWithIndex与mapPartitions的区别:
mapPartitionsWithIndex里面的函数相比于mapPartitions多了参数,该参数是一个分区号
flatMap( func: RDD元素类型=> 集合 ) = map + flatten *********
flatMap里面的函数是针对每个元素操作,RDD元素有多少个,函数就会执行多少次
flatMap的应用场景: 一对多
glom: 将分区数据封装成数组
glom生成的新RDD元素个数 = 分区个数, 元素类型就是Array
groupBy(func: RDD元素类型 => K ): 按照指定字段分组 *********
groupBy后续是按照函数的返回值进行分组
groupBy生成的新RDD的元素类型是KV键值对,K是函数返回值,V是K对应原RDD中的所有元素
groupBy会产生shuffle操作
filter(func: RDD元素类型=>Boolean ): 按照指定条件过滤 *********
filter里面的函数是针对每个元素操作,RDD元素有多少个,函数就会执行多少次
filter保留的是函数返回值为true的数据
sample(withReplement,fraction): 抽样 *********
withReplement: 元素是否可以被多次采样[true-代表同一个元素可能被采样多次, false-代表同一个元素最多被采样一次]
fraction
withReplement=true,fraction代表元素期望被采样的次数
withReplement=false,fraction代表每个元素被采样的概率
sample一般用于数据倾斜场景,产生数据倾斜之后通过采样,根据采样的小样本数据映射整个数据集的情况.
工作中withRelement一般设置为false,fraction一般设置为0.1-0.2
distinct: 去重 【会产生shuffle操作】 *********
coalesce(分区数[,shuffle=false]): 合并分区 *********
coalesce默认只能减少分区,此时没有shuffle操作
coalesce如果想要增大分区,需要设置shuffle=true,此时会产生shuffle操作
repartition(分区数): 重分区 *********
repartition默认既可以增大分区也可以减少分区
repartition底层就是coalesce(分区数,shuffle=true)
repartition与coalesce的区别: *********
coalesce默认只能减少分区,此时没有shuffle操作,coalesce如果想要增大分区,需要设置shuffle=true,此时会产生shuffle操作
repartition默认既可以增大分区也可以减少分区,都会产生shuffle操作
repartition与coalesce的应用场景:
如果想要减少分区,推荐使用coalesce,因为不会产生shuffle操作
如果想要增大分区,推荐使用repartition,因为使用简单
sortBy(func: RDD元素类型=>K[,ascding=true]): 根据指定字段排序 *********
sortBy后续是按照函数的返回值进行排序
ascding=true是升序,ascding=fasle是降序
sortBy会产生shuffle操作
pipe(path): 调用外部脚本
pipe会生成新的RDD,新RDD的元素是通过在脚本中echo返回的
pipe调用脚本是每个分区调用一次
intersection: 取两个RDD的交集元素,会产生shuffle操作
union: 取两个RDD的并集,不会产生shuffle操作.
union生成的新RDD的分区数 = 两个父RDD的分区数之和
subtract: 取两个RDD的差集,会产生shuffle操作
partitionBy(分区器):按照指定的分区器进行重分区
自定义分区器:
1、定义一个class继承Partitioner
2、重写抽象方法
reduceByKey(func: (Value值类型,Value值类型)=>Value值类型): 按照key分组之后,对每个key所有的value值聚合 *********
groupByKey: 按照key分组, groupByKey生成的RDD里面是KV类型,K就是原来的key,value是原来key对应的所有的value值
reduceByKey与groupByKey的区别:
groupByKey没有combiner预聚合
reduceByKey有combiner预聚合,性能上比groupByKey更高
combineByKey(createCombine: Value值类型=>B, mergeValue: ( B,Value值类型 )=>B, mergeCombine: (B,B)=>B ): 按照key分组之后,对每个key所有的value值聚合
createCombine: 是在combiner阶段对每个组的第一个value值进行转换
mergeValue: 是combiner聚合逻辑
mergeCombine: 是reduce计算逻辑[汇总所有分区combiner的结果]
foldByKey(默认值)(func: (Value值类型,Value值类型)=>Value值类型 ): 按照key分组之后,对每个key所有的value值聚合
aggregateByKey(默认值)(seqop: ( 默认值类型,Value值类型 )=> 默认值类型, comop: (默认值类型,默认值类型)=>默认值类型): 按照key分组之后,对每个key所有的value值聚合
seqop: 是combiner聚合逻辑
comop: 是reduce计算逻辑[汇总所有分区combiner的结果]
foldByKey、reduceByKey、combineByKey、aggregateByKey的区别:
reduceByKey: combiner与reducer计算逻辑是一样的,在combiner阶段对每个组第一次计算的时候,函数的第一个参数的初始值 = 每个组第一个元素
foldByKey: combiner与reducer计算逻辑是一样的,在combiner阶段对每个组第一次计算的时候,函数的第一个参数的初始值 = 默认值
combineByKey: combiner与reducer计算逻辑可以不一样,在combiner阶段对每个组第一次计算的时候,函数的第一个参数的初始值 = 第一个函数的转换值
aggregateByKey: combiner与reducer计算逻辑可以不一样,在combiner阶段对每个组第一次计算的时候,函数的第一个参数的初始值 = 默认值
sortByKey(ascding=true):按照key排序【ascding=true为升序,ascding=false为降序】
mapValues(func: Value值类型 => B): 针对每个元素的vlaue值进行映射
join: 只有两个RDD的key相同才能Join上,生成的新RDD[(K,V)] 【K就是join的key,V是(左RDD的value值,右RDD的value值)】
leftOuterJoin: 只有两个RDD的key相同才能Join上 + 左RDD不能join的数据 ,生成的新RDD[(K,V)] 【K就是join的key,V是(左RDD的value值,Option[右RDD的value值])】
rightOuterJoin: 只有两个RDD的key相同才能Join上 + 右RDD不能join的数据 ,生成的新RDD[(K,V)] 【K就是join的key,V是(Option[左RDD的value值],右RDD的value值)】
fullOuterJoin: 只有两个RDD的key相同才能Join上 + 右RDD不能join的数据 + 左RDD不能join的数据 ,生成的新RDD[(K,V)] 【K就是join的key,V是(Option[左RDD的value值],Option[右RDD的value值])】
cogroup = groupByKey+ fullOuterJoin
cogroup生成的新RDD的元素是KV,K就是原RDD元素的key,V也是二元元组,二元元组第一个值 = key在左RDD中对应的所有的value值,二元元组第二个值 = key在右RDD中对应的所有的value值,
action算子: 触发任务计算,最终会得到结果不会生成RDD
reduce(func: (RDD元素类型,RDD元素类型)=>RDD元素类型) : 对RDD所有元素聚合
reduce不会产生shuffle操作
reduce是先对每个分区所有元素进行聚合,然后将每个分区的聚合结果发给Driver再次汇总
collect: 收集RDD每个分区的数据然后用数组封装之后发给Driver
如果RDD分区数据比较大,Driver默认内存只有1G,所以可能出现内存溢出。
所以在工作中一般设置Driver内存为5-10G
可以通过bin/spark-submit --driver-memory 10G 设置Driver内存
count: 统计RDD元素个数
first: 获取RDD第一个元素
take: 获取RDD前N个元素
takeOrdered: 获取RDD排序之后的前N个元素
fold(默认值)(func: (RDD元素类型,RDD元素类型)=>RDD元素类型): 对RDD所有元素聚合
fold是先对每个分区所有元素进行聚合,然后将每个分区的聚合结果发给Driver再次汇总
fold与reduce的区别:
reduce在对每个分区的数据/Driver汇总过程中第一次聚合的时候,函数的第一个参数的初始值 = 第一个元素
fold在对每个分区的数据/Driver汇总过程第一次聚合的时候,函数的第一个参数的初始值 = 默认值
aggregate(默认值)(seqop: (默认值类型,RDD元素类型)=> 默认值类型,comop:(默认值类型,默认值类型)=>默认值类型): 对RDD所有元素聚合
seqop:在每个分区中的聚合逻辑
comop: Driver汇总逻辑
aggregate与fold的区别:
fold在每个分区中聚合逻辑与Driver汇总逻辑是一样的
aggregate在每个分区中聚合逻辑与Driver汇总逻辑可以不一样
countByKey: 统计RDD中每个key出现的次数
countByKey一般结合sample一起使用.
后续出现数据倾斜之后,先使用sample抽样数据,在使用countByKey统计样本数据中每个key的次数,从而确定哪个key出现了数据倾斜
save: 保存数据到文件
foreach(func: RDD元素类型=>Unit):Unit : 遍历[针对每个元素遍历]
foreach是针对每个元素操作,元素有多少个,函数就调用多少次
foreachPartition(func: Iterator[RDD元素类型]=>Unit): 遍历[针对分区遍历]
foreach与foreachPartition的区别:
foreach里面的函数是针对每个元素操作,元素有多少个,函数就调用多少次
foreachPartition里面的函数是针对每个分区,RDD有多少分区,函数就调用多少次
foreachPartition一般用于保存数据到mysql/hbase等位置,因为此时使用foreachPartition可以减少链接创建次数
4、RDD序列化
原因: Spark算子里面的代码是在Executor中执行,算子外面的代码是在Driver中执行,所以如果算子里面使用了算子外面的对象,那么此时会将该对象序列化之后才能传入Executor中
Spark序列化方式有两种: Java序列化,Kryo序列化
spark默认采用java序列化,工作中建议使用Kryo序列化,因为Kryo序列化性能要比java序列化要高10倍左右
如何配置kryo序列化
1、在sparkconf中配置: new SparkCOnf().set(“spark.serializer”,“org.apache.spark.serializer.KryoSerializer”)
2、注册待使用kryo序列化的类[可选]: new SparkConf().registerKryoClasses(Array(类的class形式,…))
5、RDD依赖关系
1、查看血统关系[指RDD以及父RDD还有祖宗RDD的链条关系]: rdd.toDebugString
2、查看依赖[是指父子RDD的关系]: rdd.dependencys
RDD的依赖关系分为两大类: 宽依赖、窄依赖
宽依赖: 有shuffle的称之为宽依赖
窄依赖: 没有shuffle的称之为窄依赖
3、stage切分: 根据最后一个RDD的依赖关系从后向前依次查找,找到宽依赖则切分,一直找到第一个RDD为止
4、stage的执行: 一个Job中stage的执行是从前向后执行,先执行前面的stage,在执行后面的stage
5、名词
Application: 应用[一个SparkContext称之为一个Application]
Job: 任务[一个action算子产生一个Job]
Stage: 阶段 [一个job中stage的个数 = shuffle个数+1]
Task: 执行的任务[ 一个stage中task的个数 = stage最后一个RDD的分区数 ]
一个job中stage的执行是串行
一个stage中task的执行是并行
6、RDD持久化
原因: 如果一个RDD在多个Job中重复使用的时候,每次job执行的时候,该RDD的数据都会重复计算,导致程序运行的效率比较低.
使用场景:
1、一个RDD在多个Job中重复使用的时候,此时可以将该RDD的数据持久化,等到第一个job执行完成之后,RDD的数据就已经保存了,后续其他job执行的时候就不会重复计算,直接获取持久化数据使用。
2、当job的RDD的依赖链条比较长的时候,此时可以将RDD的数据持久化,避免计算出错的时候,需要重新计算浪费大量时间。
RDD持久化分为两种: 缓存、checkpoint
缓存:
数据保存位置: task所在服务的内存/本地磁盘中
使用缓存: rdd.cache/rdd.persist(StorageLevel.XXX)
cache与persist的区别:
cache是将数据只保存在内存中
persist可以指定将数据保存在内存/磁盘中
checkpoint:
原因: 缓存是将数据保存在task所在服务的内存/本地磁盘中,所以如果服务器宕机,数据丢失之后需要重新计算,所以最好将数据保存在可靠性高的存储介质中
数据保存位置: HDFS
使用checkpoint:
1、设置数据持久化路径: sc.setCheckpointDir(…)
2、持久化数据: rdd.checkpoint
checkpoint类似一个action算子,在第一个Job执行完成之后会检查job中是否有RDD进行checkpoint操作,如果有则再触发一个job执行,进行数据的持久化。
所以使用checkpoint的时候会额外产生一个Job执行,所以该进行checkpoint操作的RDD的计算会重复执行两次,为了避免重复执行可以将RDD的数据先缓存起来: rdd.cache + rdd.checkpoint
缓存与checkpoint的区别:
1、数据保存位置不一样
缓存是将数据保存在task所在服务的内存/本地磁盘中
checkpoint是将数据保存在HDFS中
2、依赖关系是否切除不一样
缓存是将数据保存在task所在服务的内存/本地磁盘中,所以数据可能丢失,丢失之后需要根据依赖关系重新计算得到数据,此时依赖关系不能切除
checkpoint是将数据保存在HDFS中,数据不会丢失,所以依赖关系会切除
shuffle算子相当于自带缓存,因为shuffle的时候数据本身就会落盘。
7、分区器
Spark默认的分区器有HashPartitioner、RangePartitioner
HashPartitioner的分区规则,key所在的分区号 = key.hashCode % 分区数 < 0 ? key.hashCode % 分区数+分区数 : key.hashCode % 分区数
RangePartitioner的分区规则:
1、首先对RDD数据进行采样,抽取 N-1 个key确定N个分区的边界
2、后续根据key与每个分区的边界确定key的数据放入哪个分区中
3、文件的读取与保存
1、读取文件
sc.textFile() //读取文本文件
sc.objectFile数据类型 //读取对象文件
sc.sequenceFileK,V //读取序列文件
2、写入
rdd.saveAsTextFile() //写成文本文件
rdd.saveAsObjectFile() //写成对象文件
rdd.saveAsSequenceFile() //写成序列文件
4、累加器
原理: 先在每个分区中累加,然后将分区累加的结果发给Driver汇总
应用场景: 一般用于聚合【分组建议不要使用累加器,因为分组不会减少分区的数据量,最终汇总到Driver的时候数据量可能很大导致Driver内存溢出】
好处: 能够减少shuffle操作
自定义累加器:
1、定义一个class继承AccumulatorV2[IN,OUT]
IN: 累加的元素类型
OUT: 最终结果类型
2、重写抽象方法
isZero: 累加器是否为空
copy: 复制新的累加器
reset: 重置累加器
add: 累加单个元素[在每个分区中累加]
merge: 汇总分区的结果[在Driver中汇总]
value: 获取最终结果
3、创建自定义累加器对象: val acc = new XXXX
4、注册累加器: sc.register(acc,“累加器名称”)
5、在spark算子中累加元素: acc.add(…)
6、获取最终结果: acc.value
5、广播变量
原因: spark算子里面的代码是在task中执行,算子外面的代码是在Driver中执行,所以如果算子中需要使用Driver数据的时候,默认情况下是将该数据向所有的task都发送一份,此时该数据占用总内存空间 = 数据大小 * task个数。
使用场景:
1、算子中需要使用Driver数据的时候,能够减少数据占用的内存空间
好处: 此时会将Driver的数据广播到executor,后续task需要使用数据的时候从executor获取使用即可,此时数据占用的空间大小 = 数据大小 * executor个数
2、大表 join 小表的时候,可以将小表的数据通过collect收集到Driver端之后广播出去,减少shuffle操作
使用:
1、广播数据: val bc = sc.broadcast(数据)
2、使用数据: bc.value

1、SparkSql概述
1、Sparksql是什么
SparkSql是处理结构化数据的spark组件
2、SparkSql的数据抽象
1、DataFrame
DataFrame类似mysql的二维表,DataFrame只关注列的信息[列名、列类型]
DataFrame是弱类型,无论每一行存的是什么类型,表现出来都是Row类型
2、DataSet
DataSet类似mysql的二维表,DataSet既关注列的信息[列名、列类型]也关注行的信息[行对象]
DataSet是强类型,每一行存的是什么类型,表现出来就是什么类型
3、RDD、DataFrame、DataSet关系
1、RDD、DataFrame、DataSet都是弹性分布式数据集
2、RDD、DataFrame、DataSet都有分区
3、RDD、DataFrame、DataSet数据会动态存储在内存中,内存空间不足会落盘
2、SparkSql编程
1、SparkSession的创建:
SparkSession.builder().master(…).appName(…).getOrCreate()
2、DataFrame的创建
1、读取文件创建 【常用】
spark.read.json/csv/parquet/orc/…
2、通过toDF方法创建 【常用】
使用toDF方法必须导入隐式转换: import spark.implicits._
集合.toDF
rdd.toDF
如果集合/RDD中元素类型是样例类,通过toDF转成DataFrame之后列的名称就是属性名
如果集合/RDD中元素类型是元组,通过toDF转成DataFrame之后列的名称就是_N,此时可以通过toDF(“列名”,…)来重定义列名
3、通过createDataFrame方法创建
val rdd:RDD[Row] = …
val schema = StructType(Array(StructField(列名,列类型),…))
spark.createDataFrame(rdd,schema)
4、其他DataFrame衍生 【常用】
val df = spark.read.csv(…)
val df2 = df.where(…)
3、SparkSql编程风格
1、命令式[API方法操作数据]
常用命令式:
1、过滤
df.filter(“sql语句过滤条件”)
df.where(“sql语句过滤条件”)
2、去重
distinct 【两行数据所有列的值都相同才会去重】
dropDumplicates(列名,…) 【两行数据指定列的值相同才会去重】
3、列裁剪
selectExpr(“列名”,“列名 别名”,“函数(列名)”,…)
2、声明式[SQL语句操作数据]
1、需要给DataFrame/DataSet起表名
df.createOrReplaceTempView(“表名”) 【创建临时表,只能在当前sparksession中使用】
df.createOrReplaceGlobalTempView(“表名”) 【创建临时表,只能在多个sparksession中使用,在使用的时候必须通过 global_temp.表名 方式使用】
2、通过sql操作数据
spark.sql(“sql语句”)
4、DataSet的创建
1、读取文件创建: sc.textFile(“…”)
2、通过toDS创建:
集合.toDS
rdd.toDS
如果集合/RDD中元素类型是样例类,通过toDS转成DataSet之后列的名称就是属性名
如果集合/RDD中元素类型是元组,通过toDS转成DataSet之后列的名称就是_N,此时可以通过toDF(“列名”,…)来重定义列名
3、通过其他DataSet衍生: ds.map/flatMap/…
4、通过createDataSet方法创建: spark.createDataSet(rdd/集合)
5、RDD、DataFrame、DataSet的转换
rdd转DataFrame: rdd.toDF/ rdd.toDF(…)
rdd转DataSet: rdd.toDS
dataFrame转rdd: df.rdd
dataSet转rdd: ds.rdd
dataSet转DataFrame: ds.toDF/ ds.toDF(…)
DataFrame转DataSet: val ds:DataSet[元素类型] = df.as[元素类型]
dataframe转dataset,as的泛型如果是元组, 元组的个数与类型必须与列的个数和类型一致
dataframe转dataset,as的泛型如果是样例类, 样例类的属性名必须与列名一致
6、DataFrame、DataSet的使用场景
1、如果rdd需要使用sql语句操作数据,此时rdd中元素如果是元组,此时推荐使用DataFrame,因为可以使用toDF重定义列名
2、如果rdd需要使用sql语句操作数据,此时rdd中元素如果是样例类,可以随意转换
3、如果想要使用map/flatMap这种需要写函数的强类型算子的时候,推荐使用dataset
7、自定义函数
1、UDF函数: 一进一出
自定义UDF函数的步骤:
1、定义一个函数对象
2、注册成udf函数: spark.udf.register(函数名,函数对象)
3、使用: spark.sql(“select 函数名(…) from …”)
2、UDAF函数: 多进一出[聚合函数]
1、自定义弱类型UDAF函数
1、定义一个class继承UserDefinedAggregator
2、重写抽象方法
3、注册成udaf函数: spark.udf.register(函数名,自定义udaf对象)
4、使用:: spark.sql(“select 函数名(…) from … group by …”)
2、自定义强类型UDAF函数
1、定义一个class继承Aggregator[IN,BUFF,OUT]
IN: 函数参数类型
BUFF: 中间变量类型
OUT: 最终结果类型
2、重写抽象方法
3、使用sparksql函数将Aggregator转成udaf:
import org.apache.spark.sql.functions._
val uf = udaf(new 自定义UDAF对象)
4、注册成udaf函数: spark.udf.register(函数名,uf)
5、使用:: spark.sql(“select 函数名(…) from … group by …”)
3、数据加载和保存
1、读取数据
1、spark.read
.format(“csv/text/parquet/json/jdbc/orc”) --指定数据读取的格式
.option(k,v)… --指定读取数据需要的参数
.load([path]) --加载数据
2、spark.read.option(k,v)…json/csv/text/parquet/json/jdbc/orc(…) [常用]
读取csv文件常用的option:
sep: 指定字段之间的分隔符
header: 指定是否将文件的第一行作为列名
inferSchema: 指定是否自动推断列的类型
读取jdbc常用的option:
user: 指定mysql的账号
password: 指定mysql密码
url: 指定mysql的jdbc连接
dbtable: 指定读取哪个表
读取jdbc常用方式:
val props = new Properties()
props.setProperty(“user”,“…”)
props.setProperty(“password”,“…”)
1、spark.read.jdbc(url,table,props) --此种方式读取mysql的时候生成的DataFrame的分区数 = 1 【常用】
val conditions = Array(“id<10”,“id>=10 and id<=20”,“id>20”)
2、spark.read.jdbc(url,table,conditions,props) --此种方式读取mysql的时候生成的DataFrame的分区数 = conditions数组的元素个数,每个分区拉取数据的条件就是根据condition数组元素拉取的
3、spark.read.jdbc(url,table,columName,lowerBound,upperBound,numPartitions,props)[常用]
columName: mysql表的列名[必须是数字、Date、时间戳类型的列]
lowerBound: columName列的下限,一般是指该列最小值
upperBound: columName列的下限,一般是指该列最大值
numPartitions: 指定分区数
此种方式读取mysql的时候生成的DataFrame的分区数 = (upperBound-lowerBound)>=numPartitions ? numPartitions : upperBound-lowerBound
2、保存数据
1、df/ds.write
.mode(SaveMode.XXX) --指定写入模式
.format(csv/text/parquet/json/jdbc/orc) --指定写入格式
.option(…)… --指定写入数据需要的参数
.save([path]) --保存
2、df/ds.write.mode(SaveMode.XXX).option(…)…json/csv/parquet/orc()
3、常用的写入模式
SaveMode.Append: 如果保存数据的目录/表已经存在则追加数据到目录/表中 【一般用于保存数据到mysql中,保存数据到mysql中可能出现主键冲突,如果出现使用foreachPartition解决】
SaveMode.Overwrite: 如果保存数据的目录/表已经存在,此时会直接删除目录/表重新写入数据【一般用于将数据保存到HDFS中】
3、与hive交互
idea读取hive数据:
1、引入依赖和hive-site.xml配置文件
2、在创建sparksession的时候通过enableHiveSupport开启hive支持:
SparkSession.builder.master(…).appName(…).enableHiveSupport().getOrCreate()
3、读取hive表: spark.sql(“select … from hive表名 …”)

1、SparkStreaming概述
1、SparkStreaming是什么?
SparkStreaming是负责流式计算的spark组件。
sparkstreaming是微批次的流水计算组件,微批次是指一次接收指定时间内的数据然后统一处理。
sparkstreaming接收一个批次的数据之后会将其封装成一个RDD。
sparkstreaming中的数据抽象是DStream,DStream中流动的是一个个批次的RDD
2、背压机制
原因: sparkstreaming主要是接收kafka数据进行处理,处理过程中可能出现以下情况:
1、如果一个批次接收的数据比较多,导致计算的时间比批次时间要长,会出现批次积压的问题[sparkstreaming在计算的时候是一个批次一个批次串行计算]
2、如果一个批次接收的数据比较少,计算的时间比批次时间要短,资源浪费
背压机制能够根据task运行的速度的快慢动态调整拉取数据的速率
sparkstreaming默认是关闭背压机制,如果想要开启的需要设置参数spark.streaming.backpressure.enabled=true
2、DStream的创建
1、RDD队列数据源:
val queue = mutable.QueueRDD[T]
ssc.queueStream(queue,oneAtATime)
oneAtATime:
true: 每次只消费队列中一个RDD
false: 是将一个批次时间内写入的多个RDD合并之后一起消费
2、自定义数据源
1、创建一个class继承Receiver数据类型
2、重写抽象方法[onStart、onStop]
3、ssc.receiverStream(自定义数据源对象)
3、kafka数据源
目前实际工作中使用的kafka数据源一般采用direct方式,此种方式能够使用背压机制。
1、需要引入依赖
2、通过KafkaUtils读取数据

	val kafkaPara: Map[String, Object] = Map[String, Object](
        ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "hadoop102:9092,hadoop103:9092,hadoop104:9092",
        ConsumerConfig.GROUP_ID_CONFIG -> "atguiguGroup",
        ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> "org.apache.kafka.common.serialization.StringDeserializer",
        ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer]
    )

    //4.读取Kafka数据创建DStream
    val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
        ssc,
        LocationStrategies.PreferConsistent, //优先位置
        ConsumerStrategies.Subscribe[String, String](Set("testTopic"), kafkaPara)// 消费策略:(订阅多个主题,配置参数)
    )
	sparkstreaming消费kafka数据的时候,RDD的分区数 = kafka topic的分区数,可以根据topic分区数动态调整

3、DStream转换
状态: 之前批次的统计结果
1、无状态转换
map/flatMap/reduceByKey/groupByKey/filter
2、有状态转换
UpdateStateByKey(func: (Seq[value值类型], Option[最终结果的value值类型])=>Option[最终结果的value值类型]): 统计全局结果
函数第一个参数是当前批次的每个key对应的所有的value值
函数第二个参数是之前批次该key的统计结果
每个批次计算完成之后都会更新状态
window(窗口长度,滑动长度): 窗口
窗口长度,滑动长度都必须是批次时间的整数倍
reduceByKeyAndWindow(func: (Value值类型,value值类型)=>value值类型,窗口长度,滑动长度) = window + reduceByKey
reduceByKeyAndWindow(func: (Value值类型,value值类型)=>value值类型,inrevFunc: (Value值类型,value值类型)=>value值类型,窗口长度,滑动长度): 一般用于窗口长度特别长,滑动长度比较短的情况[比如窗口长度是3600s,滑动长度是1s,批次时间1s,此时两个窗口包含大量重复的批次,此时默认情况下计算窗口数据的时候是将所有批次一起计算,所以两个窗口大量重复的批次在计算的时候会浪费时间,此时可以通过上一个窗口的结果-滑出批次结果+滑入批次的结果从而快速得到本窗口的结果]
func函数是将之前窗口的结果+滑入的批次的数据
inrevFunc函数是将将之前窗口的结果-滑出的批次的结果
4、DStream的输出
SparKStreaming统计结果一般是存储在mysql、hbase、redis、mongdb等地方,此时可以通过foreachRDD(func: RDD[元素类型]=> Unit)保存当前批次rdd的数据。
5、Sparkstreaming程序的关闭
SparkStreaming程序默认情况一直执行,当业务有改变需要升级代码的时候,就需要停止sparkstreaming程序.
默认情况下,sparkstreaming程序停止的时候是不考虑接受的数据是否有处理完成,会直接关闭,可能导致数据的丢失
所以应该尽量让接受到的数据处理完成只有再关闭程序
可以通过监控Mysql表的字段的值是否改变或者监控hDFS目录是否存在作为条件进行关闭sparkstreaming程序
可以通过ssc.stop(stopSparkContext=true,stopGracefully=true )关闭
stopGracefully:
true: 代表将接收到的数据处理完成在关闭
false: 直接关闭,不考虑是否数据处理完成

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你想要学习Java Spark吗?Java Spark是一个用于大数据处理的开源框架,它提供了简单易用的API和工具来处理大规模数据集。如果你已经熟悉Java编程,并且对大数据处理有兴趣,学习Java Spark是一个不错的选择。 要开始学习Java Spark,你可以按照以下步骤进行: 1. 了解基本概念:首先,你需要了解什么是大数据和分布式计算,以及为什么需要使用框架如Spark来处理大规模数据集。 2. 学习Java和Spark的基础知识:确保你对Java编程语言和相关的基础知识有一定的了解。然后,你可以开始学习Spark的基本概念,如RDD(弹性分布式数据集)和Spark的核心概念。 3. 安装和配置Spark:在你的开发环境中安装和配置Spark。你可以从Spark官方网站下载并按照它们提供的指南进行设置。 4. 编写Spark应用程序:使用Java编写Spark应用程序来处理大规模数据集。你可以使用Spark的API来进行数据转换、过滤、聚合等操作。 5. 调优和优化:学习如何调优和优化你的Spark应用程序,以提高性能和效率。这包括了解Spark的调优技巧、并行处理和集群配置等。 6. 实践项目:尝试使用Spark处理一些真实的大数据集,以提升你的经验和技能。 此外,还有许多在线教程、文档和资源可供你学习Java Spark。你可以参考Spark官方文档、在线教程和示例代码来加深理解和提高技能。 祝你学习Java Spark的过程愉快!如果你还有其他问题,可以继续问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值