Spark学习笔记:Spark Streaming数据存储与调优

目录

 

Spark Streaming数据存储与调优

一.缓存与持久化机制

二.Checkpoint机制

三.部署、升级和监控应用程序

1.部署应用程序

2.升级应用程序

3.监控应用程序

4.容错机制以及事务语义详解

5.Spark Steraming架构原理

6.性能调优


Spark Streaming数据存储与调优

一.缓存与持久化机制

与RDD类似,Spark Streaming也可以让开发人员手动控制,将数据流中的数据持久化到内存中,对DStream调用persist()方法,就可以让Spark Streaming自动将该数据流中的所有产生的RDD都持久化到内存中。如果要对一个DStream多次执行操作,那么,对DSteram持久化是非常有用的。因为多次操作,可以共享使用内存中的一份缓存数据。

对于基础窗口的操作,比如reduceByWindow、reduceByKeyAndWindow,以及基于状态的操作,比如updateStateByKey,默认就隐式开启了持久化机制,即Spark Streaming默认就会将上述操作产生的DStream中的数据,缓存到内存中,不需要开发人员手动调用persist()方法。

对于通过网络接收数据的输入流,比如Socket、Kafka、Flume等,默认的持久化级别是将数据复制一份,以便于容错,相当于用的是MEMORY_ONLY_SER_2。

与Spark Core中的RDD不同的是,默认的持久化级别,统一都是要序列化的。

二.Checkpoint机制

每一个Spark Streaming应用,正常来说都是要7x24小时运转的,这就是实时计算程序的特点。要持续不断的对数据进行计算,必须要能够对于应用程序逻辑无关的失败进行容错。

对于一些将多个batch的数据进行聚合的,有状态的transformation操作,这是非常有用的。在这种transformation操作中,生成的RDD是依赖之前的batch中的RDD的,这样就会随着时间的推移,依赖链条越来越长,从而导致失败恢复时间也变得越来越差。有状态的transformation操作执行过程当中产生的RDD要定期的被checkpoint到可靠的存储上,这样做可以消减RDD的依赖链条,从而缩短恢复时间。

当使用了有状态的transformation操作时,必须要开启checkpoint机制,提供checkpoint目录。

注意,并不是所有的Spark Streaming应用程序都要启用checkpoint机制

如何启用Checkpoint机制

  • 配置一个文件系统(比如HDFS)的目录,作为checkpoint目录
  • 使用StreamingContext的checkpoint方法,填入配置好的目录作为参数即可

三.部署、升级和监控应用程序

1.部署应用程序

  • 需要有一个集群资源管理器,比如standalone模式下的Spark集群,Yarn模式下的Yarn集群等
  • 打包应用程序为一个jar包
  • 为Executor配置充足的内存,比如如果你要执行窗口长度为10分钟的窗口操作,那么Executor的内存资源就必须足够保存10分钟内的数据
  • 配置checkpoint

2.升级应用程序

  • 如果能够允许多个客户端读取各自独立的数据,也就是读取相同数据的时候,可以直接启动升级后的Spark应用程序,先与旧的Spark应用程序并行执行,确保新的程序没有问题之后,再将旧的程序停掉
  • 如果是支持缓存的数据源(如Flume,Kafka等),可以用stop方法先关闭已经在运行的应用程序,然后将升级后的程序部署上去,再启动,这样不会有数据的丢失,如果是不支持缓存的数据源,就会导致数据的丢失

3.监控应用程序

通过Spark Web UI显示相关信息

Spark UI中,以下两个统计指标格外重要

  • 处理时间:每个batch的数据的处理耗时
  • 调度延迟:一个batch在队列中阻塞住,等待上一个batch完成处理的时间

4.容错机制以及事务语义详解

容错机制

  • RDD是一个弹性分布式数据集,是不可变的,确定的,可重新计算的,每个RDD都会记住确定好的计算操作的血缘关系,这些操作应用在一个容错的数据集上来创建RDD,那么如果因为某个Worker节点的失败,导致RDD的某个partition数据丢失,那么这个partition可以通过原始的容错的数据集应用操作血缘来重新计算出来,所以的RDD的transformation操作都是确定的,最后计算出的那个RDD的数据一定是不会因为Spark集群的失败而丢失的。Spark的操作通常是容错文件系统中的数据,比如HDFS,所以所有通过容错数据生成的RDD也是容错的,但是对于Spark Streaming来说是行不通的,因为在大多数情况下数据都是通过网络接收的,那么要让Spark Streaming程序生成的RDD都达到普通Spark程序相同的容错性,接收到的数据必须被复制到多个Worker节点上的Executor内存中,默认的复制因子是2。
  • 在出现失败的事件时,有两种数据需要被恢复:
  • (1)数据接收到了,并且已经复制过——这种数据在一个Worker节点挂掉后,是可以继续存活的,因为在其他Worker节点上,还有它的一份副本
  • (2)数据接收到了,但是正在缓存中,等待复制的——因为还没有复制该数据,因此恢复它的唯一办法就是重新从数据源获取一份

容错语义的定义

  • 流式计算系统的容错语义,通常是以一条记录能够被处理多少次来衡量的
  • 有三种类型的语义可以提供:
  • (1)最多一次:每条记录可能会被处理一次,或者根本就不会被处理。可能有数据丢失
  • (2)至少一次:每条记录会被处理一次或多次,这种语义比最多一次要强,因为它确保零数据丢失,但是可能会导致记录被重复处理几次。
  • (3)一次且仅一次:每条记录只会被处理一次——没有数据会丢失,并且没有数据会处理多次。这是最强的一种容错语义

基础容错语义

如果应用程序要求必须有一次且仅一次的语义,每条数据都得保证,只能接收一次、只能计算一次、只能推送一次

Spark Streaming中实现这些语义的步骤如下

  • 接收数据:不同的数据源提供不同的语义保障,使用receiver或者其他方式接收数据
  • 计算数据:使用DStream离散流的transformation操作对数据进行计算和处理,所有接收到的数据一定只会被计算一次,这是基于RDD的基础语义所保障的,就算有失败,只要接收到的数据还是可访问的,最后计算出来的数据一定是相同的
  • 推送数据:output操作默认能确保至少一次的语义,但是用户可以实现它们自己的事务机制来确保一次且仅一次的语义,最后计算出来的数据会被推送到外部系统,比如文件系统,数据库等

接收数据的容错语义

  • 基于文件的数据源:如果所有的数据都在一个容错的数据源(如HDFS),Spark Streaming一定可以从失败中恢复,并且处理数据,那么这样就提供了一次且仅一次的语义,意味着所有数据只会处理一次
  • 基于Receiver的数据源
  • (1)可靠的Receiver:在接收到了数据,并且将数据复制之后,对数据源执行确认操作,数据不会丢失,如果Receiver在数据接收和复制完成之前就失败了,那么数据源对缓存数据会接收不到确认,那么这个时候,数据源会重新发送数据,从而避免数据丢失,比如Kafka。
  • (2)不可靠的Receiver:不会发送确认操作,当Worker节点失败的时候,可能会导致数据丢失

为了避免所有数据都丢失的这种问题,Spark从1.2版本开始就引入了预写日志的机制,将Receiver中接收到的数据保存到容错存储中,如果使用了可靠的Receiver并开启了预写日志机制,那么可以保证数据0丢失,这种情况会提供至少一次的保障

输出数据的容错语义

  • output操作,比如foreachRDD,可以提供至少一次的语义,也就是说,当Worker节点失败的时候,转换后的数据可能会被写入外部系统一次或多次
  • 要真正获得一次且仅一次的语义,有两个方法:
  • (1)幂等更新:多次写操作,都是写相同的数据
  • (2)事务更新:所有的操作都应该做成事务,从而让写入操作执行一次且仅一次
dstream.foreachRDD { (rdd, time)=>
  rdd.foreachPartition { partitionIterator => 
    val partitionId = TaskContext.get.partitionId()
    val uniqueId = generateUniqueId(time.milliseconds,partitionId)
    //partitionId和foreachRDD传入的时间,可以构成一个唯一的标识
  }
}

5.Spark Steraming架构原理

Spark Streaming相对其他流处理系统最大的优势,在于流处理引擎和数据处理在同一个软件栈

  • Spark Streaming功能主要包括流处理引擎的流数据接收与存储以及批处理作业的生成与管理
  • Spark Core负责处理Spark Streaming发送过来的作业

Spark Streaming进行流数据处理大致可分为4步

  • 启动流处理引擎
  • 接收及存储流数据
  • 处理流数据
  • 输出处理结果

6.性能调优

数据接收并行度调优

  • 创建多个DStream和Receiver

       如果通过网络接收数据时,比如Kafka,Flume,它会将数据进行反序列化,并且存储在Spark的内存中,如果接收成为系统的瓶颈,那么可以考虑并行化数据接收。每一个输入的DStream都会在某个worker启动的Executor上启动一个Receiver,这个Receiver可以接收一个数据流,可以通过创建多个输入DStream和Receiver并且配置他们接收数据源不同的分区数据,达到接收多个数据流的效果。比如说一个接受两个Kafka topic的DStream,我们可以拆分为两个输入DStream,那么每个DStream分别接收一个topic的数据,这样就可以创建两个Receiver,并行的接收数据,可以提升吞吐量。多个DStream可以利用Union这个算子进行聚合,形成一个DSteram,然后后续的transformation算子操作都可以针对这一个聚合后的DStream进行计算。

  • 调节Block interval,通过参数spark.streaming.blockInterval,可以设置block interval, 默认是200ms

      对于大多数的Receiver来说,将接收的数据保存到Spark的Block Manager之前,都会将数据切分为一个个的block,每个batch中的block数量就决定了这个batch对应的RDD的partition数量,和针对这个RDD执行的transformation时创建的task的数量。那么每个batch中的task数量是可以估计的,通过batch interval除以block interval就可以进行估计了。如果认为每个batch的task数量太少,低于每台机器的Spark Core数量,那就说明task数量是不够的,因为所有的CPU资源无法被完全利用起来,这个时候要为batch增加block数量,就可以去减少block interval,官方推荐的block interval最小值是50ms,如果低于这个值,那么大量的task启动时间可能会变成性能的开销点

数据处理并行度调优

如果每秒钟启动的task数量太多,那么发送task去worker节点的Executor的性能开销会比较大,而且这个时候就很难达到毫秒级的延迟了,那么可以通过以下两个方面来减小这方面的开销:

  • task任务序列化
  • 如果在计算的任何stage,使用了并行task的数量没有足够多,那么集群的资源是无法被充分利用的,举个例子,对于分布式的reduce操作,比如reduceByKey、reduceByKeyAndWindow默认的并行task数量是由spark.default.parallelism参数决定的,因此可以在使用reduceByKey等算子进行操作时,传入第二个参数,手动指定该操作的并行度,也可以调节全局的spark.default.parallelism参数

数据序列化调优

  • 数据序列化造成的系统开销可以由序列化格式的优化来减小
  • 使用Kryo序列化类库可以减小CPU和内存的性能开销
  • 在流式计算的场景下有两种类型的数据需要序列化,第一种是输入数据,在默认情况下,接收到的输入数据是存储在Executor的内存中的,使用的持久化级别是MEMORY_AND_DISK_SER2,这意味着数据被序列化为字节从而减小开销,并且会进行复制来作为Executor失败的容错,所以说数据会先存储到内存中,当内存不足的时候会写到磁盘上,这样可以为流式计算来存储所有需要的数据,这里的序列化有明显的性能开销,比如说Receiver必须反序列化从网络接收到的数据,然后再用Spark的序列化格式序列化数据。
  • 第二种,是流式计算操作生成的持久RDD,可能会被持久到内存中,比如窗口操作默认就会将数据持久化到内存中,因为这些数据可能会在多个窗口中被使用,并且被处理多次。但是不像Spark Core的默认持久化级别是MEMORY_ONLY,流式计算生成的RDD默认持久化级别是MEMORY_ONLY_SER,这样就会减小GC开销,同时我们还可以使用Kryo序列化类库来减小CPU和内存的性能开销。在一些特殊的场景当中,比如需要为流式应用保持的数据总量并不是很多,那么就可以以非序列化的方式持久化,这样可以减小序列化和反序列化时CPU的开销,只需要显式指定持久化级别即可。

batch interval调优

  • 如果想让一个运行在集群上的Spark Streaming应用程序可以稳定,那就必须要尽可能多的处理接收到的数据,那么换句话说就是batch在生成之后就尽可能快的处理掉。对于应用来说这是否会成为问题,可以通过观察Spark UI上的batch处理时间来定,batch处理时间必须要小于batch interval的时间,基于流式计算的本质,batch interval对于在固定集群资源条件下,应用能保持的数据接收速率,会有巨大的影响。如果要检查应用是否跟得上数据传输的速率,可以检查每个batch的处理时间的延迟,如果处理时间与batch interval基本吻合,那么应用就是稳定的。如果batch调度的延迟持续增长,那么说明这个应用无法跟得上这个速率,也就是不稳定的。如果想要一个稳定的配置,可以尝试提升数据处理的速度或者增加batch interval。注意,由于临时性的数据增长导致的暂时的延迟增长可以是合理的,只要在短时间内可以恢复就可以。

内存调优

  • 通过内存调优来降低内存使用和GC开销,Spark Streaming应用需要的集群内存资源是通过使用的transformation操作类型来确定的,比如想使用一个窗口长度为10分钟的window操作,那么集群就必须要有内存资源来保存这10分钟的数据。如果内存不够存储,是会溢写到磁盘上的,这样会降低应用的性能,所以要为应用提供足够的内存资源。对于流式应用来说,如果要获得低延迟,肯定不想要有因为JVM垃圾回收而导致的长时间延迟。
  • (1)DStream的持久化。输入数据和一些操作中产生的中间RDD,它的默认持久化都会序列化为字节,与非序列化的方式相比,这样会降低内存和GC的开销,可以使用Kryo序列化机制或者对数据进行压缩(由spark.rdd.compress参数控制)进一步降低内存使用
  • (2)清理旧数据。所有输入数据和输入DStream transformation生成的持久化RDD都会自动的被清理,什么时候清理取决于操作类型。但是在一些特定场景下,比如Spark SQL和Spark Streaming整合使用时,在异步开启的线程中,使用Spark SQL对batch RDD执行查询,那么就需要让Spark保存更长时间的数据,直到Spark SQL查询结束,可以使用StreamingContext.remember()方法来实现
  • (3)CMS垃圾回收期。使用并行的mark-sweep垃圾回收机制,用来保持GC低开销,虽然并行的GC会降低吞吐量,但还是推荐使用它来减少batch的处理时间

猜你喜欢

转载自blog.csdn.net/lrxcmwy2/article/details/82778432
今日推荐