spark基础理论及优化思路(三)

本文深入探讨了Spark Streaming的DStream工作原理,对比了Hadoop与Spark的相似点和差异,强调了Spark在迭代计算和内存管理上的优势。介绍了Spark集群搭建步骤,并详细解析了Spark的内核架构,包括Master、Driver、Worker和Executor的角色。此外,文章还讨论了数据倾斜和Spark的高可用性策略,以及如何根据任务量调整资源。最后,分享了避免数据丢失的策略和Spark SQL的执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DStream以及基本⼯作原理?

  • DStream是spark streaming提供的⼀种⾼级抽象,代表了⼀个持续不断的数据流。DStream可以通过输⼊数据源来创建,⽐如
    Kafka、flume等,也可以通过其他DStream的⾼阶函数来创建,⽐如map、reduce、join和window等。
  • DStream内部其实不断产⽣RDD,每个RDD包含了⼀个时间段的数据。
  • Spark streaming⼀定是有⼀个输⼊的DStream接收数据,按照时间划分成⼀个⼀个的batch,并转化为⼀个RDD,RDD的数据
    是分散在各个⼦节点的partition中。

hadoop和spark的相同点和不同点?

  • Hadoop底层使⽤MapReduce计算架构,只有map和reduce两种操作,表达能⼒⽐较⽋缺,⽽且在MR过程中会重复的读写
    hdfs,造成⼤量的磁盘io读写操作,所以适合⾼时延环境下批处理计算的应⽤;
  • Spark是基于内存的分布式计算架构,提供更加丰富的数据集操作类型,主要分成转化操作和⾏动操作,包括map、reduce、filter、flatmap、groupbykey、reducebykey、union和join等,数据分析更加快速,所以适合低时延环境下计算的应⽤;
  • spark与hadoop最⼤的区别在于迭代式计算模型。基于mapreduce框架的Hadoop主要分为map和reduce两个阶段,两个阶段完了就结束了,所以在⼀个job⾥⾯能做的处理很有限;spark计算模型是基于内存的迭代式计算模型,可以分为n个阶段,根据⽤户编写的RDD算⼦和程序,在处理完⼀个阶段后可以继续往下处理很多个阶段,⽽不只是两个阶段。所以spark相较于
    mapreduce,计算模型更加灵活,可以提供更强⼤的功能。
  • 但是spark也有劣势,由于spark基于内存进⾏计算,虽然开发容易,但是真正⾯对⼤数据的时候,在没有进⾏调优的轻局
    昂下,可能会出现各种各样的问题,⽐如OOM内存溢出等情况,导致spark程序可能⽆法运⾏起来,⽽mapreduce虽然运⾏缓慢,但是⾄少可以慢慢运⾏完

hadoop和spark使⽤场景?

Hadoop/MapReduce和Spark最适合的都是做离线型的数据分析,但Hadoop特别适合是单次分析的数据量“很⼤”的情景,
⽽Spark则适⽤于数据量不是很⼤的情景。

  • ⼀般情况下,对于中⼩互联⽹和企业级的⼤数据应⽤⽽⾔,单次分析的数量都不会“很⼤”,因此可以优先考虑使⽤Spark。
  • 业务通常认为Spark更适⽤于机器学习之类的“迭代式”应⽤,80GB的压缩数据(解压后超过200GB),10个节点的集群规
    模,跑类似“sum+group-by”的应⽤,MapReduce花了5分钟,⽽spark只需要2分钟。

搭建spark集群步骤?

  • 安装spark包
  • 修改spark-env.sh
  • 修改slaves⽂件
  • 安装spark集群
  • 启动spark集群

spark内核架构原理?

  • Master进程:主要负责资源的调度和分配,还有集群的监控等职责。
  • Driver进程:我们编写的spark程序就在Driver上由Driver进程执⾏。
  • Worker进程:主要负责两个,第⼀个是⽤⾃⼰的内存去存储RDD的某个或者某些partition,第⼆个是启动其他进程和线
    程,对RDD上的partition进⾏并⾏的处理和计算。
  • Executor和Task:负责执⾏对RDD的partition进⾏并⾏的计算,也就是执⾏我们对RDD定义的各种算⼦。
    • 将spark程序通过spark-submit(shell)命令提交到结点上执⾏,其实会通过反射的⽅式,创建和构造⼀个DriverActor进
      程出来,通过Driver进程执⾏我们的Application应⽤程序。
    • 应⽤程序的第⼀⾏⼀般是先构造SparkConf,再构造SparkContext;
    • SparkContext在初始化的时候,做的最重要的两件事就是构造DAGScheduler和TaskScheduler;
    • TaskScheduler会通过对应的⼀个后台进程去连接Master,向Master注册Application;
    • Master通知Worker启动executor;
    • executor启动之后会⾃⼰反向注册到TaskSchduler,所有的executor都反向注册到Driver之后,Driver结束SparkContext
      初始化,然后继续执⾏⾃⼰编写的代码;
    • 每执⾏⼀个action就会创建⼀个job;
    • DAGScheduler会根据Stage划分算法,将job划分为多个stage,并且为每个stage创建TaskSet(⾥⾯有多个task);
    • TaskScheduler会根据task分配算法,把TaskSet⾥⾯每⼀个task提交到executor上执⾏;
    • executor每接收到⼀个task,都会⽤taskRunner来封装task,然后从线程池取出⼀个线程,执⾏这个task;
    • taskRunner将我们编写的代码(算⼦及函数)进⾏拷贝,反序列化,然后执⾏task;
    • task有两种,shuffleMapTask和resultTask,只有最后⼀个stage是resultTask,之前的都是shuffleMapTask;
    • 所以最后整个spark应⽤程序的执⾏,就是job划分为stage,然后stage分批次作为taskset提交到executor执⾏,每
      个task针对RDD的⼀个partition执⾏我们定义的算⼦和函数,以此类推,直到所有的操作执⾏完⽌。

spark解决了hadoop的哪些问题?

  • MR:抽象层次低,需要使⽤⼿⼯代码来完成程序编写,使⽤上难以上⼿;
    Spark:Spark采⽤RDD计算模型,简单容易上⼿。
  • MR:只提供map和reduce两个操作,表达能⼒⽋缺;
    Spark:Spark采⽤更加丰富的算⼦模型,包括map、flatmap、groupbykey、reducebykey等;
  • MR:⼀个job只能包含map和reduce两个阶段,复杂的任务需要包含很多个job,这些job之间的管理以来需要开发者⾃⼰进
    ⾏管理;
    Spark:Spark中⼀个job可以包含多个转换操作,在调度时可以⽣成多个stage,⽽且如果多个map操作的分区不变,是可
    以放在同⼀个task⾥⾯去执⾏;
  • MR:中间结果存放在hdfs中;
    Spark:Spark的中间结果⼀般存在内存中,只有当内存不够了,才会存⼊本地磁盘,⽽不是hdfs;
  • MR:只有等到所有的map task执⾏完毕后才能执⾏reduce task;
    Spark:Spark中分区相同的转换构成流⽔线在⼀个task中执⾏,分区不同的需要进⾏shuffle操作,被划分成不同的stage需
    要等待前⾯的stage执⾏完才能执⾏。
  • MR:只适合batch批处理,时延⾼,对于交互式处理和实时处理⽀持不够;
    Spark:Spark streaming可以将流拆成时间间隔的batch进⾏处理,实时计算。
  • MR:对于迭代式计算处理较差;
    Spark:Spark将中间数据存放在内存中,提⾼迭代式计算性能。
    总结:Spark替代hadoop,其实应该是spark替代mapreduce计算模型,因为spark本⾝并不提供存储,所以现在⼀般⽐较常⽤的⼤数据架构是基于spark+hadoop这样的模型;

数据倾斜的产⽣和解决办法?

数据倾斜意味着某⼀个或者某⼏个partition的数据特别⼤,导致这⼏个partition上的计算需要耗费相当长的时间。在spark中
同⼀个应⽤程序划分成多个stage,这些stage之间是串⾏执⾏的,⽽⼀个stage⾥⾯的多个task是可以并⾏执⾏,task数⽬由partition数⽬决定,如果⼀个partition的数⽬特别⼤,那么导致这个task执⾏时间很长,导致接下来的stage⽆法执⾏,从⽽导致整个job执⾏变慢。
避免数据倾斜,⼀般是要选⽤合适的key,或者⾃⼰定义相关的partitioner,通过加盐或者哈希值来拆分这些key,从⽽将这
些数据分散到不同的partition去执⾏。
如下算⼦会导致shuffle操作,是导致数据倾斜可能发⽣的关键点所
在:groupByKey;reduceByKey;aggregaByKey;join;cogroup;

spark 实现⾼可⽤性:High Availability

如果有些数据丢失,或者节点挂掉;那么不能让你的实时计算程序挂了;必须做⼀些数据上的冗余副本,保证你的实时计
算程序可以7 * 24⼩时的运转。

  • updateStateByKey、window等有状态的操作,⾃动进⾏checkpoint,必须设置checkpoint⽬录:容错的⽂件系统的⽬录,⽐
    如说,常⽤的是HDFS SparkStreaming.checkpoint(“hdfs://192.168.1.105:9090/checkpoint”)
    设置完这个基本的checkpoint⽬录之后,有些会⾃动进⾏checkpoint操作的DStream,就实现了HA⾼可⽤性;checkpoint,相当于是会把数据保留⼀份在容错的⽂件系统中,⼀旦内存中的数据丢失掉;那么就可以直接从⽂件系统中读取数据;不需要重新进⾏计算
  • Driver⾼可⽤性
    第⼀次在创建和启动StreamingContext的时候,那么将持续不断地将实时计算程序的元数据(⽐如说,有些dstream或
    者job执⾏到了哪个步骤),如果后⾯,不幸,因为某些原因导致driver节点挂掉了;那么可以让spark集群帮助我们⾃动重
    启driver,然后继续运⾏实时计算程序,并且是接着之前的作业继续执⾏;没有中断,没有数据丢失
    第⼀次在创建和启动StreamingContext的时候,将元数据写⼊容错的⽂件系统(⽐如hdfs);spark-submit脚本中加⼀些
    参数;保证在driver挂掉之后,spark集群可以⾃⼰将driver重新启动起来;⽽且driver在启动的时候,不会重新创建⼀
    个streaming context,⽽是从容错⽂件系统(⽐如hdfs)中读取之前的元数据信息,包括job的执⾏进度,继续接着之前的进
    度,继续执⾏。
    使⽤这种机制,就必须使⽤cluster模式提交,确保driver运⾏在某个worker上⾯;但是这种模式不⽅便我们调试程序,⼀
    会⼉还要最终测试整个程序的运⾏,打印不出log;我们这⾥仅仅是⽤我们的代码给⼤家⽰范⼀下:
JavaStreamingContextFactory contextFactory = new JavaStreamingContextFactory() {
2 @Override
3 public JavaStreamingContext create() {
4 JavaStreamingContext jssc = new JavaStreamingContext(...);
5 JavaDStream<String> lines = jssc.socketTextStream(...); 
6 jssc.checkpoint(checkpointDirectory); 
7 return jssc;
8 }
9 };
10
11 JavaStreamingContext context = JavaStreamingContext.getOrCreate(checkpointDirectory, contextFactory
12 context.start();
13 context.awaitTermination();
14
15 spark-submit
16 --deploy-mode cluster
17 --supervise
  • 实现RDD⾼可⽤性:启动WAL预写⽇志机制
    spark streaming,从原理上来说,是通过receiver来进⾏数据接收的;接收到的数据,会被划分成⼀个⼀个的
    block;block会被组合成⼀个batch;针对⼀个batch,会创建⼀个rdd;启动⼀个job来执⾏我们定义的算⼦操作。
    receiver主要接收到数据,那么就会⽴即将数据写⼊⼀份到容错⽂件系统(⽐如hdfs)上的checkpoint⽬录中的,⼀份磁
    盘⽂件中去;作为数据的冗余副本。⽆论你的程序怎么挂掉,或者是数据丢失,那么数据都不肯能会永久性的丢失;因为
    肯定有副本。
    WAL(Write-Ahead Log)预写⽇志机制
spark.streaming.receiver.writeAheadLog.enable true

spark实际⼯作中,是怎么来根据任务量,判定需要多少资源的?

1 /mydata/spark-1.6.3-bin-spark-1.6.3-2.11-withhive/bin/spark-submit \
2 --master yarn \
3 --deploy-mode client \
4 --num-executors 1 \
5 --executor-memory 7G \
6 --executor-cores 6 \
7 --conf spark.ui.port=5052 \
8 --conf spark.yarn.executor.memoryOverhead=1024 \
9 --conf spark.storage.memoryFraction=0.2 \
10 --class UserAnalytics \
11 /mydata/dapeng/comecarsparkstreamingproject.jar
12 --num-executors 表示启动多少个executor来运⾏该作业
13 --executor-memory 表示每⼀个executor进程允许使⽤的内存空间
14 --executor-cores 在同⼀个executor⾥,最多允许多少个task可同时并发运⾏
15 --executor.memoryOverhead ⽤于存储已被加载的类信息、常量、静态变量等数据
16 --shuffle.memoryFraction ⽤于shuffle阶段缓存拉取到的数据所使⽤的内存空间
17 --storage.memoryFraction ⽤于Java堆栈的空间
18
19 /mydata/spark-1.6.3-bin-spark-1.6.3-2.11-withhive/bin/spark-submit \
20 --master yarn \
21 --deploy-mode client \
22 --num-executors 6 \
23 --executor-memory 7G \
24 --executor-cores 5 \
25 --conf spark.ui.port=5051 \
26 --conf spark.yarn.executor.memoryOverhead=1024 \
27 --conf spark.memory.storageFraction=0.2 \
28 --conf spark.executor.extraJavaOptions=-XX:+UseG1GC \
29
30 配置了6个executor,每个executor有5个core,每个executor有7G内存

为什么要设计宽窄依赖?

  • 对于窄依赖:
    窄依赖的多个分区可以并行计算;
    窄依赖的一个分区的数据如果丢失只需要重新计算对应的分区的数据就可以了。
  • 对于宽依赖:
    划分 Stage(阶段)的依据:对于宽依赖,必须等到上一阶段计算完成才能计算下一阶段

DAG 是什么?

DAG(Directed Acyclic Graph 有向无环图)指的是数据转换执行的过程,有方向,无闭环(其实就是 RDD 执行的流程);
原始的 RDD 通过一系列的转换操作就形成了 DAG 有向无环图,任务执行时,可以按照 DAG 的描述,执行真正的计算(数据被操作的一个过程)

DAG 中为什么要划分 Stage?

并行计算。
一个复杂的业务逻辑如果有 shuffle,那么就意味着前面阶段产生结果后,才能执行下一个阶段,即下一个阶段的计算要依赖上一个阶段的数据。那么我们按照shuffle 进行划分(也就是按照宽依赖就行划分),就可以将一个 DAG 划分成多个 Stage/阶段,在同一个 Stage 中,会有多个算子操作,可以形成一个pipeline 流水线,流水线内的多个平行的分区可以并行执行。

如何划分 DAG 的 stage?

  • 对于窄依赖,partition 的转换处理在 stage 中完成计算,不划分(将窄依赖尽量放在在同一个 stage 中,可以实现流水线计算)。
  • 对于宽依赖,由于有 shuffle 的存在,只能在父 RDD 处理完成后,才能开始接下来的计算,也就是说需要要划分 stage

DAG 划分为 Stage 的算法了解吗?

核心算法:回溯算法
从后往前回溯/反向解析,遇到窄依赖加入本 Stage,遇见宽依赖进行 Stage 切分。Spark 内核会从触发 Action 操作的那个 RDD 开始从后往前推,首先会为最后一个 RDD 创建一个 Stage,然后继续倒推,如果发现对某个 RDD 是宽依赖,那么就会将宽依赖的那个 RDD 创建一个新的 Stage,那个 RDD 就是新的 Stage的最后一个 RDD。 然后依次类推,继续倒推,根据窄依赖或者宽依赖进行 Stage的划分,直到所有的 RDD 全部遍历完成为止;

对于 Spark 中的数据倾斜问题你有什么好的方案?

  • 前提是定位数据倾斜,是 OOM 了,还是任务执行缓慢,看日志,看 WebUI
  • 解决方法,有多个方面:
    • 避免不必要的 shuffle,如使用广播小表的方式,将 reduce-side-join提升为 map-side-join
    • 分拆发生数据倾斜的记录,分成几个部分进行,然后合并 join 后的结果
    • 改变并行度,可能并行度太少了,导致个别 task 数据压力大
    • 两阶段聚合,先局部聚合,再全局聚合
    • 自定义 paritioner,分散 key 的分布,使其更加均匀

Spark 中的 OOM 问题?

  • map 类型的算子执行中内存溢出如 flatMap,mapPatitions
    • 原因:map 端过程产生大量对象导致内存溢出:这种溢出的原因是在单个map 中产生了大量的对象导致的针对这种问题。
  • 解决方案
    • 增加堆内内存。
    • 在不增加内存的情况下,可以减少每个 Task 处理数据量,使每个 Task产生大量的对象时,Executor 的内存也能够装得下。具体做法可以在会产生大量对象的 map 操作之前调用 repartition 方法,分区成更小的块传入 map。
  • shuffle 后内存溢出如 join,reduceByKey,repartition。
    • shuffle 内存溢出的情况可以说都是 shuffle 后,单个文件过大导致的。在 shuffle 的使用,需要传入一个 partitioner,大部分 Spark 中的shuffle 操作,默认的 partitioner 都是 HashPatitioner,默认值是父RDD 中最大的分区数.这个参数 spark.default.parallelism 只对HashPartitioner 有效.如果是别的 partitioner 导致的 shuffle 内存溢出就需要重写 partitioner 代码了.
  • driver 内存溢出
    • 用户在 Dirver 端口生成大对象,比如创建了一个大的集合数据结构。解决方案:将大对象转换成 Executor 端加载,比如调用 sc.textfile 或者评估大对象占用的内存,增加 dirver 端的内存
    • 从 Executor 端收集数据(collect)回 Dirver 端,建议将 driver 端对 collect 回来的数据所作的操作,转换成 executor 端 rdd 操作。

Spark 中数据的位置是被谁管理的?

每个数据分片都对应具体物理位置,数据的位置是被 blockManager 管理,无论数据是在磁盘,内存还是 tacyan,都是由 blockManager 管理

介绍一下 join 操作优化经验

  • join 其实常见的就分为两类: map-side joinreduce-side join
    当大表和小表 join 时,用 map-side join 能显著提高效率。
    将多份数据进行关联是数据处理过程中非常普遍的用法,不过在分布式计算系统中,这个问题往往会变的非常麻烦,因为框架提供的 join 操作一般会将所有数据根据 key 发送到所有的 reduce 分区中去,也就是 shuffle 的过程。造成大量的网络以及磁盘 IO 消耗,运行效率极其低下,这个过程一般被称为
    reduce-side-join:
    如果其中有张表较小的话,我们则可以自己实现在 map 端实现数据关联,跳过大量数据进行 shuffle 的过程,运行时间得到大量缩短,根据不同数据可能会有几倍到数十倍的性能提升。在大数据量的情况下,join 是一中非常昂贵的操作,需要在 join 之前应尽可能的先缩小数据量。
    对于缩小数据量,有以下几条建议
  • 若两个 RDD 都有重复的 key,join 操作会使得数据量会急剧的扩大。所有,最好先使用 distinct 或者 combineByKey 操作来减少 key 空间或者用 cogroup 来处理重复的 key,而不是产生所有的交叉结果。在combine 时,进行机智的分区,可以避免第二次 shuffle。
  • 如果只在一个 RDD 出现,那你将在无意中丢失你的数据。所以使用外连接会更加安全,这样你就能确保左边的 RDD 或者右边的 RDD 的数据完整性,在 join 之后再过滤数据。
  • 如果我们容易得到 RDD 的可以的有用的子集合,那么我们可以先用filter 或者 reduce,如何在再用 join

Spark 与 MapReduce 的 Shuffle 的区别?

  • 相同点:都是将 mapper(Spark 里是 ShuffleMapTask)的输出进行partition,不同的 partition 送到不同的 reducer(Spark 里 reducer可能是下一个 stage 里的 ShuffleMapTask,也可能是 ResultTask)
  • 不同点:
    • MapReduce 默认是排序的,spark 默认不排序,除非使用 sortByKey 算子。
    • MapReduce 可以划分成 split,map()、spill、merge、shuffle、sort、reduce()等阶段,spark 没有明显的阶段划分,只有不同的 stage 和算子操作。
    • MR 落盘,Spark 不落盘,spark 可以解决 mr 落盘导致效率低下的问题;

Spark SQL 执行的流程?

  • parser:基于 antlr 框架对 sql 解析,生成抽象语法树。
  • 变量替换:通过正则表达式找出符合规则的字符串,替换成系统缓存环境的变量 SQLConf 中的spark.sql.variable.substitute,默认是可用的;参考SparkSqlParser
  • parser:将 antlr 的 tree 转成 spark catalyst 的 LogicPlan,也就是 未解析的逻辑计划;详细参考 AstBuild, ParseDriver
  • analyzer:通过分析器,结合 catalog,把 logical plan 和实际的数据绑定起来,将 未解析的逻辑计划 生成 逻辑计划;详细参考QureyExecution
  • 缓存替换:通过 CacheManager,替换有相同结果的 logical plan(逻辑计划)
  • logical plan 优化,基于规则的优化;优化规则参考 Optimizer,优化执行器 RuleExecutor
  • 生成 spark plan,也就是物理计划;参考QueryPlanner和SparkStrategies
  • spark plan 准备阶段
  • 构造 RDD 执行,涉及 spark 的 wholeStageCodegenExec 机制,基于janino 框架生成 java 代码并编译

Spark SQL 是如何将数据写到 Hive 表的?

  • 方式一:是利用 Spark RDD 的 API 将数据写入 hdfs 形成 hdfs 文件,之后再将 hdfs 文件和 hive 表做加载映射。
  • 方式二:利用 Spark SQL 将获取的数据 RDD 转换成 DataFrame,再将DataFrame 写成缓存表,最后利用 Spark SQL 直接插入 hive 表中。而对于利用 Spark SQL 写 hive 表官方有两种常见的 API,第一种是利用JavaBean 做映射,第二种是利用 StructType 创建 Schema 做映射。

Spark 运行效率更高?来源于 Spark 内置的哪些机制?

  1. 基于内存计算,减少低效的磁盘交互;
  2. 高效的调度算法,基于 DAG;
  3. 容错机制 Linage。
    重点部分就是 DAG 和 Lingae

在实际开发的时候是如何保证数据不丢失的

 flume 那边采用的 channel 是将数据落地到磁盘中,保证数据源端安全性
(可以在补充一下,flume 在这里的 channel 可以设置为 memory 内存中,提高数据接收处理的效率,但是由于数据在内存中,安全机制保证不了,故选择 channel 为磁盘存储。整个流程运行有一点的延迟性)
 sparkStreaming 通过拉模式整合的时候,使用了 FlumeUtils 这样一个类,该类是需要依赖一个额外的 jar 包(spark-streaming-flume_2.10)
 要想保证数据不丢失,数据的准确性,可以在构建 StreamingConext 的时候,利用 StreamingContext.getOrCreate(checkpoint, creatingFunc:() => StreamingContext)来创建一个 StreamingContext,使用StreamingContext.getOrCreate 来创建 StreamingContext 对象,传入的第一个参数是 checkpoint 的存放目录,第二参数是生成StreamingContext 对象的用户自定义函数。如果 checkpoint 的存放目录存在,则从这个目录中生成 StreamingContext 对象;如果不存在,才会调用第二个函数来生成新的 StreamingContext 对象。在 creatingFunc函数中,除了生成一个新的 StreamingContext 操作,还需要完成各种操作,然后调用ssc.checkpoint(checkpointDirectory)来初始化checkpoint 功能,最后再返回 StreamingContext 对象。
这样,在 StreamingContext.getOrCreate 之后,就可以直接调用 start()函数来启动(或者是从中断点继续运行)流式应用了。如果有其他在启动或继续运行都要做的工作,可以在 start()调用前执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值