网上的怎么关闭SparkStreaming的文章一大堆,可我还是费了很大的力气才解决了我的问题。
如何解决?
方案一: Spark1.4之前的版本,需要一个钩子函数(该方案弃用)
sys.ShutdownHookThread {
log.info("Gracefully stopping Spark Streaming Application")
ssc.stop(true, true)
log.info("Application stopped")
}
方案二:Spark 1.4之后的版本(有两种方式,都没有测试过)
方式一:通过端口号找到主进程然后kill掉
只需要在SparkConf里面设置下面的参数即可:
sparkConf.set("spark.streaming.stopGracefullyOnShutdown","true")
然后,如果需要停掉sparkstreaming程序时:
(1)登录spark ui页面在executors页面找到driver程序所在的机器
(2)使用ssh命令登录这台机器上,执行下面的命令通过端口号找到主进程然后kill掉
ss -tanlp | grep 55197|awk '{print $6}'|awk -F, '{print $2}'|xargs kill -15
注意:上面的操作执行后,sparkstreaming程序,并不会立即停止,而是会把当前的批处理里面的数据处理完毕后才会停掉,此间sparkstreaming不会再消费kafka的数据,这样以来就能保证结果不丢和重复。
方式二:发送SIGTERM信号到spark驱动程序然后关闭程序
首先将 spark.streaming.stopGracefullyOnShutdown 参数设置为true(默认值为false)。Spark中引入此参数来解决正常关机问题。开发人员无需再在代码中调用ssc.stop()。但需要向主程序driver端发送SIGTERM信号。在实践中,我们需要做到以下几点:
(1)使用Spark UI找出驱动程序进程在哪个节点上运行。在yarn群集部署模式中,驱动程序进程和AM在同一个容器中运行。
(2)登录该节点并执行 ps -ef | grep java | grep ApplicationMaster 并找出pid。
请注意,根据您的应用程序/环境等,grep字符串可能会有所不同。
(3)将SIGTERM发送到进程。
kill-SIGTERM
此外还有一个问题是,spark on yarn模式下,默认情况下,Spark的spark.yarn.maxAppAttempts 参数使用 YARN中yarn.resourcemanager.am.max- attempts中的默认值 ,默认值为2。因此,在第一个AM被kill命令停止后,YARN将自动启动另一个AM 驱动程序,作为高可用,也就是上面的操作你可能要执行两次,才能真能的停掉程序,当然我们也可以在spark-submit期间设置 --conf spark.yarn.maxAppAttempts = 1 驱动程序一次挂掉之后,就真的挂掉了,这样就没有容灾机制了,需要慎重考虑。
方案三:使用HDFS系统做消息通知(我采用这样方式)
在驱动程序中,加一段代码,这段代码的作用每隔一段时间可以是10秒也可以是3秒,扫描HDFS上某一个文件,如果发现这个文件存在,就调用StreamContext对象stop方法,自己优雅的终止自己,其实这里HDFS可以换成redis,zk,hbase,db都可以,这里唯一的问题就是依赖了外部的一个存储系统来达到消息通知的目的,如果使用了这种方式后。停止流程序就比较简单了,登录上有hdfs客户端的机器,然后touch一个空文件到指定目录,然后等到间隔的扫描时间到之后,发现有文件存在,就知道需要关闭程序了。
package com.spark.test
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
object Test {
val shutdownMarker = "/tmp/spark-test/stop-spark"
var stopFlag: Boolean = false
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("stop spark streaming")
val ssc = new StreamingContext(conf, Seconds(5))
val file = ssc.textFileStream("/tmp/sparkstreaming")
val res = file.map { line =>
val arr = line.split("\t")
arr(0) + "\t" + arr(2)
}
res.saveAsTextFiles("/tmp/stroage")
ssc.start()
//检查间隔毫秒
val checkIntervalMillis = 10000
var isStopped = false
while (!isStopped) {
println("calling awaitTerminationOrTimeout")
//等待执行停止。执行过程中发生的任何异常都会在此线程中抛出,如果执行停止了返回true,
//线程等待超时长,当超过timeout时间后,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。
isStopped = ssc.awaitTerminationOrTimeout(checkIntervalMillis)
if (isStopped) {
println("confirmed! The streaming context is stopped. Exiting application...")
} else {
println("Streaming App is still running. Timeout...")
}
//判断文件夹是否存在
checkShutdownMarker
if (!isStopped && stopFlag) {
println("stopping ssc right now")
//第一个true:停止相关的SparkContext。无论这个流媒体上下文是否已经启动,底层的SparkContext都将被停止。
//第二个true:则通过等待所有接收到的数据的处理完成,从而优雅地停止。
ssc.stop(true, true)
println("ssc is stopped!!!!!!!")
}
}
}
def checkShutdownMarker = {
if (!stopFlag) {
//开始检查hdfs是否有stop-spark文件夹
val fs = FileSystem.get(new Configuration())
//如果有返回true,如果没有返回false
stopFlag = fs.exists(new Path(shutdownMarker))
}
}
}
我一直纠结 awaitTerminationOrTimeout(checkIntervalMillis) 这个方法,对我数据处理是否有影响,查阅大量资料这个方法就是等待执行停止,在执行期间发生的任何异常都会被抛出这个线程。而且检查间隔毫秒其实是线程等待超时时长,当超过timeout时间后,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。还有我在测试的时候,停掉spark streaming查看日志,发现会有一个警告,告诉我sparkContext 上下文已经关闭,没办法写数据。
学习连接 http://www.linkedin.com/pulse/how-shutdown-spark-streaming-job-gracefully-lan-jiang
感谢各位大神提供的方案,我在这里个了详细的说明。