SparkStreaming的调优tuning

本文深入探讨SparkStreaming的性能优化策略,包括减少每个批次处理时间、设置合理批次大小、内存调优及关键参数调整,旨在充分利用集群资源,实现数据处理与接收的均衡。

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

SparkStreaming之优化

除了Spark的常规调优(序列化、内存调整、RDD优化、缓存、共享变量等等)之外,SparkStreaming还有一些重要的调优手段,那么下面就开始介绍SparkStreaming应该从哪些方面进行优化

那么如何提高SparkStreaming应用程序的性能呢?作为一个Spark高手,你需要考虑2个方面

  • 充分的利用集群的资源来减少每个batch批次的数据的处理时间
  • 设置合适的批次大小,让所有批次的数据能在接收到之后能尽快的处理完(让数据的ingest于数据的processing保持均衡)

那么就从以下几个方面去下手完善优化吧!~~

1 减少每个batch批次的处理时间

每个批次的处理时间其实分为2部分,一个是接收数据所需要的时间,二一个是接收到数据之后处理数据所需要的时间,所以可以使用以下的方式去尝试优化。

1.1 接收数据的并行度

通过网络去接受数据要求数据被反序列化而且存储在Spark的内存中,如果接收数据成为了瓶颈,考虑并行接收数据,基于Receiver形式的input Stream 会创建一个单独的Receiver在Executor节点上,所以为了达到接收数据的并行化,我们可以添加Receiver的个数,打个比方,假如kafka有2个topic,用一个Receiver同时去接收来自kafka2个topic的数据,我们可以优化成用2个Receiver去分别接受不同topic的数据,这样增加吞吐量,提高效率,这些不同的input streams可以通过stream1.union(stream2)的方式进行合并

一、增加接受数据效率

具体伪代码如下:

//并行度为5
val kafkaStreams = (1 to numStreams).map { i => KafkaUtils.createStream(...) }
val unifiedStream = streamingContext.union(kafkaStreams)
unifiedStream.print()

二、参数调优

spark.streaming.blockInterval

对大多数Receiver而言,接收到的数据通过合并成一个Block之后然后才通过BlockManager存储在Spark的内存中,每个批次的blocks的个数决定了将来会被用来处理这些数据的tasks的数量(map-like的算子转换)

假如streaming的batch duration设置成 2s(2000ms)

但是block interval被设置成200ms,那么这个batch就会创建10个task去处理这些数据,如果tasks的数量太低,那么会造成集群资源使用不充分,效率低下,所以得设置合理的参数值去增加tasks的数量,官方建议最小的block interval为50ms,否则tasks的启动会成为天花板问题。

inputstream.repartition()//去重新设置处理数据的并行度

1.2 处理数据的并行度

park.default.parallelism

通过调整序列化的格式可以减少数据序列化的天花板问题,在streaming中,这里有2种数据会被序列化。

1.3 数据序列化

  • input输入数据
    • 默认,通过Receivers接收的数据会被以StorageLevel.MEMORY_AND_DISK_SER_2的存储级别存储在executor的jvm内存中,数据被序列化成字节从而减少GC次数,2个备份副本保证容错机制,而且数据首先是存在内存中,只有内存不够用才溢写到磁盘,这里很明显会有性能问题-receive必须先反序列化数据然后再按照指定的序列化方式进行存储,所以kryo可以实现优化,减少CPU性能消耗
  • 在Streaming中的被persisted的RDD数据
    • 在流式计算中的RDDs可能会被持久化在内存中,比如,窗口操作会默认将RDD的数据存储子在内存中保证他们有些数据可能会被重新处理,与Spark core(``StorageLevel.MEMORY_ONLY)不同的是,streaming计算中的RDD数据会被以StorageLevel.MEMORY_ONLY_SER`去减少GC消耗

在这以上的案例中,通过Kryo序列化可以减少CPU和内存的消耗,对于Kryo,可以考虑:

1、给自定义类注册kryo序列化;

2、通过配置来禁止对象引用跟踪配置

note:如果没有窗口操作而且批次时间比较短只有几秒钟,可以禁用持久化数据中的序列化,具体可以参考序列化配置

2 设置正确的批次间隔

为了让Spark Streaming程序稳定地运行在一个集群上,这个系统应该保证一旦接收到数据就能够尽快的处理掉这些数据,换句话说就是批次的数据生成后应尽可能快地被处理掉,我们可以在Spark的WebUI监控界面验证流式处理系统是否足够稳定,主要判断依据是processing times,这个processing time of the batch应该小于batch interval

根据流式处理的特性,batch interval可能会对数据的在一个集群中所需要的processing time产生重大的影响,举个栗子:在spark项目example目录下的WordCountNetWord,系统能够保持每2s报告一个计数的值,而不是500ms,所以batch interval应该被设置成2s。

2.1 怎么找出你的程序的合适的batch interval

我们可以用一个保守的时间区间段(5-10s)的批次时间&较低的数据生成率,要验证系统是否能跟上数据产生的速率?

可以检查每个已处理批次的end-to-end的值(可以在Spark的Driver程序的log4j日志中找到Total dealy,也可以使用 StreamingListener接口里面的方法)如果这个批次的延迟时间与batch size相比很稳定,那么这个系统就是稳定的,否则,如果延迟持续上升,那么这个系统不能维持批次数据的处理,系统也是不稳定的。

NOTE:如果延迟的增加只是临时短暂的,而且延迟又会回到较低的状态,那么系统也可能是稳定的。

一旦你有一个关于稳定的配置的idea,你能尝试增加数据的生成速率 and/or 减少batch interval

3 内存调优

之前在SparkCore的tuning中我们也提到了内存调优的方法,但是除了那些内存调优之外,我们这里还讨论一些通过SparkStreaming的上下文环境通过参数调优的方法

SparkStreaming应用程序所需要的集群内存很大程度上取决于所使用的转换类型,例如,如果需要对每10min的数据作窗口操作,则集群应具有足够的内存将10分钟的数据保存在内存中,或者当你要使用updateStateByKey用到大量的key,则此时需要的内存会很大,相反,假如你仅仅作一个map-filter-store操作,则所需要的内存量就非常小。

3.1 submit的时候增加Executor的内存

通常通过接收器接收的数据以StorageLevel.MEMORY_AND_DISK_SER_2来保证数据的容错性,因此当内存无法容纳时会将数据溢写到磁盘,由于大量的磁盘IO会导致流式应用的性能降低,因此建议根据streaming application的要求提供足够的内存,最好尝试小规模查看内存使用情况并据此进行估计。

3.2 JVM GC的优化

内存调整的另一个方面是Garbage Collect,对于低延迟的流式应用,不希望因为JVM的垃圾回收引起大量的停顿。以下参数可以调整内存的使用和GC的回收

  • DStream的 persist level
    • 如前面的序列化所述,默认情况下,输入数据和RDD被持久化成序列化字节。与反序列化的持久性相比,这减少了了内存使用和GC开销,启用Kryo可以进一步减少序列化的大小及内存使用量。通过压缩可以进一步减少内存使用(spark.rdd.compress),但是这还会占用CPU的时间,典型的时间换空间。
  • 清除旧数据
    • 默认情况下,将自动清除DStream转换生成的所有输入数据和持久化的RDD。spark streaming根据使用的转换来决定合适清除数据。例如,你使用10分钟的窗口,那么Spark streaming将保留最后10分钟的数据,并主动丢弃之前的旧数据,通过设置可以将数据保留更长的时间,streamingContext.remember。
  • CMS GC处理器
    • 强烈建议使用并发的mark-and-sweepGC算法,始终让GC相关的暂停时间保持较低,尽管已经知道兵法GC会降低系统的整体处理的吞吐量,但是仍然建议使用并发GC以实现更一致的批处理时间,确保在Driver程序(spark-submit --driver-java-options中设置)&executor中(spark.executor.extraJavaOptions)上都设置了CMS GC。
  • 其他GC提示
    • 使用off_heap存储级别持久化RDD。
    • 使用100个10G的executors 代替使用10个100G的executors,减少每次JVM中heap GC的压力

4 重要的重要的要点!!!!!

  1. 一个DStream只伴随着一个Receiver,一个Receiver运行在一个Executor上并占用一个core,预定Receiver的slot之后,请确有足够的cores用于processing,Receiver以round robbin的方式分配数据给executor,可以根据Receiver的slot数设置spark.cores.max
  1. 当从数据源接收数据时,Receiver会创建数据块,每一个blockinterval生成一个block通过BlockManager进行管理。这些Block通过BlockManager分发给其他的Executor的执行器的BlockManager,BlockInterval==BatchInterval的时候意味着只会创建一个分区,减少了网络IO,并且达到数据的本地化处理。
  2. 块上的映射任务在执行器中执行(一个执行器接收该块,另一个执行器复制该块),该执行器具有与块间隔无关的块,除非执行非本地调度。除非间隔时间间隔越大,则块越大。较高的值会spark.locality.wait增加在本地节点上处理块的机会。需要在这两个参数之间找到平衡,以确保较大的块在本地处理。
  3. 无需依赖batchInterval和blockInterval,您可以通过调用来定义分区数inputDstream.repartition(n)。这会随机重新随机排列RDD中的数据以创建n个分区。是的,以获得更大的并行度。虽然以洗牌为代价。RDD的处理由驾驶员的Jobscheduler安排为工作。在给定的时间点,只有一项作业处于活动状态。因此,如果一个作业正在执行,则其他作业将排队。
  4. 如果您有两个dstream,则将形成两个RDD,并且将创建两个作业,这些作业将一个接一个地调度。为避免这种情况,可以合并两个dstream。这将确保为dstream的两个RDD形成单个unionRDD。然后将此unionRDD视为一项工作。但是,RDD的分区不受影响。
  5. 如果批处理时间超过batchinterval,那么显然接收方的内存将开始填满,并最终引发异常(最有可能是BlockNotFoundException)。当前,无法暂停接收器。使用SparkConf配置spark.streaming.receiver.maxRate,可以限制接收器的速率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值