解决7*24小时稳定运行
首先,为了保证实时计算程序可以7*24小时能稳定运行,则第一个考虑就是要实现SparkStreaming对接Kafka实时计算程序HA高可用。
-
保证master高可用,worker节点的失败是具有容错性的(迄今为止,Spark自身而言对于丢失部分计算工作是有容错性的,它会将丢失的计算工作迁移到其他worker节点上执行)。然而,调度器是依托于master进程来做出调度决策的,这就会造成单点故障:如果master挂掉了,就没法提交新的应用程序。所以为了解决这个问题,spark有一个高可用方案,基于zookeeper的HA方案。
- 使用zookeeper来提供leader选举以及一些状态存储,你可以在集群中启动多个master进程,让它们连接到zookeeper实例。其中一个master进程会被选举为leader,其他的master会被指定为standby模式。如果当前的leader master进程挂掉了,其他的standby master会被选举,从而恢复旧master的状态。并且恢复作业调度。整个恢复过程(从leader master挂掉开始计算)大概会花费1~2分钟。要注意的是,这只会推迟调度新的应用程序,master挂掉之前就运行的应用程序是不被影响的。
在spark-env.sh文件中,设置SPARK_DAEMON_JAVA_OPTS选项:
spark.deploy.recoveryMode # 设置为ZOOKEEPER来启用standby master恢复模式(默认为NONE)
spark.deploy.zookeeper.url # zookeeper集群url(举例来说,192.168.75.101:2181,192.168.75.102:2181)
spark.deploy.zookeeper.dir # zookeeper中用来存储恢复状态的目录(默认是/spark)
备注:如果在集群中启动了多个master节点,但是没有正确配置master去使用zookeeper,master在挂掉进行恢复时是会失败的,因为没法发现其他master,并且都会认为自己是leader。这会导致集群的状态不是健康的,因为所有master都会自顾自地去调度。
1. 在启动一个zookeeper集群之后,简单地在多个节点上启动多个master进程,并且给它们相同的zookeeper配置(zookeeper url和目录),就完成了配置HA高可用。master就可以被动态加入master集群,并可以在任何时间被移除掉。
2. 为了调度新的应用程序或者向集群中添加worker节点,它们需要知道当前leader master的ip地址。这可以通过传递一个master列表来完成。举例来说,我们可以将我们的SparkContext连接的地址指向spark://host1:port1,host2:port2。这就会导致你的SparkContext尝试去注册所有的master,如果host1挂掉了,那么配置还是正确的,因为会找到新的leader master,也就是host2。
3. 对于注册一个master和普通的操作,这是一个重要的区别。当一个应用程序启动的时候,或者worker需要被找到并且注册到当前的leader master的时候。一旦它成功注册了,就被保存在zookeeper中了。如果故障发生了,new leader master会去联系所有的之前注册过的应用程序和worker,并且通知它们master的改变。这样的话,它们甚至在启动的时候都不需要知道new master的存在。正是由于这个属性,new master可以在任何时间被创建,并且我们唯一需要担心的一件事情就是新的应用程序和worker可以找到并且注册到master。一旦注册上去之后,我们就不用担心它了。
- 保证Driver高可用性,因为在第一次创建和启动StreamingContext的时候,那么将持续不断地产生实时计算的元数据并写入检查点,如果driver节点挂掉,那么可以让Spark集群自动重启集群(必须使用yarn cluster模式,spark-submit --deploy-mode cluster --supervise …)。
package cn.piesat.spark
import org.apache.kafka.clients.consumer.{ConsumerRecord}
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkStreamingKafka {
private val brokers = "hadoop01:9092"
private val topics = "lj01"
private val checkPointPath = "hdfs://hadoop01:9000/sparkStreaming/kafka6"
def main(args: Array[String]): Unit = {
val spark = getSparkSession()
val streamingContext = StreamingContext.getOrCreate(checkPointPath, () => {
val ssc = new StreamingContext(spark.sparkContext, Seconds(5))
ssc.checkpoint(checkPointPath)
val kafkaInputStream = getKafkaInputStream(ssc)
val result = kafkaInputStream.map(x => x.value()).flatMap(x => {
x.split(" ").map(x => {
(x, 1)
})
}).reduceByKey(_ + _)
result.print()
ssc
})
streamingContext.start()
streamingContext.awaitTermination()
}
def getSparkSession(): SparkSession = {
SparkSession.builder()
.appName("kafka_test")
.master("local[4]")
.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
.getOrCreate()
}
def getKafkaInputStream(ssc: StreamingContext): InputDStream[ConsumerRecord[String, String]] = {
val topicArray = topics.split(",").toList
val kafkaParams = Map[String, Object](
"bootstrap.servers" -> brokers,
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
"group.id" -> "lj00",
"auto.offset.reset" -> "latest",
"enable.auto.commit" -> (false: java.lang.Boolean)
)
KafkaUtils.createDirectStream[String, String](
ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String, String](topicArray, kafkaParams)
)
}
}
注意:对streaming的操作逻辑必须写在StreamingContext.getOrCreate()方法里,因为若是第二次恢复时则执行方法里的逻辑!!!
- 实现RDD的高可用,设置启动WAL预写日志机制。
sparkStreaming从原理上说,是通过receiver来进行数据接收的,接收到的数据,会被划分成一个个的block,block会被组合成batch,针对一个batch,会创建一个Rdd,启动一个job来执行定义的算子操作。receiver主要接收到数据,那么就会立即将数据写入一份到时容错文件系统(比如hdfs)上的checkpoint目录中的,一份磁盘文件中去,作为数据的冗余副本。
SparkConf conf = new SparkConf()
.setMaster("local[2]")
.setAppName("AdClickRealTimeStatSpark")
.set("spark.streaming.receiver.writeAheadLog.enable","true");
- 保证启用checkpoint,需在updateStateByKey、mapWithState、基于window等有状态的操作时,设置自动保存checkpoint,即必须设置cheakpoint目录,其一般是保存在HDFS上。
解决在程序升级的条件下,实现断点续传
断点续传是指数据同步任务在运行过程中因各种原因导致任务失败,不需要重头同步数据,只需要从上次失败的位置继续同步即可,类似于下载文件时因网络原因失败,不需要重新下载文件,只需要继续下载就行,可以大大节省时间和计算资源。保证能断点续传,则需要结合任务的出错重试机制才能完成。当任务运行失败,会在Engine###里进行重试,重试的时候会接着上次失败时读取的位置继续读取数据,直到任务运行成功为止。
前置条件:
- 数据源(这里特指关系数据库)中必须包含一个升序的字段,比如主键或者日期类型的字段,同步过程中会使用checkpoint机制记录这个字段的值,任务恢复运行时使用这个字段构造查询条件过滤已经同步过的数据,如果这个字段的值不是升序的,那么任务恢复时过滤的数据就是错误的,最终导致数据的缺失或重复;
- 数据源必须支持数据过滤,如果不支持的话,任务就无法从断点处恢复运行,会导致数据重复;
- 目标数据源必须支持事务,比如关系数据库,文件类型的数据源也可以通过临时文件的方式支持;
如果任务异常终止,任务如果异常结束,假设任务结束时最后一个checkpoint记录的状态为异常结束的offset,那么任务恢复的时候就会把各个通道记录的状态赋值给offset,再次读取数据时构造sql会加上where条件。
以上,就能满足7*24小时稳定运行,且解决在程序升级的条件下,实现断点续传。