Spark系列 - 2 - 三大数据结构

为能够进行高并发和高吞吐的数据处理,Spark封装了三大数据结构,用于不同的数据场景。

包括 RDD、累加器、广播变量。下面详细介绍这三大数据结构。

一、RDD

1、什么是RDD

前面提到RDD 是弹性分布式数据集,是Spark最基本的数据抽象。代表一个不可变、可分区、元素可并发计算的集合。

弹性包括:

        存储的弹性,涉及内存、磁盘的自动切换。

        容错的弹性,数据丢失会自动恢复。

        计算的弹性,当计算出错会启动重试机制,默认task 重试4次,stage 重试4次。

        调度的弹性,DAGScheduler 会自动处理stage 和 task的失败,调度到正常的节点上执行。

        分片的弹性,可以根据需要重新分片。

数据集:封装了计算逻辑,但不保存数据。

数据抽象:抽象成一个类,通过子类具体实现。

不可变:RDD封装的计算逻辑不可以改变,如需改变则需要重新生成RDD。

可分区:任务被分成多个Task,发送到不同的Executor 执行,从而实现并行计算。

2、为什么有RDD

Spark提供了一个抽象的数据模型,不用担心底层数据的分布式特性,只需将具体的计算逻辑表达为一系列转换操作。不同的RDD之间的转换还可以形成依赖关系,进而实现管道化,避免中间结果的存储,降低数据复制、磁盘IO以及序列化等操作的开销。

3、RDD属性

主要属性包括:

 

4、RDD优点

通过上面的介绍可以发现RDD的一些特性优点,其中就包括: 支持迭代、流批处理、交互式SQL查询,同时适合多用户管理,运行应用程序的弹性扩展和缩减计算资源等。

对比Hadoop的MapReduce, Spark 通过丰富的RDD 计算模型,让开发人员更容易掌握。计算的中间结果优先存放到内存减少中间结果落盘带来的磁盘IO开销。同时在计算过程中分区相同的转换构成流水在一个task上执行;分区不同的需要shuffle操作的,则被划分到不同的stage,从而实现的分布式和并发计算,大大提升了计算性能。

5、RDD算子

算子可以理解为方法函数,主要包括 转换算子 和 行动算子两类,在后续的文章中会针对这两类算子进行详细的介绍,这里只需理解概念。其中 转换算子 是 功能逻辑的封装,懒加载不执行。而行动算子是实际出发任务调度和作业执行,每一个行动算子就触发生成一个Job。

6、算子的执行端

简单理解,RDD 算子的执行端包扩两个部分,算子外部的操作是在Driver端,而内部的操作是发送到Executor 端执行。

7、RDD操作流程

RDD的操作流程可以四类:创建操作、转换操作、控制操作、行动操作。

创建操作:用于创建RDD,方式包括来自内存集合或外部存储,如 textFile("hdfs://xxx/word.txt");

        通过转换生成,如 makeRDD(1 to 5) 生成一个序列。

转换操作:将一个RDD通过一定的操作转换生成一个新的RDD,这个过程是惰性的,即只定义不执行。如:map(x=> x*2)

控制操作:是对RDD的持久化,按照不同的存储策略存储到不同的地方,如内存、磁盘等。

行动操作:触发Spark运行的操作,底层是调用环境对象的runJob放放风,创建一个Job并提交执行,并将结果保存成集合、变量 或 外部存储中。

案例:

val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("TestRDD")  
val sc = new SparkContext(conf) // 定义SparkContext 环境对象

val arr: RDD[Int] = sc.makeRDD(1 to 5)  // 创建操作,生成一个序列RDD
val mapRDD: RDD[Int] = arr.map(x => x * 2)  // 转换操作,对序列的每一个元素乘2
mapRDD.cache()                              // 控制操作,缓存到内存中,也可以存储到磁盘或者 checkpoint 持久化操作,这个在后面会详细讲解。
mapRDD.collect().foreach(println)  // 执行操作, 将结果打印
sc.stop()     // 关闭环境

8、RDD序列化

为了将对象数据在网络上传输,需要将其做转换。其中将对象数据转换二进制是序列化,将二进制对象数据恢复则是反序列化。在Spark中RDD有时需要引用外部对象或数据,所以需要序列化。当然Spark中还有其他一些需要序列化的场景如 Task 分发、 RDD缓存、Shuffle 过程等,有兴趣可以自行研究。

RDD的序列化只能使用Java 序列化器 和 Kryo序列化器。后续有时间再详细展开讲解。

9、RDD依赖关系

前面文章提到一个任务会被划分成多个Stage,每个Stage再被划分成多个task。这个过程就构成了一个DAG(有向无环图), 在DAG中,相邻的两个RDD的关系是依赖关系,多个连续的RDD 的依赖关系是血缘关系。每个RDD都会保存血缘关系,一旦出现错误,可以通过血缘关系将数据重新计算。

当一个RDD的分区被其下游的RDD的多个分区依赖,则这个关系就是宽依赖。

当一个RDD的分区只会被下游的RDD的一个分区依赖,这种关系叫窄依赖。

在DAG划分中,当存在宽依赖时,就会被划分出一个Stage,每个Stage在根据分区的不同划分成N个Task。执行过程中,碰到宽依赖,则需等待上一阶段计算完成才会进行下一阶段。而碰到窄依赖时,多个分区可以并行计算。

查看依赖关系可以通过 打印 RDD的 debug信息(toDebugString)或者 (dependencies)

val arr: RDD[Int] = sc.makeRDD(1 to 5)
val mapRDD: RDD[Int] = arr.map(x => x * 2)
mapRDD.cache()
mapRDD.collect().foreach(println)
println(mapRDD.toDebugString)

输出:

(4) MapPartitionsRDD[1] at map at SparkRDDDemo.scala:14 [Memory Deserialized 1x Replicated]
 |       CachedPartitions: 4; MemorySize: 96.0 B; DiskSize: 0.0 B
 |  ParallelCollectionRDD[0] at makeRDD at SparkRDDDemo.scala:13 [Memory Deserialized 1x Replicated]

10、持久化和CheckPoint

在实际的开发中会碰到 RDD的计算或转换比较耗时的节点,可将这些RDD持久化或在缓存便于下次使用,从而提升效率。

持久化方法包括: cache 默认, persist (可设置存储级别,包括 内存、磁盘、内存和磁盘以及多份等),在需要执行持久化的点 添加 如:

         xxrdd.cache()  或者  xxrdd.persist(StorageLevel.NONE)  等,便可实现。

Checkpoint:设置检查点,将数据落盘实现RDD的容错和高可用。需要先设置保存路径在执行。如:

        sc.setCheckpointDir("output")

        xxrdd.checkpoint()

需要注意的是,checkpoint 会重建血缘关系。如:

 

两者的区别:

通过对比可以发现,持久化在作业执行完后会丢失,而checkpoint 会涉及磁盘IO的开销,所以在实际生产中会将两者结合起来使用,即可以能提升性能又能保证数据的安全。 

11、RDD容错

通过之前的介绍,可以总结出对于RDD的容错分为三个层面。

调度层:利用重试达到容错,包括Stage 和 Task的重试。当Stage 输出出错时,上层调度器DAGScheduler 会进行重试。当Task输出出错时,上层调度器TaskScheduler 会进行重试。默认都是4次。 

血缘层:利用血缘关系进行恢复。当部分计算结果丢失时,会根据血缘关系恢复计算。窄依赖中,子RDD分区丢失,重算父RDD分区即可。宽依赖中,丢失一个子RDD分区,需要重算父RDD的所有分区,此时可能存在冗余计算开销。

Checkpoint层:利用检查点重做血缘关系达到容错。将RDD写入磁盘做检查点,如果之后的节点出现问题,则可以从检查点的RDD重做血缘关系,特别是在宽依赖上做checkpoint 可以避免重新计算带来的冗余计算开销。

二、累加器

1、需求场景

默认情况下,spark在集群的多个不同节点上并行运行一个函数时,会把函数中涉及到的每个变量在每个子任务中生成一个副本,但有时需要在多个任务中同时修改一个变量,此时就需要共享一个写变量。

2、说明

累加器就是这个分布式的共享只写变量。在Driver端定义的变量,会在Executor端的每一个task得到一个新的副本,task 更新后会回传到Driver 端,进行合并。

3、使用案例

求列表元素的和

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)  // 定义一个2分区的列表
//    var sum = 0
//    rdd.foreach(
//      num => {
//        sum += num
//      }
//    )
//    println(sum)
    // 获取系统累加器
    val sumAcc: LongAccumulator = sc.longAccumulator("sum")
    rdd.foreach(
      num => {
        sumAcc.add(num)
      }
    )
    println(sumAcc.value)

输出:10

说明:

        累加器一般用在行动算子中,如果出现在转换算子中,可能会出现少计算的现象,因为转换算子时懒加载,没有行动算子是不会执行。当然如果重复执行行动算子可能出现多次计算的情况,需要注意。

三、广播变量

1、需求场景

需求场景同累加器,只不过这个共享变量是只读变量。

2、说明

把变量在所有节点的内存中共享,在每个机器上缓存一份只读变量,而不是为每一个任务生成一个副本。可以用来高效的分发大对象。

3、使用案例

模拟两张表join

第一种方式(匹配模式):

    val rdd1: RDD[(String, Int)] = sc.makeRDD(List(
      ("a", 1), ("b", 2), ("c", 3)
    ))
    val rdd2: mutable.Map[String, Int] = mutable.Map(("a", 4), ("b", 5), ("d", 6))
    rdd1.map{
      case (word, count) =>{
        val i: Int = rdd2.getOrElse(word, 0)
        (word, (count, i))
      }
    }.collect().foreach(println)

第二种方式(广播方式):

    val rdd1: RDD[(String, Int)] = sc.makeRDD(List(
      ("a", 1), ("b", 2), ("c", 3)
    ))
    val rdd2: mutable.Map[String, Int] = mutable.Map(
      ("a", 4), ("b", 5), ("d", 6))
    val bc: Broadcast[mutable.Map[String, Int]] = sc.broadcast(rdd2)

    rdd1.map{
      case (word, count) =>{
        // 方法广播变量
        val i1: Int = bc.value.getOrElse(word, 0)
        (word, (count, i1))
      }
    }.collect().foreach(println)

自此关于Spark的三大数据结构已讲解完成。后续会针对RDD算子做进一步的说明。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spark Structured Streaming是一种基于Spark SQL引擎的流处理框架,它可以实现实时数据处理和分析。在使用Spark Structured Streaming进行大数据处理时,需要注意以下几点最佳实践: 1. 使用高可用性的集群:在使用Spark Structured Streaming时,需要保证集群的高可用性,以确保数据处理的稳定性和可靠性。 2. 选择合适的数据源:Spark Structured Streaming支持多种数据源,包括Kafka、Flume、HDFS等,需要根据实际情况选择合适的数据源。 3. 设计合理的数据处理流程:在设计数据处理流程时,需要考虑数据的实时性、处理效率和数据质量等因素,以确保数据处理的准确性和高效性。 4. 优化性能:在使用Spark Structured Streaming进行大数据处理时,需要优化性能,包括调整资源分配、调整并行度、使用缓存等方法,以提高数据处理的效率和速度。 5. 监控和调试:在使用Spark Structured Streaming进行大数据处理时,需要进行监控和调试,及时发现和解决问题,以确保数据处理的稳定性和可靠性。 ### 回答2: Spark Structured Streaming是一种用于实时流式数据处理的大数据最佳实践。它是Apache Spark的一部分,提供了一种简单而强大的方式来处理连续的数据流。 Spark Structured Streaming的实现原理基于Spark的弹性分布式数据集(RDD)模型,它将数据流视为一系列连续的批处理作业。这使得开发者能够使用Spark的强大功能进行实时数据分析和处理。 Spark Structured Streaming的关键特性包括: 1. 高级API:Structured Streaming提供了一种高级API,可以轻松地处理数据流。开发者只需编写一次数据处理逻辑,然后Spark会自动将其应用于连续的数据流。 2. 实时处理:Structured Streaming能够以低延迟处理数据流,提供近实时的结果。 3. 容错性:Structured Streaming具有Spark的容错机制,可以自动处理故障并继续处理数据流,确保数据不丢失。 4. 高吞吐量:Structured Streaming能够处理大规模的数据流,具有较高的吞吐量和扩展性。 5. 与其他Spark组件的集:Structured Streaming可以与其他Spark组件(如Spark SQL和MLlib)无缝集,从而可以方便地进行数据分析和机器学习任务。 在实践,可以使用Spark Structured Streaming来解决许多实时数据处理的问题。例如,可以使用它进行实时日志分析、实时监测和预测、实时推荐和广告投放等。 总而言之,Spark Structured Streaming是一种强大且灵活的实时数据处理解决方案,适用于大规模的数据流处理场景。它提供了简单的API和高性能的处理能力,为开发者提供了处理实时数据流的最佳实践。 ### 回答3: 大数据最佳实践Spark的Structuring是一种优秀的实时处理框架,它针对流数据进行高效的处理和分析。StructStreaming提供了一种简单、易于使用的API来处理结构化数据流,并支持强大的时间窗口操作、累加器和支持容错的状态更新。 Spark StructStreaming的最佳实践包括以下几个方面: 1. 数据流处理:结构化流可以处理各种实时数据源,如Kafka、Flume等,并且能够处理高吞吐量和低延迟数据。在处理数据流时,可以使用规约、窗口操作等技术来实现实时的数据计算和聚合。 2. 内存优化:Spark StructStreaming可以将数据缓存到内存以提高性能,并使用解析器和列式存储来最大限度地减少内存占用和I/O开销。此外,通过调整内存分配和堆外内存配置,可以进一步优化性能。 3. 容错性和数据一致性:StructStreaming提供了容错性和一致性保证,可以在节点故障时自动恢复数据处理,并确保最终输出的一致性。 4. 结构化数据查询:Spark StructStreaming支持基于SQL的查询语言,可以使用SQL语句对流数据进行查询和分析。此外,还可以使用DataFrame和DataSet API进行更高级的数据操作和转换。 5. 流式机器学习:StructStreaming可以与Spark的机器学习库集,实现基于流数据的实时机器学习模型训练和预测。通过结合流式数据和机器学习算法,可以实现实时的数据分析和预测。 总而言之,Spark StructStreamin的最佳实践是基于结构化数据流的实时处理和分析。它提供了简单易用的API和强大的功能,可以应用于各种大数据场景,并提供高性能、容错性和一致性保证。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值