一.缓存与持久化机制
与RDD类似,Spark Streaming也可以让开发人员手动控制,将数据流中的数据持久化到内存中。对DStream调用persist()方法,就可以让Spark Streaming自动将该数据流中的所有产生的RDD,都持久化到内存中。如果要对一个DStream多次执行操作,那么,对DStream持久化是非常有用的。因为多次操作,可以共享使用内存中的一份缓存数据。
对于基于窗口的操作,比如reduceByWindow、reduceByKeyAndWindow,以及基于状态的操作,比如updateStateByKey,默认就隐式开启了持久化机制。即Spark Streaming默认就会将上述操作产生的Dstream中的数据,缓存到内存中,不需要开发人员手动调用persist()方法。
对于通过网络接收数据的输入流,比如socket、Kafka、Flume等,默认的持久化级别,是将数据复制一份,以便于容错。相当于是,用的是类似MEMORY_ONLY_SER_2。
与RDD不同的是,默认的持久化级别,统一都是要序列化的。
二.Checkpoint机制概述
每一个Spark Streaming应用,正常来说,都是要7 * 24小时运转的,这就是实时计算程序的特点。因为要持续不断的对数据进行计算。因此,对实时计算应用的要求,应该是必须要能够对与应用程序逻辑无关的失败,进行容错。
如果要实现这个目标,Spark Streaming程序就必须将足够的信息checkpoint到容错的存储系统上,从而让它能够从失败中进行恢复。有两种数据需要被进行checkpoint:
1、元数据checkpoint——将定义了流式计算逻辑的信息,保存到容错的存储系统上,比如HDFS。当运行Spark Streaming应用程序的Driver进程所在节点失败时,该信息可以用于进行恢复。元数据信息包括了:
1.1 配置信息——创建Spark Streaming应用程序的配置信息,比如SparkConf中的信息。
1.2 DStream的操作信息——定义了Spark Stream应用程序的计算逻辑的DStream操作信息。
1.3 未处理的batch信息——那些job正在排队,还没处理的batch信息。
2、数据checkpoint——将实时计算过程中产生的RDD的数据保存到可靠的存储系统中。
对于一些将多个batch的数据进行聚合的,有状态的transformation操作,这是非常有用的。在这种transformation操作中,生成的RDD是依赖于之前的batch的RDD的,这会导致随着时间的推移,RDD的依赖链条变得越来越长。
要避免由于依赖链条越来越长,导致的一起变得越来越长的失败恢复时间,有状态的transformation操作执行过程中间产生的RDD,会定期地被checkpoint到可靠的存储系统上,比如HDFS。从而削减RDD的依赖链条,进而缩短失败恢复时,RDD的恢复时间。
一句话概括,元数据checkpoint主要是为了从driver失败中进行恢复;而RDD checkpoint主要是为了,使用到有状态的transformation操作时,能够在其生产出的数据丢失时,进行快速的失败恢复。
(1)何时启用Checkpoint机制?
1、使用了有状态的transformation操作——比如updateStateByKey,或者reduceByKeyAndWindow操作,被使用了,那么checkpoint目录要求是必须提供的,也就是必须开启checkpoint机制,从而进行周期性的RDD checkpoint。
2、要保证可以从Driver失败中进行恢复——元数据checkpoint需要启用,来进行这种情况的恢复。
要注意的是,并不是说,所有的Spark Streaming应用程序,都要启用checkpoint机制,如果即不强制要求从Driver失败中自动进行恢复,又没使用有状态的transformation操作,那么就不需要启用checkpoint。事实上,这么做反而是有助于提升性能的。
如何启用Checkpoint机制?
1、对于有状态的transformation操作,启用checkpoint机制,定期将其生产的RDD数据checkpoint,是比较简单的。
可以通过配置一个容错的、可靠的文件系统(比如HDFS)的目录,来启用checkpoint机制,checkpoint数据就会写入该目录。使用StreamingContext的checkpoint()方法即可。然后,你就可以放心使用有状态的transformation操作了。
2、如果为了要从Driver失败中进行恢复,那么启用checkpoint机制,是比较复杂的。需要改写Spark Streaming应用程序。
当应用程序第一次启动的时候,需要创建一个新的StreamingContext,并且调用其start()方法,进行启动。当Driver从失败中恢复过来时,需要从checkpoint目录中记录的元数据中,恢复出来一个StreamingContext。
(2)为Driver失败的恢复机制重写程序(Java)
JavaStreamingContextFactory contextFactory = new JavaStreamingContextFactory() {
@Override
public JavaStreamingContext create() {
JavaStreamingContext jssc = new JavaStreamingContext(...);
JavaDStream<String> lines = jssc.socketTextStream(...);
jssc.checkpoint(checkpointDirectory);
return jssc;
}
};
JavaStreamingContext context = JavaStreamingContext.getOrCreate(checkpointDirectory, contextFactory);
context.start();
context.awaitTermination();
(3)为Driver失败的恢复机制重写程序(Scala)
def functionToCreateContext(): StreamingContext = {
val ssc = new StreamingContext(...)
val lines = ssc.socketTextStream(...)
ssc.checkpoint(checkpointDirectory)
ssc
}
val context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext _)
context.start()
context.awaitTermination()
(4)配置spark-submit提交参数
按照上述方法,进行Spark Streaming应用程序的重写后,当第一次运行程序时,如果发现checkpoint目录不存在,那么就使用定义的函数来第一次创建一个StreamingContext,并将其元数据写入checkpoint目录;当从Driver失败中恢复过来时,发现checkpoint目录已经存在了,那么会使用该目录中的元数据创建一个StreamingContext。
但是上面的重写应用程序的过程,只是实现Driver失败自动恢复的第一步。第二步是,必须确保Driver可以在失败时,自动被重启。
要能够自动从Driver失败中恢复过来,运行Spark Streaming应用程序的集群,就必须监控Driver运行的过程,并且在它失败时将它重启。对于Spark自身的standalone模式,需要进行一些配置去supervise driver,在它失败时将其重启。
首先,要在spark-submit中,添加--deploy-mode参数,默认其值为client,即在提交应用的机器上启动Driver;但是,要能够自动重启Driver,就必须将其值设置为cluster;此外,需要添加--supervise参数。
使用上述第二步骤提交应用之后,就可以让driver在失败时自动被重启,并且通过checkpoint目录的元数据恢复StreamingContext。
(5)Checkpoint说明
将RDD checkpoint到可靠的存储系统上,会耗费很多性能。当RDD被checkpoint时,会导致这些batch的处理时间增加。因此,checkpoint的间隔,需要谨慎的设置。对于那些间隔很多的batch,比如1秒,如果还要执行checkpoint操作,则会大幅度削减吞吐量。而另外一方面,如果checkpoint操作执行的太不频繁,那就会导致RDD的lineage变长,又会有失败恢复时间过长的风险。
对于那些要求checkpoint的有状态的transformation操作,默认的checkpoint间隔通常是batch间隔的数倍,至少是10秒。使用DStream的checkpoint()方法,可以设置这个DStream的checkpoint的间隔时长。通常来说,将checkpoint间隔设置为窗口操作的滑动间隔的5~10倍,是个不错的选择。
三.部署、升级和监控应用程序
(1)部署应用程序
1、有一个集群资源管理器,比如standalone模式下的Spark集群,Yarn模式下的Yarn集群等。
2、打包应用程序为一个jar包
3、为executor配置充足的内存,因为Receiver接受到的数据,是要存储在Executor的内存中的,所以Executor必须配置足够的内存来保存接受到的数据。要注意的是,如果你要执行窗口长度为10分钟的窗口操作,那么Executor的内存资源就必须足够保存10分钟内的数据,因此内存的资源要求是取决于你执行的操作的。
4、配置checkpoint,如果你的应用程序要求checkpoint操作,那么就必须配置一个Hadoop兼容的文件系统(比如HDFS)的目录作为checkpoint目录.
5、配置driver的自动恢复,如果要让driver能够在失败时自动恢复,之前已经讲过,一方面,要重写driver程序,一方面,要在spark-submit中添加参数。
(2)部署应用程序:启用预写日志机制
预写日志机制,简写为WAL,全称为Write Ahead Log。从Spark 1.2版本开始,就引入了基于容错的文件系统的WAL机制。如果启用该机制,Receiver接收到的所有数据都会被写入配置的checkpoint目录中的预写日志。这种机制可以让driver在恢复的时候,避免数据丢失,并且可以确保整个实时计算过程中,零数据丢失。
要配置该机制,首先要调用StreamingContext的checkpoint()方法设置一个checkpoint目录。然后需要将spark.streaming.receiver.writeAheadLog.enable参数设置为true。
然而,这种极强的可靠性机制,会导致Receiver的吞吐量大幅度下降,因为单位时间内,有相当一部分时间需要将数据写入预写日志。如果又希望开启预写日志机制,确保数据零损失,又不希望影响系统的吞吐量,那么可以创建多个输入DStream,启动多个Rceiver。
此外,在启用了预写日志机制之后,推荐将复制持久化机制禁用掉,因为所有数据已经保存在容错的文件系统中了,不需要在用复制机制进行持久化,保存一份副本了。只要将输入DStream的持久化机制设置一下即可,persist(StorageLevel.MEMORY_AND_DISK_SER)。(之前讲过,默认是基于复制的持久化策略,_2后缀)
(3)部署应用程序:设置Receiver接收速度
如果集群资源有限,并没有大到,足以让应用程序一接收到数据就立即处理它,Receiver可以被设置一个最大接收限速,以每秒接收多少条单位来限速。
spark.streaming.receiver.maxRate和spark.streaming.kafka.maxRatePerPartition参数可以用来设置,前者设置普通Receiver,后者是Kafka Direct方式。
Spark 1.5中,对于Kafka Direct方式,引入了backpressure机制,从而不需要设置Receiver的限速,Spark可以自动估计Receiver最合理的接收速度,并根据情况动态调整。只要将spark.streaming.backpressure.enabled设置为true即可。
在企业实际应用场景中,通常是推荐用Kafka Direct方式的,特别是现在随着Spark版本的提升,越来越完善这个Kafka Direct机制。
优点:
1、不用receiver,不会独占集群的一个cpu core;
2、有backpressure自动调节接收速率的机制;3、....。
(4)升级应用程序
由于Spark Streaming应用程序都是7 * 24小时运行的。因此如果需要对正在运行的应用程序,进行代码的升级,那么有两种方式可以实现:
1、升级后的Spark应用程序直接启动,先与旧的Spark应用程序并行执行。当确保新的应用程序启动没问题之后,就可以将旧的应用程序给停掉。但是要注意的是,这种方式只适用于,能够允许多个客户端读取各自独立的数据,也就是读取相同的数据。
2、小心地关闭已经在运行的应用程序,使用StreamingContext的stop()方法,可以确保接收到的数据都处理完之后,才停止。然后将升级后的程序部署上去,启动。这样,就可以确保中间没有数据丢失和未处理。因为新的应用程序会从老的应用程序未消费到的地方,继续消费。但是注意,这种方式必须是支持数据缓存的数据源才可以,比如Kafka、Flume等。如果数据源不支持数据缓存,那么会导致数据丢失。
注意:配置了driver自动恢复机制时,如果想要根据旧的应用程序的checkpoint信息,启动新的应用程序,是不可行的。需要让新的应用程序针对新的checkpoint目录启动,或者删除之前的checkpoint目录。
(5)监控应用程序
当Spark Streaming应用启动时,Spark Web UI会显示一个独立的streaming tab,会显示Receiver的信息,比如是否活跃,接收到了多少数据,是否有异常等;还会显示完成的batch的信息,batch的处理时间、队列延迟等。这些信息可以用于监控spark streaming应用的进度。
Spark UI中,以下两个统计指标格外重要:
1、处理时间——每个batch的数据的处理耗时
2、调度延迟——一个batch在队列中阻塞住,等待上一个batch完成处理的时间
如果batch的处理时间,比batch的间隔要长的话,而且调度延迟时间持续增长,应用程序不足以使用当前设定的速率来处理接收到的数据,此时,可以考虑增加batch的间隔时间。