SparkStreaming性能调优

一、数据接收并行度调优——创建更多的输入DStream和Receiver

通过网络接收数据时(比如Kafka,Flume),会将数据反序列化,并存储在Spark的内存中。如果数据接收成为系统的瓶颈,可以考虑并行化数据接收。每个输入DStream都会在某个Worker的Executor上启动一个Receiver,该Receiver接收一个数据流。因此可以通过创建多个输入DStream,并配置它们接收数据源不同的分区数据,达到接收多个数据流的效果。

比如,一个接收两个Kafka Topic的输入DStream,可以拆分成两个输入DStream,每个分别接收一个topic的数据。这样就会创建两个Receiver,从而并行地接收数据,提高吞吐量。多个DStream可以使用union算子进行合并,从而形成一个DStream。后续的算子操作只需要针对合并之后的DSream即可。

代码示例:

int numStreams = 5;

List<JavaPairDStream<String, String>> kafkaStreams = new ArrayList<JavaPairDStream<String, String>>(numStreams);

for (int i = 0; i < numStreams; i++) {

  kafkaStreams.add(KafkaUtils.createStream(...));

}

JavaPairDStream<String, String> unifiedDStream = streamingContext.union(kafkaStreams.get(0), kafkaStreams.subList(1, kafkaStreams.size()));

unifiedDStream.print();

 

二、数据接收并行度调优——调节block interval

数据接收并行度调优,除了创建更多输入DStream和Receiver以外,还可以调节block interval。通过参数spark.streaming.blockInterval,可以设置block interval,默认是200ms。

对于大多数Receiver而言,在将接收到的数据保存到Spark的BlockManager之前,都会将数据切分成一个一个的block。每个batch的block数量,决定了该batch对应的RDD的partition的数量,以及针对该RDD执行transformation操作时创建的task数量。每个batch对应的task的数量可以大约估算出来,即batch interval / block interval。

比如,batch interval为1s,block interval为100ms,则会创建10个task。如果每个batch的task数量太少,即低于每台机器的CPU Core,说明batch的task数量偏少,导致所有的CPU资源没有被完全利用起来。此时应该为batch增加block的数量,需要减小block interval。

但是,需要注意的是,推荐的block interval的最小值为50ms,如果低于这个值,那么大量的task的启动时间可能会变成性能的一个开销。

 

三、数据接收并行度调优——输入流数据重分区

使用inputStream.repartition(<number of partitions>),将接收到的batch,分不到指定数量的机器上,然后进行后续操作。

 

四、任务启动调度

如果每秒钟启动的task过多,比如每秒启动50个,100个,那么发送这些task去Worker节点上的Executor的性能开销将会大大增加,可以使用下述操作减少这方面的性能开销:

  1. Task序列化:使用Kryo序列化机制来序列化task,减小task的大小,从而减少发送这些task到各个Worker节点上的Executor的时间
  2. 执行模式:在Standalone模式下运行Spark,可以达到更少的task启动时间

 

五、数据处理并行度调优

如果在计算的任何stage中使用的并行task的数量没有足够多,那么集群资源是无法被充分利用的。

举例来说,对于分布式的reduce操作,比如reduceByKey和reduceByKeyAndWindow,默认的并行task的数量是由spark.default.parallelism参数决定的。也可以在reduceByKey等操作中,传入第二个参数,手动指定该操作的并行度,也可以调节全局的spark.default.parallelism参数。

 

六、数据序列化调优

数据序列化造成的系统开销可以由序列化格式的优化来减小。在流式计算的场景下,由两种类型的数据需要优化:

  1. 输入数据:默认情况下,接收到的输入数据,是存储在Executor的内存中的,使用的持久化级别是StorageLevel.MEMORY_AND_SER_2。这意味着,数据被序列化为字节流从而减小GC开销,并且会复制以进行executor失败的容错。因此,数据首先会存储在内存中,然后在内存不足时会溢写到磁盘上,从而为流式计算来保存所有需要的数据。这里的序列化有明显的性能开销——Receiver必须反序列化从网络接收到的数据,然后再使用Spark的序列化格式序列化数据。
  2. 流式计算操作生成的持久化RDD:流式计算操作生成的持久化RDD,可能会持久化到内存中。例如,窗口操作默认就会将数据持久化在内存中,因为这些数据后面可能会在多个窗口中使用,并被处理多次。然而,不像Spark Core的默认持久化级别,StorageLevel.MEMORY_ONLY,流式计算操作生成的RDD的默认持久化级别是StorageLevel.MEMORY_ONLY_SER,默认就会减小GC开销。

在上述的两个场景中,使用Kyro序列化类库可以减小CPU和内存的性能开销。使用Kyro时,一定要考虑注册自定义的类,并且禁用对应引用的tracking(spark.kyro.referenceTracking)。

在一些特殊的场景中,比如需要为流式应用保持的数据总量并不是很多,也许可以将数据以非序列化的方式进行持久化,从而减少序列化和反序列化的CPOU开销,而且又不会有太昂贵的GC开销。举例来说,如果设置的batch interval,并且没有使用window操作,那么可以通过显式地设置持久化级别,来禁止持久化对数据进行序列化。这样就可以减少用于序列化和反序列化的CPU性能开销,并且不用承担太多的GC开销。

 

七、batch interval调优(最重要)

如果想让一个运行在集群上的Spark Streaming应用程序可以稳定,就必须尽可能快地处理接收到的数据。换句话说,batch应该在生成之后,尽可能快地处理掉。对于一个应用来说,可以通过观察Spark UI上的batch处理时间来判断batch interval的设置是否合适。batch处理的时间必须小于等于batch interval的值。

给予流式计算的本质,在固定集群资源条件下,应用能保持的数据接收速率,batch interval的设置会有巨大的影响。例如,在WordCount例子中,对于一个特定的数据接收速率,应用业务可以保证每2秒打印一次单词计数,而不是每500ms。因此batch interval需要设置,让预期的数据接收速率可以在生产环境中保持住。

为应用计算合适的batch大小,比较好的方法是先设置一个很保守的batch interval,比如5s~10s,以很慢的数据接收速率进行测试。要检查应用是否跟得上这个数据速率,可以检查每个batch的处理时间的延迟,如果处理时间与batch interval基本吻合,那么应用就是稳定的。否则,如果batch调度的延迟持续增长,那么久意味着应用无法跟得上这个速率,就是不稳定的。此时可以提升数据处理的速度,或者增加batch interval,以保证应用的稳定。

注意,由于临时性的数据增长导致的暂时的延迟增长是合理的,只要延迟情况可以在短时间内回复即可。

 

八、内存调优——内存资源

Spark Streaming应用需要的集群内存资源,是由使用的transformation操作类型决定的。举例来说,如果想要使用一个窗口长度为10分钟的window操作,那么集群就必须有足够的内存来保存10分钟内的数据。如果想要使用uodateStateByKey来维护许多key的state,那么内存资源就必须足够大。反过来说,如果想要做一个简单的map-filter-store操作,那么需要使用的内存就很少。

通常来说,通过Receiver接收到的数据,会使用StorageLevel.MEMPRY_AND_DISK_SER_2持久化级别来进行存储,因此无法保存在内存中的数据就会溢写到磁盘上。而溢写到磁盘上,会降低应用的性能。因此,通常的建议是为应用提供它需要的足够的内存资源。

(建议在一个小规模的场景下测试内存的使用量,并进行评估)

 

九、内存调优——垃圾回收

内存调优的另一个方面是垃圾回收。对于流式应用来说,如果要获得低延迟,肯定不能有因为JVM垃圾回收导致的长时间延迟。有很多参数可以帮助降低内存使用和GC开销:

  1. DStream的持久化:正如在“数据序列化调优”一节中提到的,输入数据和某些操作产生的中间RDD,默认持久化时都会序列化为字节。与非序列化的方式相比,这会降低内存和GC开销。使用Kyro序列化机制可以进一步减少内存使用和GC开销。进一步降低内存使用率,可以对数据进行压缩,由spark.rdd.compress参数控制(默认false)
  2. 清理旧数据:默认情况下,所有输入数据和通过DStream transformation操作生成的持久化RDD,会自动被清理。Spark Streaming会决定何时清理这些数据,取决于transformation操作类型。例如,在使用窗口长度为10分钟的window操作,Spark会保持10分钟以内的数据,时间过了以后就会清理旧数据。但是在某些特定的场景下,比如Spark SQL和Spark Streaming整合使用时,在异步开启的线程中,使用Spark SQL针对batch RDD进行执行查询。那么就需要让Spark保存更长时间的数据,直到Spark SQL查询结束。可以使用streamingContext.remember()方法来实现。
  3. CMS垃圾回收器:使用并行化的mark-sweep垃圾回收机制,被推荐使用,用来保持GC低开销。虽然并行的GC会降低吞吐量,但是还是建议使用它,来减少batch的处理时间(降低处理过程中的gc开销)。如果要使用,那么要在driver端和executor端都开启。在spark-submit中使用--driver-java-options设置;使用spark.executor.extraJavaOptions参数设置。-XX:+UseConcMarkSweppGC。
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值