从集群上的Spark Streaming应用程序中获得最佳性能需要进行一些调整。在高层次上,我们需要考虑两件事:
可以在Spark中进行许多优化,以最大限度地缩短每个批处理的处理时间。
通过网络接收数据(如Kafka,Flume,Socket等)需要将数据反序列化并存储在Spark中。如果数据接收称为系统中的瓶颈,则考虑并行化数据接收。请注意,每个输入DStream都会创建一个接收单个数据流的接收器(在工作机器上运行)。因此,可以通过创建多个输入DStream可以分成两个Kafka输入流,每个输入流只接收一个主题。这将运行两个接收器,允许并行接收数据,从而提高整体吞吐量。这些多个DStream可以组合在一起以创建单个DStream。然后,可以在统一流上应用在单个输入DStream上应用的转换。如下:
int numStreams = 5;
List> kafkaStreams = new ArrayList<>(numStreams);
for (int i = 0; i < numStreams; i++) {
kafkaStreams.add(KafkaUtils.createStream(...));
}
JavaPairDStream unifiedStream = streamingContext.union(kafkaStreams.get(0), kafkaStreams.subList(1, kafkaStreams.size()));
unifiedStream.print();
应考虑的另一个参数是接收器的块间隔,它由配置参数spark.streaming.blockInterval决定。对于大多数接收器,接收的数据存储在Spark的内存中之前合并为数据块。每批中的块决定了在类似map的转换中处理接收数据的任务书。每批每个接收器的任务数量大约是(批处理间隔/块间隔)。例如,200ms的块间隔将每2秒批次创建10个任务。如果任务数量太少(即,少于每台计算机的核心数),那么效率将会很低,因为所有可能的核心都不会用于处理数据。要增加给定批处理间隔的任务书,请减少块间隔。但是,建议的块间隔最小值约为50ms,低于该值,任务启动开销可能会出现问题。
使用多个输入流/接收器接收数据的替代方案是显式地重新分区输入数据流(使用inputStream.repartition(
如果在计算的任何阶段中使用的并行任务的数量不够高,则可能未充分利用集群资源。例如,对于像reduceByKey和reduceByKeyAndWindow的分布式reduce操作,默认的并行任务数由配置属性spark.default.parallelism控制。我们可以将并行级别作为参数传递。
通过调整序列化格式可以减少数据序列化的开销。在流式传输的情况下,有两种类型的数据被序列化。
在这两种情况下,使用Kryo序列化可以减少CPU和内存开销。对于Kryo,请考虑注册自定义类,并禁用对象引用跟踪。
在需要为流应用程序保留地数据量不大地特定情况下,将数据(两种类型)保存为反序列化对象可能是可行地,而不会产生过多地GC开销。例如,如果我们使用几秒钟地批处理间隔而没有窗口操作,则可以尝试通过相应地显式设置存储级别来禁用持久数据中地序列化。这将减少由于序列化导致地CPU开销,可能在没有太多GC开销的情况下提高性能。
如果每秒启动地任务数量很高(例如,每秒50或更多),则向从属设备发送任务地开销可能很大,并且将难以实现亚秒级延迟。通过以下更改可以减少开销:
这些更改可以将批处理时间减少100毫秒,从而允许亚秒级批量大小可行。
要使集群上运行的Spark Streaming应用程序保持稳定,系统应该能够以接收数据的速度处理数据。换句话说,批处理数据应该在生成时尽快处理。通过监视流式Web UI中的处理时间可以找到是否适用于应用程序,其中批处理时间应小于批处理间隔。
根据流式计算的性质,所使用的批处理间隔可能对应用程序在固定的一组集群资源上可以维持的数据速率产生重大影响。例如,让我们考虑一下早期的WordCountNetwork示例。对于特定数据速率,系统可能能够每2秒跟上报告字数,但不是每500毫秒。因此需要设置批处理间隔,以便可以维持生产中的预期数据速率。
确定适合我们的ing用程序批量大小的好方法是使用保守的批处理间隔(例如,5-10秒)和低数据速率进行测试。要验证系统是否能够跟上数据速率,我们可以检查每个已处理批处理所遇到的端到端延迟的值(在Spark驱动程序log4j日志中查找“总延迟”,或使用StreamingListener接口)。如果延迟保持与批量大小相当,则系统稳定。否则,如果延迟不断增加,则意味着系统无法跟上,因此不稳定。一旦了解了稳定的配置,就可以尝试提高数据速率和/或减少批量。注意,只要延迟减小到低值(即,小于批量大小),由于临时数据速率增加引起的延迟的瞬时增加可能是正常的。
Spark Streaming应用程序所需的集群内存量在很大程度上取决于所使用的转换类型。例如,如果要在最后十分重的数据上使用窗口操作,那么我们的集群应该有足够的内存来保存10分钟的数据。或者,如果我们想使用updateStateByKey大量的键,那么需要的内存将很高。相反,如果我们想做一个简单的map-filter-store操作,那么必要的内存就会很低。
通常,由于通过接收器接收的数据与StorageLevel.MEMORY_AND_DISK_SER_2一起存储,因此不适合内存的数据将移除到磁盘。这可能会降低流应用程序的性能,因此建议我们根据应用程序的需要提供足够的内存。最好尝试小规模的查看内存使用情况进行相应估算。
内存调整的另一个方面是垃圾收集。对于需要低延迟的流应用程序,不希望有JVM垃圾收集引起大的暂停。
有一些参数可以帮助我们调整内存使用和GC开销: