Spark Streaming 性能调优

这里的Spark Streaming 性能调优,主要涉及从Kafka读数据、往Kafka中写数据的过程。

Spark Streaming 对接 Kafka

Spark Streaming 对接 kafka有receive和direct两种方式。

基于Receiver的方式

这种方式利用接收器(Receiver)来接收kafka中的数据,其最基本是使用Kafka高阶用户API接口。对于所有的接收器,从kafka接收来的数据会存储在spark的executor中,之后spark streaming提交的job会处理这些数据。如下图:


需要注意的点:

  • 在Receiver的方式中,Spark中的partition和kafka中的partition并不是相关的,所以如果我们加大每个topic的partition数量,仅仅是增加线程来处理由单一Receiver消费的主题。但是这并没有增加Spark在处理数据上的并行度。
  • 对于不同的Group和topic我们可以使用多个Receiver创建不同的Dstream来并行接收数据,之后可以利用union来统一成一个Dstream。
  • 如果我们启用了Write Ahead Logs复制到文件系统如HDFS,那么storage level需要设置成 StorageLevel.MEMORY_AND_DISK_SER,也就是KafkaUtils.createStream(..., StorageLevel.MEMORY_AND_DISK_SER)

直连方式

在spark1.3之后,引入了Direct方式。不同于Receiver的方式,Direct方式没有receiver这一层,其会周期性的获取Kafka中每个topic的每个partition中的最新offsets,之后根据设定的maxRatePerPartition来处理每个batch。其形式如下图:

这种方法相较于Receiver方式的优势在于:

  • 简化的并行:在Receiver的方式中我们提到创建多个Receiver之后利用union来合并成一个Dstream的方式提高数据传输并行度。而在Direct方式中,Kafka中的partition与RDD中的partition是一一对应的并行读取Kafka数据,这种映射关系也更利于理解和优化。
  • 高效:在Receiver的方式中,为了达到0数据丢失需要将数据存入Write Ahead Log中,这样在Kafka和日志中就保存了两份数据,浪费!而第二种方式不存在这个问题,只要我们Kafka的数据保留时间足够长,我们都能够从Kafka进行数据恢复。
  • 精确一次:在Receiver的方式中,使用的是Kafka的高阶API接口从Zookeeper中获取offset值,这也是传统的从Kafka中读取数据的方式,但由于Spark Streaming消费的数据和Zookeeper中记录的offset不同步,这种方式偶尔会造成数据重复消费。而第二种方式,直接使用了简单的低阶Kafka API,Offsets则利用Spark Streaming的checkpoints进行记录,消除了这种不一致性。

下面我们都采用直连的方式读取kafka数据。

直连方式的kafka offset管理

而在Direct的方式中,我们是直接从kafka来读数据,那么offset需要自己记录,可以利用checkpoint、数据库或文件记录或者回写到zookeeper中进行记录。

拉去数据量控制

spark.streaming.kafka.maxRatePerPartition=40000
spark.streaming.backpressure.enabled=true

使用高性能算子

  • 使用reduceByKey/aggregateByKey替代groupByKey
  • 使用mapPartitions替代普通map
  • 使用foreachPartitions替代foreach
  • 使用filter之后进行coalesce操作
  • 使用repartitionAndSortWithinPartitions替代repartition与sort类操作

Spark Streaming 并行度

DStream的Partition个数

Spark Streaming 消费 Kafka的最直接的并行度和kafka的topic的partition个数相关,采用直连的方式topic的partition个数决定了Spark Streaming 的 RDD的partition个数。所以可以通过增加topic的paritition个数来提高Spark Streaming并行度。

Repatition

Spark 提供了api可以控制RDD的partition数量,使用repartition函数来增加partittion个数(会产生shuffle),使用coalesce函数来减少partition个数。

Executors和cpu核心数

executor的数量和每个executor配置的cpu cores直接影响着Spark的处理能力。每个Partition会对应启动一个task,每个task至少需要一个线程来处理。 --num-executors 控制分配的executor个数,--executor-memory控制每个executor分配的内存资源,--executor-cores控制每个executor的cpu cores。num-executors 和 executor-cores 根据DStream的partition个数和数据量大小设置。

举例:

假设Spark 提交到Yarn上运行,yarn有3个node manager节点,spark streaming任务执行中产生了6个partitions,那么num-executors和executor-cores如何设置呢?

针对上面的情况,为了充分利用分布式的特点,建议num-executors建议设成3;为了spark中的task能够快速的被执行,executor-cores建议设成2,这样可以实现每个cpu core处理一个task。

Spark on Yarn 弹性伸缩

Spark on Yarn提供了基于资源的使用情况,弹性伸缩的特性。

启用spark.dynamicAllocation,需要将 spark.shuffle.service.enabled和 spark.dynamicAllocation.enabled设为true,其他的  spark.shuffle.service.* 和 spark.dynamicAllocation.* 配置是可选的。

配置示例如下:

 spark.shuffle.service.enabled=true 

 spark.dynamicAllocation.enabled=true

 spark.dynamicAllocation.executorIdleTimeout=60s

 spark.dynamicAllocation.initialExecutors=1

 spark.dynamicAllocation.maxExecutors=5

 spark.dynamicAllocation.minExecutors=0


去Spark Streaming中的shuffle


写Kafka

上文阐述了Spark如何从Kafka中流式的读取数据,下面我整理向Kafka中写数据。与读数据不同,Spark并没有提供统一的接口用于写入Kafka,所以我们需要使用底层Kafka接口进行包装。

最直接的做法我们可以想到如下这种方式:

rdd.foreachRDD{ r => {
	r.foreachPartition {fp => {
			if (fp.nonEmpty) {
				MyKafkaProducer.setkafkaParams(kafkaProParams)
				fp.foreach(e => {
					// send to kafka
				})
			}
		}
	}
	}
}

显然这种做法是不灵活且低效的,因为每个rdd的partition都需要建立一次连接。如何解决呢?

参考:

Spark踩坑记——Spark Streaming+Kafka

gc问题



猜你喜欢

转载自blog.csdn.net/oitebody/article/details/79810287