SnappyStore虚拟机退出:分布式系统关闭相关Spark报错排查求助
我之前处理过好几起类似的Spark和GemFireXD集成时的关闭报错,先把你遇到的报错贴出来方便大家对照:
SnappyStore - 虚拟机正在退出 - 关闭分布式系统
org.apache.spark.SparkContext - 从关闭钩子调用stop()
o.e.jetty.server.ServerConnector - 已停止ServerConnector@244e619a{HTTP/1.1}{0.0.0.0:4040}
ERROR o.a.spark.scheduler.LiveListenerBus - 监听器SparkContextListener抛出异常
com.pivotal.gemfirexd.internal.impl.jdbc.EmbedSQLException: GemFireXD系统已关闭。
at com.pivotal.gemfirexd.internal.impl.jdbc.SQLExceptionFactory40.getSQLException(SQLExceptionFact...
问题根源
核心就是关闭顺序搞反了:JVM触发关闭钩子时,GemFireXD(SnappyStore)的关闭钩子先执行,把分布式系统关了;但Spark的SparkContext.stop()是后面才触发的,此时SparkContextListener还在尝试和GemFireXD交互,自然就抛出“系统已关闭”的异常。
具体解决方案
1. 手动控制关闭顺序(最推荐)
别依赖JVM自动触发关闭,在你的应用代码里显式先停Spark,再关GemFireXD,把顺序握在自己手里:
// 先停止Spark Context(如果用SnappyData就是SnappyContext) sparkContext.stop() // 再关闭GemFireXD集群/连接 com.pivotal.gemfirexd.internal.engine.Misc.stopGemFireXD()
要是用的是SnappyData,更简单,直接调用SnappyContext.stop()就行,它内部已经处理好了两者的关闭顺序,不会出问题。
2. 调整关闭钩子优先级(适合必须用钩子的场景)
如果你的架构必须依赖JVM关闭钩子,那可以给Spark的关闭钩子设置更高优先级(JVM钩子优先级数值越小,执行越早),让它抢在GemFireXD钩子前面执行:
// 自定义一个早于默认Spark钩子的关闭线程 Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (sparkContext != null && !sparkContext.isStopped()) { sparkContext.stop(); } }, "Spark-Early-Shutdown-Hook")); // 注意:这个钩子一定要在GemFireXD初始化之前添加,才能保证优先级生效
3. 临时屏蔽日志(不影响功能时用)
如果这个报错只是日志难看,完全不影响系统最终关闭状态,那可以直接调整Spark的日志级别,把这个Listener的报错压下去。比如在log4j.properties里加一行:
log4j.logger.org.apache.spark.scheduler.LiveListenerBus=WARN
额外注意点
- 检查你的应用里有没有其他自定义的关闭钩子,多个钩子的顺序很容易出问题,尽量统一管理;
- 要是用的是较新版本的SnappyData,建议升级到最新稳定版,官方后续修复了不少这类关闭顺序的bug。
内容的提问来源于stack exchange,提问作者eason

