定义
在SparkStreaming
应用程序中有很多能够优化的地方,这样的优化可以提高应用的运行效率。
减少批处理的时间
SparkStreaming
的优化可以大大提高每个批次的处理时间,每个批次处理其实就是操作RDD
,那么怎么样才能缩短操作RDD
的时间呢,那我们就可以参考Spark Core(十九)Spark性能的调优来尽可能的减少批处理的时间。
增加接收数据的并行度
应用程序在处理网络传输过来的数据时候需要反序列化后存储在Spark
内存中,如果数据的接受成为了应用程序性能的瓶颈,我们可以增加接受数据的并行性。默认情况下一个
只对应一个Receiver
接收器,那么我们就可以为一个DStream
增加多个Receiver
接收器,因为一个接收器只会消费一个分区上得数据,这样我们提高并行性其实也就是同时消费了多个分区上的数据。
package com.lyz.streaming.datasource.advance
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.TopicPartition
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
import org.apache.spark.streaming.kafka010.KafkaUtils
import org.apache.spark.streaming.kafka010.LocationStrategies.PreferFixed
import scala.collection.immutable
object anyReceiverTest {
def main(args: Array[String]): Unit = {
val kafkaParams = Map[String, Object](
"bootstrap.servers" -> "localhost:9092", //指定Kafka的集群地址
"key.deserialize" -> classOf[StringDeserializer], //指定key的反序列化器
"value.deserialize" -> classOf[StringDeserializer], //指定值的反序列化器
"group.id" -> "", //consumer的分组id
"auto.offset.reset" -> "latest", //从新定义消费者以后,不从头消费分区里的数据,只消费定义消费者以后的数据
"enable.auto.commit" -> (false: java.lang.Boolean) //是否自动提交offsets,也就是更新kafka里的offset,表示已经被消费过了
)
//定义消费主题topic
val topics = Array("topic1", "topic2")
val sparkConf: SparkConf = new SparkConf().setMaster("local[3]").setAppName("KafkaDatasourceTest")
val streamingContext: StreamingContext = new StreamingContext(sparkConf, Seconds(2))
//禁用消费者预先缓存分区上的消息
sparkConf.set("spark.streaming.kafka.consumer.cahce.enable", "false")
//设置接受kafka数据的并行度
val numReceiver = 5
//利用KafkaUtils创建多个接收器
val kafkaStreams: immutable.IndexedSeq[InputDStream[ConsumerRecord[String, String]]] = (1 to numReceiver).map(i => {
/**
* PreferConsistent:将分区分布在所有的Excutor上,也就是每Executor都会消费Kafka数据
* PreferBrokers:只有当执行程序与Kafka代理程序位于相同的节点时,才可以使用。
* Subscribe:传入具体的kafka参数
*/
KafkaUtils.createDirectStream[String, String](
streamingContext,
PreferFixed(Map[TopicPartition, String]()),
Subscribe[String, String](topics, kafkaParams))
})
· //合并多个接收器
val stream: DStream[ConsumerRecord[String, String]] = streamingContext.union(kafkaStreams)
}
}
合理设置接收器接受数据的块间隔
当接收器接受数据以后会合并成数据块然后再存储到Spark
内存中,既然是将数据合并成数据块,那么多久合并一次呢,这里就引入了一个配置参数叫块间隔,由spark.streaming.blockInterval
设置。每个批次的数据块数量决定了程序处理的任务数(批处理间隔时间/块间隔),我们都知道计算任务的并行度是根据Executor
上的Core
来决定的,如果任务数量小于Core
数量,那么就需要缩短块间隔,提高任务的并行度。需要注意的是块间隔建议最小值为50ms
,不建议低于这个值。
数据处理的并行度
如果在程序的任何阶段使用的任务数量不高,可能造成资源的不充分利用,我们可以设置spark.default.parallesim
参数设置任务的并行度。
数据系列化
- 数据序列化,通过
Receiver
接受到的数据以StorageLevel.MEMORY_AND_SER2
方式进行持久化的,也就是说先对数据进行序列化,这样减少了GC
的开销,然后缓存到内存中,当内存达到了最大缓存量,就把剩余的序列化数据溢写到磁盘中。这种序列化方式显然性能收到很大的影响,因为我们在处理缓存数据的时候需要对其进行反序列化。 - 流式处理得到需要持久化的
RDD
,在流式计算中,产生的中间RDD
会对其进行持久化,而与SparkCore
中持久化RDD
的级别StorageLeval.MEMORY_ONLY
不同,流式计算持久化RDD
的级别是StorageLeval.MEMORY.ONLY.SER
,而是对RDD
进行序列化以减少GC
的开销。
对于以上两种情况我们都可以使用Kryo
序列化器来介绍CPU
和内存的开销。使用Kryo
序列化器详解请点击该链接Kryo序列化器配置
正确设置批处理时间间隔
SparkStreaming
按时间间隔来进行批次的处理,如果想要保持系统的稳定性,那么处理数据的速度要跟的上接受数据的速度。换句话说就是数据的处理速度要小于数据接受的速度。
确定应用程序稳定的批处理的时间间隔应该是保守的,我们可以查看spark
中log4j
日志里的总延迟,如果延迟在允许范围内系统就是稳定的,如果延迟过高,那么就需要进行反复测试进行最优的时间间隔。
内存调优
- 调优指南Spark应用程序调优
- 中已经详细的介绍了
Spark
应用程序的调优。这里我们针对SparkStreaming
的应用程序进行调优的介绍。 SparKStreaming
应用程序消耗内存大小取决于数据的转换类型,例如窗口操作那么就需要根据窗口的小来确定内存的大小,如果用到的updateStateByKey
函数,那么就就需要更大的内存。- 由于接受到的数据的持久化方式为
StorageLevel.MEMORY_AND_DIS_SER2
方式,也就是将数据序列化后,将数据存在内存中,内存放不下的数据就会溢写到磁盘上,当处理这批数据的时候就需要反序列化该数,这绝对影响性能,这样我们可以设置数据的持久化方式 DStream
的持久化:默认持久化方式为先序列化在进行持久化,这样减少内存的开销和GC
的开销。也可以使用对数据进行压缩,由spark.rdd.compress
参数控制,默认是false
。- 清除旧数据:
SparkStreaming
默认清除旧数据的时间是根据窗口长度决定的,清除旧数据的时间我们可以利用StreamingContext.remember()
来实现 CMS
垃圾回收器:强烈推荐使用mark-sweep
并行垃圾回收器,用来保证GC
的开销。虽然并行垃圾回收器会降低系统的吞吐量,但是它可以减少batch
的处理时间。这个需要在Driver
端和Executor端都要设置。在Driver端通过spark-submit
中的--driver-java-options
参数来设置,在Excutor
端需要用spark.excutor.extralJavaOptions
配置参数。
./bin/spark-submit --name "My app" --master local[4] --conf spark.eventLog.enabled=false
--conf "spark.executor.extraJavaOptions=-XX:+PrintGCDetails -XX:+PrintGCTimeStamps" myApp.jar
具体的详细配置请参考官网Spark参数配置