解决在编程方式下无法访问Spark Master问题
我们可以选择使用spark-shell,spark-submit或者编写代码的方式运行Spark。在产品环境下,利用spark-submit将jar提交到spark,是较为常见的做法。但是在开发期间,每次都需要编译jar去做提交是一件麻烦事儿。尤其是在IDE例如IntelliJ Idea下,更直接的方式还是在main()方法中直接通过SparkContext运行。例如:
object DataFrameApp {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("DataFrame")
.setMaster("local[*]")
val sc = new SparkContext(sparkConf)
}
}
在我们的产品中,更需要采用编程方式去运行Spark数据分析。因为我们希望将数据分析的逻辑封装(暴露)为REST服务。我们选择了Spary作为REST框架。在这种方式下,应该由客户端的请求触发任务的执行。为了性能考虑,我们会在启动spary-can时实例化SparkContext,然后将其传递给真正执行任务的Actor。当然,如何将Spark Context的创建与spray-can容器的启动结合起来,则是另外一个坑,但好歹这个坑已被填平,我会在下一篇博客中介绍。
上述代码在IntelliJ中运行没有任何问题。这里给出build.sbt中设置的依赖:
scalaVersion := "2.11.6"
libraryDependencies ++= {
val sparkVersion = "1.3.1"
val hadoopVersion = "2.6.0"
Seq(
"org.apache.spark" %% "spark-core" % sparkVersion,
"org.apache.spark" %% "spark-sql" % sparkVersion,
"org.apache.spark" %% "spark-catalyst" % sparkVersion,
"org.apache.hadoop" % "hadoop-client" % hadoopVersion,
"org.postgresql" % "postgresql" % "9.4-1201-jdbc41"
)
}
在产品环境下,我们不可能将master设置为local模式。目前,我们并没有将spark部署到yarn或者mesos下,而是选择了最简单的standalone方式。方法就是运行SPARK_HOME/sbin目录下的脚本start-master.sh或者start-all.sh。而在客户端,需要将SparkConf的master设置为部署的spark url。如何获知这个url呢?当Spark启动成功后,假设机器的IP为192.168.1.4,则可以通过访问192.168.1.4:8080访问Spark Web UI:
这个页面显示了url地址:spark://192.168.1.4:7077。根据 Spark官方文档对部署模式的说明,我们可以将该URL设置到SparkConf下,例如:
trait SparkContextSupport {
val sparkConf = new SparkConf().setAppName("Spark-Spike")
.setMaster("spark://192.168.1.4:7077")
val sc = new SparkContext(sparkConf)
}
object PostgreSqlFetcherApp extends SparkContextSupport {
def main(args: Array[String]): Unit = {
val sqlContext = new SQLContext(sc)
val url = "jdbc:postgresql://localhost:5432/demo?user=zhangyi"
val dataFrame = sqlContext.load("jdbc", Map(
"url" -> url,
"driver" -> "org.postgresql.Driver",
"dbtable" -> "tab_datasets"
))
dataFrame.registerTempTable("Employees")
val emps = sqlContext.sql("select name from Employees")
emps.take(100).map(row => row.getString(0)).foreach(println)
sc.stop()
}
}
当然,我们也可以在/etc/hosts下为该ip地址设置hostname,从而通过hostname来访问。
运行这段程序会发生什么呢?很不幸,它在创建SparkContext的过程中抛出了如下错误:
/05/18 09:41:12 INFO AppClient$ClientActor: Connecting to master akka.tcp://sparkMaster@192.168.1.4:7077/user/Master...
/05/18 09:41:12 WARN ReliableDeliverySupervisor: Association with remote system [akka.tcp://sparkMaster@192.168.1.4:7077] has failed, address is now gated for [5000] ms. Reason is: [Disassociated].
/05/18 09:41:32 INFO AppClient$ClientActor: Connecting to master akka.tcp://sparkMaster@192.168.1.4:7077/user/Master...
/05/18 09:41:32 WARN ReliableDeliverySupervisor: Association with remote system [akka.tcp://sparkMaster@192.168.1.4:7077] has failed, address is now gated for [5000] ms. Reason is: [Disassociated].
/05/18 09:41:52 ERROR SparkDeploySchedulerBackend: Application has been killed. Reason: All masters are unresponsive! Giving up.
/05/18 09:41:52 WARN SparkDeploySchedulerBackend: Application ID is not initialized yet.
/05/18 09:41:52 ERROR TaskSchedulerImpl:Exiting due to error from cluster scheduler: All masters are unresponsive! Giving up.
Spark客户端与standalone方式部署的spark master是通过AKKA的remote actor来通信的。根据这段错误信息,我直觉认为是获取path为akka.tcp://sparkMaster@192.168.1.4:7077/user/Master的RemoteActor出现了问题。通过单步调试结合阅读源代码,我看到在standalone模式下,创建SparkContext时,会创建对应的TaskSchedulerImpl与SparkDeploySchedulerBackend对象。之后,它会执行SparkDeploySchedulerBackend的start()方法,进而跟踪到ClientActor的创建。ClientActor是一个AKKA actor,它会在启动前(actor被创建后会自动地异步方式启动)执行钩子方法preStart()。如下为Spark的源代码:
class ClientActor extends Actor with ActorLogReceive with Logging {
override def preStart() {
context.system.eventStream.subscribe(self, classOf[RemotingLifecycleEvent])
try {
registerWithMaster()
} catch {
case e: Exception =>
logWarning("Failed to connect to master", e)
markDisconnected()
context.stop(self)
}
}
def registerWithMaster() {
tryRegisterAllMasters()
import context.dispatcher
var retries = 0
registrationRetryTimer = Some {
context.system.scheduler.schedule(REGISTRATION_TIMEOUT, REGISTRATION_TIMEOUT) {
Utils.tryOrExit {
retries += 1
if (registered) {
registrationRetryTimer.foreach(_.cancel())
} else if (retries >= REGISTRATION_RETRIES) {
markDead("All masters are unresponsive! Giving up.")
} else {
tryRegisterAllMasters()
}
}
}
}
}
def tryRegisterAllMasters() {
for (masterAkkaUrl <- masterAkkaUrls) {
logInfo("Connecting to master " + masterAkkaUrl + "...")
val actor = context.actorSelection(masterAkkaUrl)
actor ! RegisterApplication(appDescription)
}
}
}
注意tryRegisterAllMasters()方法的实现以及调用。启动ClientActor时,会根据设置的重试次数,不停地去尝试注册所有的Master,实现即为调用ActorContext的actorSelection()方法,根据传入的masterAkkaUrl获得remote actor。根据actor的path,以及发送的RegisterApplicaition消息,可以了解到这个remote actor就是定义在org.apache.spark.deploy.master包中Master。
根据前面看到的错误信息,我想当然地认为是通信问题导致无法获得remote actor。然后通过单步调试,结果颠覆了我的猜测,执行到如下步骤是可以获得actor对象的:
val actor = context.actorSelection(masterAkkaUrl)
在spark master业已启动的前提下,我编写了如下程序验证了remote actor是可以正常获得:
object RemoteActorApp extends App {
val system = ActorSystem("spike-spark-issue")
val actor = system.actorSelection("akka.tcp://sparkMaster@192.168.1.4:7077/user/Master")
if (actor == null) println("null actor") else println("correct")
}
之后就是冗长而耗时的解决问题时间。无论是通过google查找解决方案,还是通过spark user list去咨询问题,又或者阅读spark源代码,种种方式不一而足,弄得我精力憔悴,费时费力,最后也没有找到解决方案。唯一找到一个相对靠谱的是Mithra在 StackOverFlow上的自问自答,同时他也将这个解决方案放到了spark user list上。他的主要总结为:
1.Make sure your spark version and version in your pom is same;
2.Hadoop version of the spark is the version with which spark is build or use spark hadoop prebuild version;
3.Update your spark-env.sh with required details:
export JAVA_HOME=/User/java/
export SPARK_MASTER_IP=xyz
export SPARK_WORKER_CORES=2
export SPARK_WORKER_INSTANCES=1
export SPARK_MASTER_PORT=7077
export SPARK_WORKER_MEMORY=4g
export MASTER=spark://${SPARK_MASTER_IP}:${SPARK_MASTER_PORT}
export SPARK_LOCAL_IP=xyz
4..Make sure you clean compile package your jar file every time before you code submit your spark application。
当然,这个帖子要解决的问题是spark-submit而非我这里说的编程方式。相同的设置下,我运行spark-submit并没有出现前面的问题。
不过他山之玉,可以攻石,不妨借鉴这里的建议。我也确认了spark的版本,sbt中依赖的spark版本为1.3.1,我运行的spark master也是同样的版本。我最初也怀疑是hadoop的问题。我在部署spark时,并没有安装hadoop。为了解决此问题,我专门安装了2.6版本的hadoop,然后执行如下命令重新编译了spark 1.3.1,从而保证hadoop版本与spark是兼容的:build/mvn -Phadoop-2.6 -Dhadoop.version=2.6.0 -DskipTests clean package
我甚至在spark-env.sh中配置了与hadoop有关的目录配置,当然也包括前面建议中提到的相关配置:
export HADOOP_CONF_DIR=/Users/zhangyi/lib/hadoop-2.6.0/etc/hadoop
export SPARK_MASTER_IP=192.168.1.4
export SPARK_MASTER_PORT=7077
export SPARK_WORKER_CORES=1
export SPARK_WORKER_MEMORY=2G
export SPARK_WORKER_INSTANCES=1
export SPARK_LOCAL_IP=192.168.1.4
不幸的是,问题依然存在!
痛定思痛,冷静下来,我在反思自己,觉得自己似乎走进了一个误区。因为有spark的源代码,有google和spark user list,我想当然地希望通过看到的错误信息去网上寻找相似的问题,从而获得解决方案。当查找没有结果时,我又过度地相信自己能够通过源代码发现一些端倪。我甚至考虑通过attach process的方式尝试着为remote actor设置断点,从而进行问题跟踪。然而,我却忘了要解决问题,首先要分析问题出现的根由,并由此进行下一步分析与判断。要做到这一点,最有效的手段其实是通过日志。
默认情况下,下载并编译后的spark并没有开启日志记录功能。spark使用了log4j记录日志,在conf目录下提供了log4j.properties.template文件。复制该文件,并命名为log4j.properties,利用默认的日志配置即可。重新运行start-all.sh脚本,启动spark master,然后再回到intellij下运行main函数。结果,让我惊奇地发现日志文件(日志文件出现在logs目录下)中出现了如下错误信息:
java.io.InvalidClassException: org.apache.spark.deploy.Command; local
class incompatible: stream classdesc serialVersionUID 8789839749593513237, local class serialVersionUID = -4145741279224749316
发生序列化问题的Command类其实是一个普通的样例类:
import scala.collection.Map
private[spark] case class Command(
mainClass: String,
arguments: Seq[String],
environment: Map[String, String],
classPathEntries: Seq[String],
libraryPathEntries: Seq[String],
javaOpts: Seq[String]) {
}
Scala的样例类(case class)自身支持了对象的序列化。为何会发生序列化不兼容的情况呢?由于两边的spark版本是完全一致的,这让我想起是否因为scala版本不一致。
阅读 Spark官方文档-Building Spark发现,Spark通过maven进行build时,默认scala版本为2.10。若要为Scala 2.11进行编译,需要运行如下命令:
dev/change-version-to-2.11.sh
mvn -Pyarn -Phadoop-2.4 -Dscala-2.11 -DskipTests clean package
编译后的spark包放在SPARK_HOME/assembly/target/scala-2.11。此时,如果运行sbin下的start-all.sh脚本,会提示找不到assembly/target/scala-2.10下的包,这是因为脚本仍然以2.10版本去启动spark。所以还需要在spark-env.sh中配置Scala版本:export SPARK_SCALA_VERSION="2.11"
待这一切配置妥当,并针对正确版本进行编译后,通过start-all.sh启动spark,然后回到IntelliJ下运行前面的一段代码,从控制台中能够看到如下信息:
/05/19 21:07:20 INFO AppClient$ClientActor: Connecting to master akka.tcp://sparkMaster@192.168.1.4:7077/user/Master...
/05/19 21:07:20 INFO SparkDeploySchedulerBackend: Connected to Spark cluster with app ID app-20150519210720-0000
如同玩游戏升级打怪,眼看这一道关卡算是通过了,还未来得及喘一口气,前方路途又出状况了。控制台不讨好地又打印出如下错误信息:
Exception in thread "main" java.lang.ClassNotFoundException: org.postgresql.Driver at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264)
由于前面的代码利用Spark提供的JDBC API访问了PostgreSQL。我虽然在build.sbt中添加了对PostgreSQL驱动的依赖,且在local模式下运行正常;但在启动spark master时,并没有在classpath下添加驱动的jar包,导致访问数据库时,无法找到数据库驱动,从而抛出此异常。解决方案是在SPARK_HOME/bin/compute-classpath.sh中将数据库驱动追加到classpath下。我们可以考虑在SPARK_HOME下创建一个libs目录,专门用于存放程序需要的外部依赖jar包。现在,把PostgreSQL的驱动程序jar包拷贝到该目录下,然后在compute-classpath.sh脚本中增加如下配置:
appendToClasspath "$FWDIR/libs/postgresql-9.4-1201-jdbc41.jar"
重新启动spark,然后运行程序,就获得了在我看来相当美妙的结果。
本文出自:http://ju.outofmemory.cn/entry/162428
解决在编程方式下无法访问Spark Master问题的更多相关文章
- k8s Nodeport方式下service访问,iptables处理逻辑(转)
原文 https://www.myf5.net/post/2330.htm k8s Nodeport方式下service访问,iptables处理逻辑 2017年07月11日 0条评论 976次阅读 ...
- ENC28J60 + M430G2553,用uip搭建http服务器,解决“在XP系统下可以访问,在Win7下不能访问”的问题
近日,用ENC28J60,在M430G2553上搭建一个简单的HTTP服务器,结果发现在XP系统下可以访问,在Win7下不能访问,非常奇葩的问题. 通过抓包,如下图,计算机(IP地址为192.168. ...
- mfc 类三种继承方式下的访问
知识点 public private protected 三种继承方式 三种继承方式的区别 public 关键字意味着在其后声明的所有成员及对象都可以访问. private 关键字意味着除了该类型的创 ...
- ASP.NET MVC下的四种验证编程方式
ASP.NET MVC采用Model绑定为目标Action生成了相应的参数列表,但是在真正执行目标Action方法之前,还需要对绑定的参数实施验证以确保其有效性,我们将针对参数的验证成为Model绑定 ...
- ASP.NET MVC下的四种验证编程方式【转】
ASP.NET MVC采用Model绑定为目标Action生成了相应的参数列表,但是在真正执行目标Action方法之前,还需要对绑定的参数实施验证以确保其有效 性,我们将针对参数的验证成为Model绑 ...
- ASP.NET MVC下的四种验证编程方式[续篇]
在<ASP.NET MVC下的四种验证编程方式>一文中我们介绍了ASP.NET MVC支持的四种服务端验证的编程方式("手工验证"."标注Validation ...
- ASP.NET MVC下的四种验证编程方式[续篇]【转】
在<ASP.NET MVC下的四种验证编程方式> 一文中我们介绍了ASP.NET MVC支持的四种服务端验证的编程方式(“手工验证”.“标注ValidationAttribute特性”.“ ...
- Vmware虚拟机下不能访问网络的解决办法之一
Vmware虚拟机下不能访问网络的解决办法之一 1.这个是默认的网络设置 2.如果不能访问网络,看下VMware相关的服务有没有打开,win+R 3.找到VMware的相关选项,全部启用(当然网络可能 ...
- 以编程方式使用 Microsoft Office Visio 2003 ActiveX 控件
以编程方式使用 Microsoft Office Visio 2003 ActiveX 控件 2007/10/29 Mark BukovecEmpire Down Development 适用于:Mi ...
随机推荐
- [C++]Linux之文件拷贝在系统调用和C库函数下的效率比较
声明:如需引用或者摘抄本博文源码或者其文章的,请在显著处注明,来源于本博文/作者,以示尊重劳动成果,助力开源精神.也欢迎大家一起探讨,交流,以共同进步- 0.0 题目: 1. 分别利用文件的系统调用r ...
- 配置mongo
Windows 平台安装 MongoDB MongoDB 下载 MongoDB 提供了可用于 32 位和 64 位系统的预编译二进制包,你可以从MongoDB官网下载安装,MongoDB 预编译二进制 ...
- android 内存回收及怎样避免内存泄露
http://blog.vunso.com/201307/android%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6%E5%8F%8A% ...
- python3: print()函数:def,end关键字介绍
print()函数是最最普通常见的函数,我们常用的方式为类似这种的没有任何设置的“ print("今天是个好日子") ” 的简单输出. 其实print()函数中含有如下几个关键字, ...
- MySql cmd下的学习笔记 —— 有关建立表的操作(有关于数据类型)
(01)建表的过程实际上是 声明字段 的过程 一. 列类型(字段): 存储同样的数据时,不同的列类型,所占据的空间和效率是不一样的,这就是建表时要考虑的意义. 二.MySQL三大列类型 数值型 ...
- Django中间件基础笔记
django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法. 在django项目的se ...
- TensorFlow tf.app&tf.app.flags用法介绍
TensorFlow tf.app&tf.app.flags用法介绍 TensorFlow tf.app argparse tf.app.flags 下面介绍 tf.app.flags.FL ...
- Linux内存分配小结--malloc、brk、mmap【转】
转自:https://blog.csdn.net/gfgdsg/article/details/42709943 http://blog.163.com/xychenbaihu@yeah/blog/s ...
- TCP、消息分包和协议设计
TCP是一种流式协议 TCP是一种面向连接的.可靠的.基于字节流的传输层通信协议. 流式协议的特点是什么?就像流水连续不断那样,消息之间没有边界.例如send了3条消息(这里的“消息”是指应用层的一个 ...
- 判断鼠标进入容器的方向小Demo
参考资料: 贤心博客:http://sentsin.com/web/112.html, Math.atan2(y,x) 解释 :http://www.w3school.com.cn/jsref/jsr ...