Spark工作总结(干货篇)

Spark学习总结

文章目录

  • Spark学习总结
    • 什么是大数据
    • Spark介绍及特点
    • Spark架构及重要角色
    • Spark程序启动流程
    • 基础篇
      • `Dataset`和`DataFrame`
      • 广播变量
      • 累加器
    • 算子篇
      • 转化算子,行动算子,持久化算子的区别
      • 非shuffle类算子
      • shuffle类算子
        • `sortby`和`sortbykey`
        • `groupByKey`和`reduceByKey`
    • 扩展篇
      • 广播变量特性是不能修改
      • 分组取`topN` 的最优方案
      • 当发现申请Executor个数与实际不符时
      • Spark Streaming实现精准一次性消费
      • Spark Streaming与Kafka对应关系
      • Spark Lens使用
      • SparkSql解决小文件问题
      • Receiver和Direct模式区别
      • Transent使用小结

什么是大数据

​ 大数据(big data),IT行业术语,是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。

​ 总而言之就是原先一台电脑无法处理的文件(因为数据量太大了),这时候我多加几台机器,原先一台电脑的算力无法计算数据,现在我加几台机器同时计算这份数据,是不是就能把这份数据给处理了,换个白话文,假如4G内存无法算清8G数据,这时候我用两台4G内存的计算机是不是就可以算完8g数据(mapreduce),再或者我有16G的数据 ,物理磁盘只有4G 这时候我是不是可以用4台计算机平摊存储这16G的数据(hdfs),每个机器的4g内存计算 4g 物理数据,我是不是可以让这台机器先算2G数据 再算2G数据,但是我又想人为的控制可能不太友好,所有我用了yarn,让YARN对我的数据计算进行合理分配资源

Spark介绍及特点

​ Spark是一种主要基于内存的计算的并行计算框架,提到Spark我们不得不想到另外的计算框架:MapReduceSpark的出现解决了MapReduce很多痛点(不知道为啥所有组件都喜欢和MapReduce对比)

MapReduce

  • 只能做离线计算
  • 编程API不够灵活,只能在map方法和reduce自己实现逻辑
  • 复杂计算逻辑,一个MR无法完成,需要多个MR按先后顺序串联
  • Shuffle时数据落本地磁盘
  • 多个MR就要频繁Shuffle,频繁读写HDFS,效率低

Spark

  • MapReduce快几倍甚至100倍
  • 高度封装的Api,支持多种编程语言(java, scala, python)
    • 读取Hdfs
    • 读取关系型数据库
    • 读取Hbase
    • 读取EsMongodb
  • 运行灵活,拥有多种运行模式
    • StandAlone模式: 自带的集群模式
    • Yarn: 通用调度框架
    • Local模式:方便开发调试
  • 既可以做离线计算,也可以做实时计算
  • 抽象的数据集(RDDDataSetDataFrameDStream)
  • Shuffle时数据落本地磁盘,执行checkpoint时会将本地磁盘数据上传至hdfs目录
  • 丰富的算子为计算做保障 cachecheckpoint保证安全

Spark架构及重要角色

  • Spark工作总结(干货篇)_第1张图片Applicatition(应用程序): 指用户编写的SPARK应用程序,包含驱动程序(Dirver) 和 之后会分布在多个节点上运行的Executor代码

  • Dirver(驱动程序): 运行Application 中的main 函数,并通过new sparkContext()代码,为Spark程序生成运行环境,主要通过Master 进行资源的申请,任务的分配和监控等,当Executor运行完毕后,Driver负责将SparkContext关闭,通常用SparkContext代表Driver

  • Master(总控进程):Spark Standalone运行模式下的主节点,负责管理和分配集群资源来启动Spark Application

  • Worker(工作节点): 集群中任何可以运行Application代码的节点,standalone 模式下为slaves 配置的节点名称

    Spark工作总结(干货篇)_第2张图片

  • Executor(执行进程) Application运行在Worker节点上的一个进程,该进程负责运行Task,并负责将数据存在内存或者磁盘上。

  • partition(分区数)

    • 假如读取hdfs文件,按照split切割 128mb为一个初始task数

    • 在map阶段partition数目保持不变

    • 在Reduce阶段,RDD的聚合会触发shuffle操作,聚合后的RDD的partition数目和具体操作有关,RDD在计算的时候,每个分区都会起一个task,所以,rdd的分区数目决定了总的task数目 ,可以看出如下task个数(partition)为12个。

      Spark工作总结(干货篇)_第3张图片

  • Task(计算任务): Spark程序切分的最小单位,负责执行Executor分配的任务,内部运行RDD任务集,每个分区执行一个task任务

    • task被执行的并行度=Executors数目(num-executors) * 每个Executor核数(executor-cores)
    • 此时有100个分区,那么计算的时候就会生成100个task,你的executor为10个,每个executor核数为2,那么一次可处理的task为20,计算这个RDD就需要5轮次

Spark程序启动流程

object WordCount {
  def main(args: Array[String]): Unit = {

    //创建SparkContext
    val conf = new SparkConf().setAppName("WordCount")
    //SparkContext是用来创建最原始的RDD的
    val sc: SparkContext = new SparkContext(conf)
    //创建RDD(Lazy)
    val lines: RDD[String] = sc.textFile(args(0))
    lines.partitions
    //Transformation 开始(Lazy)
    //切分压平
    val words: RDD[String] = lines.flatMap(_.split(" "))
    //将单词和一组合
    val wordAndOne: RDD[(String, Int)] = words.map((_, 1))
    //分组聚合
    val reduced: RDD[(String, Int)] = wordAndOne.reduceByKey(_ + _)
    //排序
    val sorted: RDD[(String, Int)] = reduced.sortBy(_._2, false)
    //Transformation 结束
    //Action算子,会触发任务执行
    //将数据保存到HDFS
    sorted.saveAsTextFile(args(1))
    //释放资源
    sc.stop()
  }
}

# 执行任务
spark-sbumint xxx.jar xxx.WordCount
等同于
/opt/module/jdk1.8.0_144 -cp \
/opt/module/spark-2.1.1-bin-hadoop2.7/conf/:/opt/module/spark-2.1.1-bin-hadoop2.7/jars/*:/opt/module/hadoop-2.7.2/etc/hadoop/ \
-Xmx1g -XX:MaxPermSize=256m \
org.apache.spark.deploy.SparkSubmit \
1.在启动Spark时,spark会先帮我们把SparkMasterWorker启动起来,WorkerMaster上报当前信息(计算机ip及名称,核数,物理磁盘大小,内存大小等信息),由Master收集为一张表,同时MasterWorker搭建心跳桥梁,由Worker上报心跳状况至MasterMaster通过各自的RpcEnvPoint进行通信,Worker会启动Executor进程执行task任务,通过application生成执行计划,划分state,通过state与state的依赖关系,划分分区任务,分区个数为task任务个数,当WorkerMaster 失败达到一定次数时汇报Worker节点宕机
    
2.Spark-submit提交程序命令时,假设当前为client(客户端提交模式),会在本地启动Driver进程,通过SparkConf收集spark资源配置信息,通过ConcurrentHashMap进行收集

3.当执行到SparkContext时,会向Master发送注册Application的信息,此时Driver才算是与Master进行连接成功,运行接下来的程序时,Dirver会将RDD数据集提交至Master,由Master分配Worker去执行任务

4.通过ReadTextFile从本地读取文件,此时partition就是默认设置的partition数量,在默认partition的算法中,默认初始RDD的最小partition的大小只能为12
   (sc.defaultMinPartitions=min(sc.defaultParallelism,2)5.因为我没设置分区数,所以默认分区数为cpu核数,但是与2取最小分区数量,所以为2

6.flatMap,map底层都没有runJob提交命令,所以不会触发shuflle,因此words,wordAndOne 的分区数量都为2 

7. 但是当遇到reduceByKey(_ + _) 算子是,因为底层触发了 shuffle算子(new ShuffleRDD()),所以数据会被重写洗牌,同时写入本地磁盘(生成index和data文件),分区数量进行改变,会变成了defaultParallelism(cpu)核数的分区大小 >2 
    
8. 因为sortby也会触发存在subJob方法,所以SortBy也会触发Shuffle触发,所以此时分区大小 与defaultParallelim个数一致

基础篇

DatasetDataFrame

DataSet对比DataFrame,多了类型指定,方法更加健壮,丰富,可以同时使用RDD算子方法和DataSet Sql方法,而DataSet只能使用SparkSql单一操作,通过引入import spark.implicits._依赖 调用.DF可直接使用,DataFrame = DataSet[Row]

广播变量

通常是为了实现mapside join,可以将Driver端的数据广播到属于该application的Executor,然后通过Driver广播变量返回的引用,获取实现广播到Executor的数据

累加器

累加器相当于分布式中统筹变量,分布式累加,在Driver端定义初始化,在Executor端累加

Spark工作总结(干货篇)_第4张图片

算子篇

转化算子,行动算子,持久化算子的区别

  • 转换算子: 转换算子不触发提交作业,完成作业中间处理过程,懒加载算子,需要行动算子操作的时候才会触发运算

  • 行动算子:这类算子会触发SparkContext提交job作业,行动算子和转换算子,再源码中多了一个runJob() 方法调用

  • 持久化算子

    • cache: 将经过某个算子计算后的结果全部装载进入内存,加快后续重复使用效率

    • persist:可指定存储级别,默认是物理存储,可以选择内存或者物理存储级别

    • checkpoint: 转化算子,遇到行动算子后,会单独开启一个新的job做checkpoint,这时候会切断血缘关系

非shuffle类算子

mapmappartition

  • map是针对RDD中的每一个元素进行操作
  • mapPartition是对RDD的每一个分区的迭代器进行操作,返回的是迭代器,迭代器存的是地址信息

理解思路,假设读取spark读取hdfs文件,按照split切割,原有200mb的文件,按照128mb切割为一个分区,所以此时的分区数为2,假设要对每行数据的后缀加上.index后缀,使用map,会对每行数据进行操作,效率十分缓慢,有多少个元素就会执行多少次,而使用mappartition操作时,是分区级别操作,减少大量开关操作,且在一个并行度中操作所有元素,有多少个分区就会执行多少次,此次操作为2次,独立在每个分区上运行,所以mappartition效率会比map高很多,但是mappartition并不代表一定,假如一个partition有很多数据的话,一次函数处理可能会导致oom,普通的map一般会导致oom

Spark工作总结(干货篇)_第5张图片

MapPartitionsDemo.scala

shuffle类算子

sortbysortbykey

sortby底层参考的是sortbykey,对数据进行keyby,之后进行排序

Spark工作总结(干货篇)_第6张图片

groupByKeyreduceByKey

groupbykeyreducebykey在代码中的表现就是是否开启了预聚合模式

Spark工作总结(干货篇)_第7张图片

image-20210915103202593

Spark工作总结(干货篇)_第8张图片

cachepersist

cache的底层是调用persist(磁盘)Memsist(内存)级别

image-20210915104911593

扩展篇

广播变量特性是不能修改

广播变量一但广播出去就不能改变,为了以后可以定期的改变要关联的数据,可以定义一个object[单例对象] (全局变量),在函数内使用,并且加一个定时器,然后定期更新数据,不使用广播变量解决问题

广播遍历并不是存储再Driver,而是在每个executor中都存储一份,广播遍历块(broadcast-black) 每个Executor最高只能存储40Mb的数据,在查询广播时,会先从本地的broadcast-black中先进行查询,当自己本地没有时,会向其他Executor进行请求查询,消耗网络资源

分组取topN 的最优方案

先分组,toList然后在内存中排序,每个组中的数据比较大,可能会产生内存溢出

自定义分区器,然后在分区内排序,可以使用TreeSet, 算子 top 的源码就是如此

使用方法:

it.foreach(t => {
    //将数据添加到treeset中
    sorter += t
    // 删除树形结构的 最后一个数据
    if (sorter.size > topN) {
        sorter -= sorter.last
    }
})

为什么使用TreeSet而不推荐使用ArrayList

在时间复杂度中,顺序结构 和 链表结构的时间复杂度不同,顺序表的时间复杂度为 O(n^2) 而二叉数属于链表结构 他的时间复杂度为log2^n 所以推荐使用TreeSet

当发现申请Executor个数与实际不符时

1.检查是否开启spark executor的动态感知策略,spark是默认开启动态感知策略,这个时候是需要关闭的,例如我生成申请 20个executor,但是实际只产生了 11个Executor,这个时候就是出现了开启动态策略的故障

Spark Streaming实现精准一次性消费

写入关系型数据库:在Driver端获取偏移量,然后将计算好的结果和偏移量,使用支持事务的数据库,在同一事务中将偏移量和计算结果更新到数据库中

写入非关系型数据库中,将偏移量和计算好的结果同时写入到Hbase或ES的同一行中

Spark Streaming与Kafka对应关系

Spark的分区数和Kafka的分区数需要一致,才能达到两个组件的最大吞吐量,过多的消费者只会造成资源浪费,同时过少的消费者会消耗资源,原先一个spark task只消费一个分区数据,现在一个spark task消费两个分区数据

Spark Lens使用

介绍:

我们平时写Spark Job的时候最长苦恼的应该就是如果和调节memoery,vcore这些参数,资源申请少了会造成job的失败,多了就会造成资源的浪费。所以Sparklens就是这么一个让你更加了解你的job运行情况,从而有效的进行Spark 优化的工具。

问题产生原因:

Spark工作总结(干货篇)_第9张图片

--packages qubole:Sparklens:0.3.1-s_2.11 
--conf spark.extraListeners=com.qubole.Sparklens.QuboleJobListener

原在线调用sparklens方法,该方法一执行就会从网上拉取sparklens包进行解析执行,但是http://dl.bintray.com/spark-packages/maven/ 不知怎么,网站出现问题无法下载sparklens包,–packages命令我们就没有办法使用,所以我们更改为以下方法

1.准备工作下载`spark lens.jar`包
https://mvnrepository.com/artifact/qubole/sparklens/0.3.2-s_2.11

2.将下载好的 0.3.2-s_2.11.jar 放入lib下,添加如下参数(此路径随意)

--jars ./lib/sparklens-0.3.2-s_2.11.jar  
--conf spark.extraListeners=com.qubole.sparklens.QuboleJobListener
--conf spark.sparklens.data.dir=/tmp/spark/sparklens # 默认保存hdfs上
--conf spark.sparklens.report.email=<email>   # 可以将运行结果发送至邮件中

样例:
${SPARK_HOME}/bin/spark-submit \
--master yarn   \
--deploy-mode client  \
--class com.meat.main.ApplogODS2DWD   \
--driver-memory 512M    \
--executor-memory 512M    \
--executor-cores 1    \
--queue default    \
--num-executors 3  \
--jars ./lib/sparklens-0.3.2-s_2.11.jar  \     
--conf spark.extraListeners=com.qubole.sparklens.QuboleJobListener \
/root/project/bigdata-dw-spark-offline/bigdata-dw-spark-offline.jar yarn  ${dt}

在程序运行结束时会出现sparklens分析结果

3.如果没有及时保存sparklens分析结果,在·spark.sparklens.data.dir· 下可找到刚分析的json离线文件
找到spark-submit单独执行
./bin/spark-submit --jars ./lib/sparklens-0.3.2-s_2.11.jar   --class com.qubole.sparklens.app.ReporterApp qubole-dummy-arg <filename.json.path> 即可再次得到分析结果 

SparkSql解决小文件问题

将Hive风格的Coalesce and Repartition Hint 应用到Spark SQL需要注意这种方式对Spark的版本有要求,建议在Spark2.4.X及以上版本使用,示例:

INSERT ... SELECT /*+ COALESCE(numPartitions) */ ...
INSERT ... SELECT /*+ REPARTITION(numPartitions) */ ...

Receiver和Direct模式区别

receiver 可以简称为高级API,receiver模式是借助外界的东西,比如zookeeper来维护消费者偏移量,并且是master节点接收到消息后,首先发送给从节点做zookeeper备份,然后再发送到driver端去执行

direct模式是kafka自己去维护偏移量的,kafka充当储存数据的乙方,sparkStreaming是主动去kafka中拿数据的,不需要一个task一直被占用接收数据,基于direct模式的offset是存储再内存中的

kafka的偏移量是单独存在一个_consumer_offsets主题内的

Transent使用小结

  1. 一旦变量被transient修饰,变量就将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问
  2. transient关键字只能修饰变量,而不能修饰方法和类,注意,本地变量是不能被transient关键字修饰的,变量如果是用户自定义类变量,则该类需要实现Serializable接口
  3. transient关键字修饰的变量不能再被序列化,一个静态变量不管是否被transient修饰,均不能被序列化

你可能感兴趣的:(大数据,spark,大数据,分布式)