在开发Spark Streaming应用程序时,要结合集群中各节点的配置情况尽可能地提高数据处理的实时性。在调优的过程中,一方面要尽可能利用集群资源来减少每个批处理的时间;另一方面要确保接收到的数据能及时处理掉。


运行时间优化


  • 设置合理的批处理时间和窗口大小


Spark Streaming中作业之间通常存在依赖关系,后面的作业必须确保前面的作业执行结束后才能提交,若前面的作业的执行时间超过了设置的批处理时间间隔,那么后续的作业将无法按时提交执行,造成作业的堵塞。也就是说若想Spark Streaming应用程序稳定地在集群中运行,对于接收到的数据必须尽快处理掉。例如若设定批处理时间为1秒钟,那么系统每1秒钟生成一个RDD,如果系统计算一个RDD的时间大于1秒,那么当前的RDD还没来得及处理,后续的RDD已经提交上来在等待处理了,这就产生了堵塞。因此需要设置一个合理的批处理时间间隔以确保作业能够在这个批处理时间间隔时间内结束。许多实验数据表明,500毫秒对大多Spark Streaming应用而言是较好的批处理时间间隔。


类似地,对于窗口操作,滑动时间间隔对于性能也有很大的影响。当单批次数据计算代价过高时,可以考虑适当增大滑动时间间隔。


对于批处理时间和窗口大小的设定,并没有统一的标准。通常是先从一个比较大的批处理时间(10秒左右)开始,然后不断地使用更小的值进行对比测试。如果Spark Streaming用户界面中显示的处理时间保持不变,则可以进一步设定更小的值;如果处理时间开始增加,则可能已经达到了应用的极限,再减小该值则可能会影响系统的性能。


  • 提高并行度


提高并行度也是一种减少批处理所消耗时间的常见方法。有以下三种方式可以提高并行度。一种方法是增加接收器数目。如果获取的数据太多,则可能导致单个节点来不及对数据进行读入与分发,使得接收器成为系统瓶颈。这时可以通过创建多个输入DStream来增加接收器数目,然后再使用union来把数据合并为一个数据源。第二种方法是将收到的数据显式地重新分区。如果接收器数目无法再增加,可以通过使用DStream.repartition、spark.streaming.blocklnterval等参数显式地对Dstream进行重新分区。第三种方法是提高聚合计算的并行度。对于会导致shuffle的操作,例如reduceByKey、reduceByKeyAndWindow等操作,可通过显示设置更高的行度参数确保更为充分地使用集群资源。


内存使用与垃圾回收


  • 控制批处理时间间隔内的数据量


Spark Streaming会把批处理时间间隔内获取到的所有数据存放在Spark内部可用的内存中。因此必须确保在当前节点上SparkStreaming可用的内存容量至少能容下一个批处理时间间隔内所有的数据。比如一个批处理时间间隔是1秒,但是1秒产生了1GB的数据,那么要确保当前的节点上至少有可供SparkStreaming使用的1GB内存。


  • 及时清理不再使用的数据


对于内存中处理过的、不再需要的数据应及时清理,以确保Spark Streaming能够拥有足够的内存空间可以使用。一种方法是可以通过设置合理的spark.cleaner.ttl时长来及时清理超时的无用数据,但该方法应慎重使用,以免后续数据在需要时被错误清理。另一种方法是将spark.streaming.unpersist设置为true,系统将自动清理已经不需要的RDD。该方法能显著减少RDD对内存的需要,同时潜在地提高GC的性能。此外用户还可以通过配置参数streamingContext.remember为数据设置更长的保留时间。


  • 减少序列化与反序列化的负担


SparkStreaming默认将接收到的数据序列化后放入内存,以减少内存使用。序列化和反序列化需要更多的CPU资源,因此使用适当的序列化工具(例如Kryo)和自定义的序列化接口可以更高效地使用CPU。除了使用更好的序列化工具外还可以结合压缩机制,通过配置spark.rdd.compress,以CPU的时间开销来换取内存资源,降低GC开销。