Spark知识点总结

Spark基础

Spark VS Hadoop

Hadoop Spark
类型 分布式基础平台,包含计算,存储,调度 分布式计算工具
场景 大规模数据集上的批处理 迭代计算,交互式计算,流计算
价格 对机器要求低,便宜 对内存有要求,相对较贵
编程范式 Map+Reduce,API较为底层,算法适应性差 RDD组成DAG有向无环图,API较为顶层,方便使用
数据存储结构 MapReduce中间计算结果存在HDFS磁盘上,延迟大 RDD中间运算结果存在内存中,延迟小
运行方式 Tash以进程方式维护,任务启动慢 Task以线程方式维护,任务启动快

注意:
尽管Spark相对于Hadoop而言具有较大优势,但Spark并不能完全替代Hadoop,Spark主要用于替代Hadoop中的MapReduce计算模型。存储依然可以使用HDFS。
实际上,Spark 已经很好地融入了 Hadoop 生态圈,并成为其中的重要一员,它可以借助于 YARN 实现资源调度管理,借助于 HDFS 实现分布式存储。

Spark运行模式

  1. local本地模式(单机)---学习测试使用
    分为local单线程和local-cluster多线程。

  2. standalone独立集群模式---学习测试使用
    典型的Master/Slave模式。

  3. standalone-HA高可用模式---生产环境使用
    基于standalone模式,使用zk搭建高可用,避免Master是有单点故障的。

  4. on yarn集群模式---生产环境使用
    运行在yarn集群之上,由yarn负责资源管理,Spark负责任务调度和计算
    好处:计算资源按需伸缩,集群利用率高,共享底层存储,避免数据跨集群迁移。

  5. on mesos集群模式---国内使用较少
    运行在mesos资源管理器框架之上,由mesos负责资源管理,Spark负责任务调度和计算。

  6. on cloud集群模式---中小公司未来会更多使用云服务

Spark Core

RDD是什么?

RDD叫做弹性分布式数据集,是Spark中最基本的数据抽象,代表一个不可变、可分区、里面的元素可并行计算的集合。
RDD四大属性:

  • 数据分片:partitions
  • 分片切割规则:partitioner
  • RDD依赖:dependencies
  • 转换函数:compute

RDD的算子分类

RDD的算子分为两类

  • Transformation 转换算子
转换算子 含义
map(func) 返回一个新的 RDD,该 RDD 由每一个输入元素经过 func 函数转换后组成
filter(func) 返回一个新的 RDD,该 RDD 由经过 func 函数计算后返回值为 true 的输入元素组成
flatMap(func) 类似于 map,但是每一个输入元素可以被映射为 0 或多个输出元素(所以 func 应该返回一个序列,而不是单一元素)
mapPartitions(func) 类似于 map,但独立地在 RDD 的每一个分片上运行,因此在类型为 T 的 RDD 上运行时,func 的函数类型必须是 Iterator[T] => Iterator[U]
mapPartitionsWithIndex(func) 类似于 mapPartitions,但 func 带有一个整数参数表示分片的索引值,因此在类型为 T 的 RDD 上运行时,func 的函数类型必须是(Int, Interator[T]) => Iterator[U]
sample(withReplacement, fraction, seed) 根据 fraction 指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed 用于指定随机数生成器种子
union(otherDataset) 对源 RDD 和参数 RDD 求并集后返回一个新的 RDD
intersection(otherDataset) 对源 RDD 和参数 RDD 求交集后返回一个新的 RDD
distinct([numTasks])) 对源 RDD 进行去重后返回一个新的 RDD
groupByKey([numTasks]) 在一个(K,V)的 RDD 上调用,返回一个(K, Iterator[V])的 RDD
reduceByKey(func, [numTasks]) 在一个(K,V)的 RDD 上调用,返回一个(K,V)的 RDD,使用指定的 reduce 函数,将相同 key 的值聚合到一起,与 groupByKey 类似,reduce 任务的个数可以通过第二个可选的参数来设置
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) 对 PairRDD 中相同的 Key 值进行聚合操作,在聚合过程中同样使用了一个中立的初始值。和 aggregate 函数类似,aggregateByKey 返回值的类型不需要和 RDD 中 value 的类型一致
sortByKey([ascending], [numTasks]) 在一个(K,V)的 RDD 上调用,K 必须实现 Ordered 接口,返回一个按照 key 进行排序的(K,V)的 RDD
sortBy(func,[ascending], [numTasks]) 与 sortByKey 类似,但是更灵活
join(otherDataset, [numTasks]) 在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素对在一起的(K,(V,W))的 RDD
cogroup(otherDataset, [numTasks]) 在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD
cartesian(otherDataset) 笛卡尔积
pipe(command, [envVars]) 对 rdd 进行管道操作
coalesce(numPartitions) 减少 RDD 的分区数到指定值。在过滤大量数据之后,可以执行此操作
repartition(numPartitions) 重新给 RDD 分区
  • Action 动作算子
动作算子 含义
reduce(func) 通过 func 函数聚集 RDD 中的所有元素,这个功能必须是可交换且可并联的
collect() 在驱动程序中,以数组的形式返回数据集的所有元素
count() 返回 RDD 的元素个数
first() 返回 RDD 的第一个元素(类似于 take(1))
take(n) 返回一个由数据集的前 n 个元素组成的数组
takeSample(withReplacement,num, [seed]) 返回一个数组,该数组由从数据集中随机采样的 num 个元素组成,可以选择是否用随机数替换不足的部分,seed 用于指定随机数生成器种子
takeOrdered(n, [ordering]) 返回自然顺序或者自定义顺序的前 n 个元素
saveAsTextFile(path) 将数据集的元素以 textfile 的形式保存到 HDFS 文件系统或者其他支持的文件系统,对于每个元素,Spark 将会调用 toString 方法,将它装换为文件中的文本
saveAsSequenceFile(path) 将数据集中的元素以 Hadoop sequencefile 的格式保存到指定的目录下,可以使 HDFS 或者其他 Hadoop 支持的文件系统
saveAsObjectFile(path) 将数据集的元素,以 Java 序列化的方式保存到指定的目录下
countByKey() 针对(K,V)类型的 RDD,返回一个(K,Int)的 map,表示每一个 key 对应的元素个数
foreach(func) 在数据集的每一个元素上,运行函数 func 进行更新
foreachPartition(func) 在数据集的每一个分区上,运行函数 func

使用Transformations类算子,定义并描述数据形态的转换过程,然后调用actions类算子,将计算结果收集起来,或是物化到磁盘。

Spark 在运行时的计算被划分为两个环节。

  1. 基于不同数据形态之间的transformation,构建计算流图(DAG);

  2. 通过 Actions 类算子,以回溯的方式去触发执行这个计算流图。

延迟计算: 开发者调用的各类transformations算子并不立即执行计算,当且仅当开发者调用actions算子时,之前调用的转换算子才会付诸执行

Spark进程模型

  • Driver:解析用户代码、构建计算流图,然后将计算流图转化为分布式任务,并把任务分发给集群中的执行进程(Executor)交付运行
    核心组件:

    • DAGScheduler:
      1.根据用户代码构建计算流图DAG。
      2.以Shuffle为边界,从后向前以递归的方式,把逻辑上的计算图 DAG,转化成一个又一个 Stages。
      3.Spark从后向前,以递归的方式,基于Stages创建TaskSets,并将TaskSets提交给TaskScheduler请求调度。

    • TaskScheduler
      按照任务的本地倾向性,来遴选出 TaskSet 中适合调度的 Tasks。

    • SchedulerBackend
      将分布式任务分发到 Executors 中去
      SchedulerBackend 与集群内所有 Executors 中的 ExecutorBackend 保持周期性通信,双方通过 LaunchedExecutor、RemoveExecutor、StatusUpdate 等消息来互通有无、变更可用计算资源。

  • Executor:调用内部线程池,结合事先分配好的数据分片,并发地执行任务代码。
    核心组件:

    • ExecutorBackend
      接收到任务后,将任务分派给Executors线程中的一个又一个CPU线程,每个线程负责处理一个Task

DAG

DAG指的是数据转换执行的过程,有方向,无闭环(其实就是 RDD 执行的流程);
原始的 RDD 通过一系列的转换操作就形成了 DAG ,任务执行时,可以按照 DAG 的描述,执行真正的计算(数据被操作的一个过程)。
DAG 的边界
开始:通过 SparkContext 创建的 RDD;
结束:触发 Action,一旦触发 Action 就形成了一个完整的 DAG。

DAG 划分 Stage

image

  • 一个 Spark 程序可以有多个 DAG(有几个 Action,就有几个 DAG,上图最后只有一个 Action(图中未表现),那么就是一个 DAG)。
  • 一个 DAG 可以有多个 Stage(根据宽依赖/shuffle 进行划分)。
  • 同一个 Stage 可以有多个 Task 并行执行(task 数=分区数,如上图,Stage1 中有三个分区 P1、P2、P3,对应的也有三个 Task)。

可以看到这个 DAG 中只有 reduceByKey 操作是一个宽依赖,Spark 内核会以此为边界将其前后划分成不同的 Stage。

同时我们可以注意到,在图中 Stage1 中,从 textFile 到 flatMap 到 map 都是窄依赖,这几步操作可以形成一个流水线操作,通过 flatMap 操作生成的 partition 可以不用等待整个 RDD 计算结束,而是继续进行 map 操作,这样大大提高了计算的效率。

宽窄依赖

RDD 和它依赖的父 RDD 的关系有两种不同的类型,即宽依赖窄依赖
窄依赖:父 RDD 的一个分区只会被子 RDD 的一个分区依赖
宽依赖:父 RDD 的一个分区会被子 RDD 的多个分区依赖(涉及到 shuffle)

对于窄依赖:
窄依赖的多个分区可以并行计算;
窄依赖的一个分区的数据如果丢失只需要重新计算对应的分区的数据就可以了;
partition 的转换处理在 stage 中完成计算,不划分(将窄依赖尽量放在在同一个 stage 中,可以实现流水线计算)。

对于宽依赖:
由于有 shuffle 的存在,只能在父 RDD 处理完成后,才能开始接下来的计算,也就是说需要划分 stage,必须等到上一阶段计算完成才能计算下一阶段。

总结:
Spark 会根据 shuffle/宽依赖使用回溯算法来对 DAG 进行 Stage 划分,从后往前,遇到宽依赖就断开,遇到窄依赖就把当前的 RDD 加入到当前的 stage/阶段中

Shuffle

Shuffle:集群范围内跨进程、跨节点的数据交换
把原本散落在不同 Executors 的单词,分发到同一个 Executor 的过程,就是 Shuffle

Spark Shuffle 分为两种:一种是基于 Hash 的 Shuffle;另一种是基于 Sort 的 Shuffle
在 Spark 2.0 版本中, Hash Shuffle 方式己经不再使用。
使用 HashShuffle 的 Spark 在 Shuffle 时产生大量的文件。当数据量越来越多时,产生的文件量是不可控的,这严重制约了 Spark 的性能及扩展能力

Sorted-Based Shuffle 也有缺点,其缺点反而是它排序的特性,它强制要求数据在 Mapper 端必须先进行排序,所以导致它排序的速度有点慢。好在出现了 Tungsten-Sort Shuffle ,它对排序算法进行了改进,优化了排序的速度。

SortShuffle

SortShuffleManager 的运行机制主要分成三种:

  1. 普通运行机制;
  2. bypass 运行机制,当 shuffle read task 的数量小于等于spark.shuffle.sort.bypassMergeThreshold参数的值时(默认为 200),就会启用 bypass 机制;
  3. Tungsten Sort 运行机制,开启此运行机制需设置配置项 spark.shuffle.manager=tungsten-sort。开启此项配置也不能保证就一定采用此运行机制(后面会解释)。

普通运行机制

在该模式下,数据会先写入一个内存数据结构中,此时根据不同的 shuffle 算子,可能选用不同的数据结构。如果是 reduceByKey 这种聚合类的 shuffle 算子,那么会选用 Map 数据结构,一边通过 Map 进行聚合,一边写入内存;如果是 join 这种普通的 shuffle 算子,那么会选用 Array 数据结构,直接写入内存。接着,每写一条数据进入内存数据结构之后,就会判断一下,是否达到了某个临界阈值。如果达到临界阈值的话,那么就会尝试将内存数据结构中的数据溢写到磁盘,然后清空内存数据结构。

在溢写到磁盘文件之前,会先根据 key 对内存数据结构中已有的数据进行排序。排序过后,会分批将数据写入磁盘文件。默认的 batch 数量是 10000 条,也就是说,排序好的数据,会以每批 1 万条数据的形式分批写入磁盘文件。写入磁盘文件是通过 Java 的 BufferedOutputStream 实现的。

一个 task 将所有数据写入内存数据结构的过程中,会发生多次磁盘溢写操作,也就会产生多个临时文件。最后会将之前所有的临时磁盘文件都进行合并,这就是merge 过程,此时会将之前所有临时磁盘文件中的数据读取出来,然后依次写入最终的磁盘文件之中。此外,由于一个 task 就只对应一个磁盘文件,也就意味着该 task 为下游 stage 的 task 准备的数据都在这一个文件中,因此还会单独写一份索引文件,其中标识了下游各个 task 的数据在文件中的 start offset 与 end offset。

SortShuffleManager 由于有一个磁盘文件 merge 的过程,因此大大减少了文件数量。

普通运行机制的 SortShuffleManager 工作原理如下图所示:
image

bypass运行机制

Reducer 端任务数比较少的情况下,基于 Hash Shuffle 实现机制明显比基于 Sort Shuffle 实现机制要快,因此基于 Sort huffle 实现机制提供了一个回退方案,就是 bypass 运行机制。对于 Reducer 端任务数少于配置属性spark.shuffle.sort.bypassMergeThreshold设置的个数时,使用带 Hash 风格的回退计划。

bypass 运行机制的触发条件如下:

  • shuffle map task 数量小于spark.shuffle.sort.bypassMergeThreshold=200参数的值。
  • 不是聚合类的 shuffle 算子。

此时,每个 task 会为每个下游 task 都创建一个临时磁盘文件,并将数据按 key 进行 hash 然后根据 key 的 hash 值,将 key 写入对应的磁盘文件之中。当然,写入磁盘文件时也是先写入内存缓冲,缓冲写满之后再溢写到磁盘文件的。最后,同样会将所有临时磁盘文件都合并成一个磁盘文件,并创建一个单独的索引文件。

该过程的磁盘写机制其实跟未经优化的 HashShuffleManager 是一模一样的,因为都要创建数量惊人的磁盘文件,只是在最后会做一个磁盘文件的合并而已。因此少量的最终磁盘文件,也让该机制相对未经优化的 HashShuffleManager 来说,shuffle read 的性能会更好。

而该机制与普通 SortShuffleManager 运行机制的不同在于:第一,磁盘写机制不同;第二,不会进行排序。也就是说,启用该机制的最大好处在于,shuffle write 过程中,不需要进行数据的排序操作,也就节省掉了这部分的性能开销

bypass 运行机制的 SortShuffleManager 工作原理如下图所示:
image

Tungsten Sort Shuffle 运行机制

基于 Tungsten Sort 的 Shuffle 实现机制主要是借助 Tungsten 项目所做的优化来高效处理 Shuffle。

Spark 提供了配置属性,用于选择具体的 Shuffle 实现机制,但需要说明的是,虽然默认情况下 Spark 默认开启的是基于 SortShuffle 实现机制,但实际上,参考 Shuffle 的框架内核部分可知基于 SortShuffle 的实现机制与基于 Tungsten Sort Shuffle 实现机制都是使用 SortShuffleManager,而内部使用的具体的实现机制,是通过提供的两个方法进行判断的:

对应非基于 Tungsten Sort 时,通过 SortShuffleWriter.shouldBypassMergeSort 方法判断是否需要回退到 Hash 风格的 Shuffle 实现机制,当该方法返回的条件不满足时,则通过 SortShuffleManager.canUseSerializedShuffle 方法判断是否需要采用基于 Tungsten Sort Shuffle 实现机制,而当这两个方法返回都为 false,即都不满足对应的条件时,会自动采用普通运行机制。

因此,当设置了 spark.shuffle.manager=tungsten-sort 时,也不能保证就一定采用基于 Tungsten Sort 的 Shuffle 实现机制。

要实现 Tungsten Sort Shuffle 机制需要满足以下条件:

  • Shuffle 依赖中不带聚合操作或没有对输出进行排序的要求。
  • Shuffle 的序列化器支持序列化值的重定位(当前仅支持 KryoSerializer Spark SQL 框架自定义的序列化器)。
  • Shuffle 过程中的输出分区个数少于 16777216 个。

Spark任务调度流程

  1. DAGScheduler 以 Shuffle 为边界,将DAG 拆分为多个执行阶段 Stages,然后为每个 Stage 创建任务集 TaskSet。

  2. SchedulerBackend 通过与 Executors 中的 ExecutorBackend 的交互来实时地获取集群中可用的计算资源,并将这些信息记录到 ExecutorDataMap 数据结构。 同时根据 ExecutorDataMap 中可用资源创建 WorkerOffer,以 WorkerOffer 为粒度提供计算资源。

  3. 对于给定 WorkerOffer,TaskScheduler 结合 TaskSet 中任务的本地性倾向,按照 PROCESS_LOCAL、NODE_LOCAL、RACK_LOCAL 和 ANY 的顺序,依次对 TaskSet 中的任务进行遍历,优先调度本地性倾向要求苛刻的 Task。

  4. 被选中的 Task 由 TaskScheduler 传递给 SchedulerBackend,再由 SchedulerBackend 分发到 Executors 中的 ExecutorBackend。

  5. Executors 接收到 Task 之后,即调用本地线程池来执行分布式任务。

Spark内存模型

  • Reserved Memory
    固定为300M,不受开发者控制,它是Spark预留的用来存储各种Spark内部对象的内存区域

  • User Memory
    用于存储开发者自定义的数据结构

  • Execution Memory
    用来执行分布式任务

  • Storage Memory
    缓存分布式数据集

Spark1.6 引入了统一内存管理模式 Execution Memory和Storage Memory之间可以相互转化

  • 如果对方的内存空间有空闲,双方可以互相抢占;
  • 对于 Storage Memory 抢占的 Execution Memory 部分,当分布式任务有计算需要时,Storage Memory 必须立即归还抢占的内存,涉及的缓存数据要么落盘、要么清除;
  • 对于 Execution Memory 抢占的 Storage Memory 部分,即便 Storage Memory 有收回内存的需要,也必须要等到分布式任务执行完毕才能释放。

内存优化配置

  • spark.executor.memory
    绝对值,指定了 Executor 进程的 JVM Heap 总大小。

  • spark.memory.fraction
    比例值 ,标记 Spark 处理分布式数据集的内存总大小,这部分内存包括 Execution Memory 和 Storage Memory 两部分

  • spark.memory.storageFraction
    比例值,进一步区分 Execution Memory 和 Storage Memory 的初始大小。

Spark存储系统

负责维护所有暂存在内存与磁盘中的数据,这些数据包括 Shuffle 中间文件、RDD Cache 以及广播变量。

  • BlockManagerMaster
    Driver端,通过心跳与BlockManager完成信息交换

  • BlockManager
    Executors端,核心指责在于管理数据块的元数据,这些元数据记录并维护数据块的地址、位置、尺寸以及状态

  • MemoryStore
    负责内存中的数据存取

  • DiskStore
    负责磁盘中的数据访问

RDD累加器和广播变量

在默认情况下,当 Spark 在集群的多个不同节点的多个任务上并行运行一个函数时,它会把函数中涉及到的每个变量,在每个任务上都生成一个副本。
但是,有时候需要在多个任务之间共享变量,或者在任务(Task)和任务控制节点(Driver Program)之间共享变量。
为了满足这种需求,Spark 提供了两种类型的变量:

  • 累加器 accumulators:累加器支持在所有不同节点之间进行累加计算(比如计数或者求和)。

  • 广播变量 broadcast variables:广播变量用来把变量在所有节点的内存之间进行共享,在每个机器上缓存一个只读的变量,而不是为机器上的每个任务都生成一个副本。

Spark SQL

数据分析方式

  1. 命令式
    在前面的 RDD 部分, 非常明显可以感觉的到是命令式的, 主要特征是通过一个算子, 可以得到一个结果, 通过结果再进行后续计算。
    命令式的优点

    • 操作粒度更细,能够控制数据的每一个处理环节;

    • 操作更明确,步骤更清晰,容易维护;

    • 支持半/非结构化数据的操作。

    命令式的缺点

    • 需要一定的代码功底;

    • 写起来比较麻烦。

  2. SQL
    对于一些数据科学家/数据库管理员/DBA, 要求他们为了做一个非常简单的查询, 写一大堆代码, 明显是一件非常残忍的事情, 所以 SQL on Hadoop 是一个非常重要的方向。
    SQL 的优点

    • 表达非常清晰

    SQL 的缺点

    • 试想一下 3 层嵌套的 SQL 维护起来应该挺力不从心的吧;
    • 试想一下如果使用 SQL 来实现机器学习算法也挺为难的吧。
  3. 总结
    SQL 擅长数据分析和通过简单的语法表示查询,命令式操作适合过程式处理和算法性的处理。
    在 Spark 出现之前,对于结构化数据的查询和处理, 一个工具一向只能支持 SQL 或者命令式,使用者被迫要使用多个工具来适应两种场景,并且多个工具配合起来比较费劲。
    而 Spark 出现了以后,统一了两种数据处理范式是一种革新性的进步。

Hive和SparkSQL

Hive 是将 SQL 转为 MapReduce。

SparkSQL 可以理解成是将 SQL 解析成:“RDD + 优化” 再执行。

数据分类和 SparkSQL 适用场景

  1. 结构化数据
    一般指数据有固定的 Schema(约束),例如在用户表中,name 字段是 String 型,那么每一条数据的 name 字段值都可以当作 String 来使用:
id name url alexa country
1 Google https://www.google.cm/ 1 USA
2 淘宝 https://www.taobao.com/ 13 CN
3 菜鸟教程 https://www.runoob.com/ 4689 CN
4 微博 http://weibo.com/ 20 CN
5 Facebook https://www.facebook.com/ 3 USA
  1. 半结构化数据
    一般指的是数据没有固定的 Schema,但是数据本身是有结构的。
  • 没有固定 Schema
    指的是半结构化数据是没有固定的 Schema 的,可以理解为没有显式指定 Schema。
    比如说一个用户信息的 JSON 文件,
    第 1 条数据的 phone_num 有可能是数字,
    第 2 条数据的 phone_num 虽说应该也是数字,但是如果指定为 String,也是可以的,
    因为没有指定 Schema,没有显式的强制的约束。

  • 有结构
    虽说半结构化数据是没有显式指定 Schema 的,也没有约束,但是半结构化数据本身是有有隐式的结构的,也就是数据自身可以描述自身。
    例如 JSON 文件,其中的某一条数据是有字段这个概念的,每个字段也有类型的概念,所以说 JSON 是可以描述自身的,也就是数据本身携带有元信息。

  1. 总结
  • 数据分类总结:
定义 特点 举例
结构化数据 有固定的 Schema 有预定义的 Schema 关系型数据库的表
半结构化数据 没有固定的 Schema,但是有结构 没有固定的 Schema,有结构信息,数据一般是自描述的 指一些有结构的文件格式,例如 JSON
非结构化数据 没有固定 Schema,也没有结构 没有固定 Schema,也没有结构 指图片/音频之类的格式
  • Spark 处理什么样的数据?
    RDD 主要用于处理非结构化数据 、半结构化数据、结构化;
    SparkSQL 主要用于处理结构化数据(较为规范的半结构化数据也可以处理)。

  • 总结
    SparkSQL 是一个既支持 SQL 又支持命令式数据处理的工具;
    SparkSQL 的主要适用场景是处理结构化数据(较为规范的半结构化数据也可以处理)。

Spark SQL 数据抽象

  1. DataFrame
    DataFrame 的前身是 SchemaRDD,从 Spark 1.3.0 开始 SchemaRDD 更名为 DataFrame。并不再直接继承自 RDD,而是自己实现了 RDD 的绝大多数功能。
    DataFrame 是一种以 RDD 为基础的分布式数据集,类似于传统数据库的二维表格,带有 Schema 元信息(可以理解为数据库的列名和类型)。
    总结:
    DataFrame 就是一个分布式的表;
    DataFrame = RDD - 泛型 + SQL 的操作 + 优化。

  2. DataSet
    DataSet 是在 Spark1.6 中添加的新的接口。
    与 RDD 相比,保存了更多的描述信息,概念上等同于关系型数据库中的二维表。
    与 DataFrame 相比,保存了类型信息,是强类型的,提供了编译时类型检查。
    调用 Dataset 的方法先会生成逻辑计划,然后被 spark 的优化器进行优化,最终生成物理计划,然后提交到集群中运行!
    DataSet 包含了 DataFrame 的功能。
    Spark2.0 中两者统一,DataFrame 表示为 DataSet[Row],即 DataSet 的子集。

  3. 总结
    DataFrame = RDD - 泛型 + Schema + SQL + 优化
    DataSet = DataFrame + 泛型
    DataSet = RDD + Schema + SQL + 优化

总结:

  1. DataFrame 和 DataSet 都可以通过 RDD 来进行创建;

  2. 也可以通过读取普通文本创建--注意:直接读取没有完整的约束,需要通过 RDD+Schema;

  3. 通过 josn/parquet 会有完整的约束;

  4. 不管是 DataFrame 还是 DataSet 都可以注册成表,之后就可以使用 SQL 进行查询了! 也可以使用 DSL!

Spark Streaming

Spark Streaming 是一个基于 Spark Core 之上的实时计算框架,可以从很多数据源消费数据并对数据进行实时的处理,具有高吞吐量和容错能力强等特点。

Spark Streaming 的特点:

  • 易用
    可以像编写离线批处理一样去编写流式程序,支持 java/scala/python 语言。

  • 容错
    SparkStreaming 在没有额外代码和配置的情况下可以恢复丢失的工作。

  • 易整合到 Spark 体系
    流式处理与批处理和交互式查询相结合。

整体流程

Spark Streaming 中,会有一个接收器组件 Receiver,作为一个长期运行的 task 跑在一个 Executor 上。Receiver 接收外部的数据流形成 input DStream。

DStream 会被按照时间间隔划分成一批一批的 RDD,当批处理间隔缩短到秒级时,便可以用于处理实时数据流。时间间隔的大小可以由参数指定,一般设在 500 毫秒到几秒之间。

对 DStream 进行操作就是对 RDD 进行操作,计算处理的结果可以传给外部系统。

Spark Streaming 的工作流程像下面的图所示一样,接受到实时数据后,给数据分批次,然后传给 Spark Engine 处理最后生成该批次的结果。

数据抽象

Spark Streaming 的基础抽象是 DStream(Discretized Stream,离散化数据流,连续不断的数据流),代表持续性的数据流和经过各种 Spark 算子操作后的结果数据流。

可以从以下多个角度深入理解 DStream:

  1. DStream 本质上就是一系列时间上连续的 RDD
  2. 对 DStream 的数据的进行操作也是按照 RDD 为单位来进行的
  3. 容错性,底层 RDD 之间存在依赖关系,DStream 直接也有依赖关系,RDD 具有容错性,那么 DStream 也具有容错性
  4. 准实时性/近实时性
    Spark Streaming 将流式计算分解成多个 Spark Job,对于每一时间段数据的处理都会经过 Spark DAG 图分解以及 Spark 的任务集的调度过程。
    对于目前版本的 Spark Streaming 而言,其最小的 Batch Size 的选取在 0.5~5 秒钟之间。
    所以 Spark Streaming 能够满足流式准实时计算场景,对实时性要求非常高的如高频实时交易场景则不太适合。

总结
简单来说 DStream 就是对 RDD 的封装,你对 DStream 进行操作,就是对 RDD 进行操作。

对于 DataFrame/DataSet/DStream 来说本质上都可以理解成 RDD。

DStream 相关操作

DStream 上的操作与 RDD 的类似,分为以下两种:

Transformations(转换)

Output Operations(输出)/Action

  • Transformations
    以下是常见 Transformation---都是无状态转换:即每个批次的处理不依赖于之前批次的数据:
Transformation 含义
map(func) 对 DStream 中的各个元素进行 func 函数操作,然后返回一个新的 DStream
flatMap(func) 与 map 方法类似,只不过各个输入项可以被输出为零个或多个输出项
filter(func) 过滤出所有函数 func 返回值为 true 的 DStream 元素并返回一个新的 DStream
union(otherStream) 将源 DStream 和输入参数为 otherDStream 的元素合并,并返回一个新的 DStream
reduceByKey(func, [numTasks]) 利用 func 函数对源 DStream 中的 key 进行聚合操作,然后返回新的(K,V)对构成的 DStream
join(otherStream, [numTasks]) 输入为(K,V)、(K,W)类型的 DStream,返回一个新的(K,(V,W)类型的 DStream
transform(func) 通过 RDD-to-RDD 函数作用于 DStream 中的各个 RDD,可以是任意的 RDD 操作,从而返回一个新的 RDD

除此之外还有一类特殊的 Transformations---有状态转换:当前批次的处理需要使用之前批次的数据或者中间结果。

有状态转换包括基于追踪状态变化的转换(updateStateByKey)和滑动窗口的转换:

  1. UpdateStateByKey(func)

  2. Window Operations 窗口操作

  • Output/Action
    Output Operations 可以将 DStream 的数据输出到外部的数据库或文件系统。

当某个 Output Operations 被调用时,spark streaming 程序才会开始真正的计算过程(与 RDD 的 Action 类似)。

Output Operation 含义
print() 打印到控制台
saveAsTextFiles(prefix, [suffix]) 保存流的内容为文本文件,文件名为"prefix-TIME_IN_MS[.suffix]"
saveAsObjectFiles(prefix,[suffix]) 保存流的内容为 SequenceFile,文件名为 "prefix-TIME_IN_MS[.suffix]"
saveAsHadoopFiles(prefix,[suffix]) 保存流的内容为 hadoop 文件,文件名为"prefix-TIME_IN_MS[.suffix]"
foreachRDD(func) 对 Dstream 里面的每个 RDD 执行 func

Structured Streaming

在 2.0 之前,Spark Streaming 作为核心 API 的扩展,针对实时数据流,提供了一套可扩展、高吞吐、可容错的流式计算模型。
Spark Streaming 会接收实时数据源的数据,并切分成很多小的 batches,然后被 Spark Engine 执行,产出同样由很多小的 batchs 组成的结果流。
本质上,这是一种 micro-batch(微批处理)的方式处理,用批的思想去处理流数据.这种设计让Spark Streaming 面对复杂的流式处理场景时捉襟见肘。

spark streaming 这种构建在微批处理上的流计算引擎,比较突出的问题就是处理延时较高(无法优化到秒以下的数量级),以及无法支持基于 event_time 的时间窗口做聚合逻辑。

spark 在 2.0 版本中发布了新的流计算的 API,Structured Streaming/结构化流。

Structured Streaming 是一个基于 Spark SQL 引擎的可扩展、容错的流处理引擎。
统一了流、批的编程模型,你可以使用静态数据批处理一样的方式来编写流式计算操作。
并且支持基于 event_time 的时间窗口的处理逻辑。

默认情况下,结构化流式查询使用微批处理引擎进行处理,该引擎将数据流作为一系列小批处理作业进行处理,从而实现端到端的延迟,最短可达 100 毫秒,并且完全可以保证一次容错。
自 Spark 2.3 以来,引入了一种新的低延迟处理模式,称为连续处理,它可以在至少一次保证的情况下实现低至 1 毫秒的端到端延迟。
也就是类似于 Flink 那样的实时流,而不是小批量处理。
实际开发可以根据应用程序要求选择处理模式,但是连续处理在使用的时候仍然有很多限制,目前大部分情况还是应该采用小批量模式。

1 Spark Streaming & Structured Streaming

  • Spark Streaming 时代 -DStream-RDD
    Spark Streaming 采用的数据抽象是 DStream,而本质上就是时间上连续的 RDD,对数据流的操作就是针对 RDD 的操作。

  • Structured Streaming 时代 - DataSet/DataFrame -RDD
    Structured Streaming 是 Spark2.0 新增的可扩展和高容错性的实时计算框架,它构建于 Spark SQL 引擎,把流式计算也统一到 DataFrame/Dataset 里去了。
    Structured Streaming 相比于 Spark Streaming 的进步就类似于 Dataset 相比于 RDD 的进步。

  1. 核心思想
    Structured Streaming 最核心的思想就是将实时到达的数据看作是一个不断追加的 unbound table 无界表,到达流的每个数据项(RDD)就像是表中的一个新行被附加到无边界的表中.这样用户就可以用静态结构化数据的批处理查询方式进行流计算,如可以使用 SQL 对到来的每一行数据进行实时查询处理。

  2. 应用场景
    Structured Streaming 将数据源映射为类似于关系数据库中的表,然后将经过计算得到的结果映射为另一张表,完全以结构化的方式去操作流式数据,这种编程模型非常有利于处理分析结构化的实时数据;

Spark 数据倾斜

注意,要区分开数据倾斜与数据过量这两种情况,数据倾斜是指少数task被分配了绝大多数的数据,因此少数task运行缓慢;数据过量是指所有task被分配的数据量都很大,相差不多,所有task都运行缓慢。

Spark中的数据倾斜问题主要指shuffle过程中出现的数据倾斜问题,是由于不同的key对应的数据量不同导致的不同task所处理的数据量不同的问题。

数据倾斜的表现:

  1. Spark作业的大部分task都执行迅速,只有有限的几个task执行的非常慢,此时可能出现了数据倾斜,作业可以运行,但是运行得非常慢;

  2. Spark作业的大部分task都执行迅速,但是有的task在运行过程中会突然报出OOM,反复执行几次都在某一个task报出OOM错误,此时可能出现了数据倾斜,作业无法正常运行。定位数据倾斜问题:

  3. 查阅代码中的shuffle算子,例如reduceByKey、countByKey、groupByKey、join等算子,根据代码逻辑判断此处是否会出现数据倾斜;

  4. 查看Spark作业的log文件,log文件对于错误的记录会精确到代码的某一行,可以根据异常定位到的代码位置来明确错误发生在第几个stage,对应的shuffle算子是哪一个;

解决方案

  1. 预聚合原始数据
  2. 预处理导致倾斜的keu
  3. 提高reduce并行度
  4. 使用map join

参考链接

史上最强干货!Spark知识点总结

posted @ 2021-10-13 20:01  cos晓风残月  阅读(537)  评论(0)    收藏  举报