我想调试在AWS EMR集群上运行的Spark应用程序.如果我可以使用IntelliJ远程连接和调试它将是太棒了.我搜索过但发现很少.
有可能,如果是这样,有人可以指出我正确的方向吗?
谢谢.
首先,我要提醒您,由于众多错误和AWS EMR的意外使用案例,您尝试做的事情基本上是不可能的.我强烈建议您支付最大的单个实例来运行您的工作(他们拥有c4.8xlarge
经济实惠的终端和x1.32xlarge
真正的疯狂!),只需spark
在该实例内部安装并运行您的工作.
您的VPC必须正确配置,以允许任何与外界的连接.这意味着您的Internet网关正常工作.您可以通过启动具有EC2密钥对的群集来进行测试,修改主服务器的安全组以允许来自您的计算机的SSH连接(默认情况下它们自然不会这样做)并尝试从您的计算机连接到主服务器.如果你不能这样做,你将无法调试.我甚至无法在没有其他配置的新集群上满足此先决条件!
必须可以从Internet访问运行IntelliJ以进行调试的计算机.要对此进行测试,请修改主实例的安全组,以允许在端口5005 nc -l 5005
上与计算机建立出站连接.然后,在您的计算机上运行.SSH进入你的主人并尝试echo "test" | nc your_ip_address 5005
.test
在您的机器终端上看到之前,请不要继续.
IntelliJ设置
创建新的远程配置.将调试器模式更改为Listen.命名配置并保存.当你点击调试时,它将等待连接.在该窗口中,您将看到"运行远程JVM的命令行参数",读取如下内容:
-agentlib:jdwp=transport=dt_socket,server=n,address=localhost:5005,suspend=y
你可以像我一样删除onthrow
和oncaught
行.假设您的调试机器可通过Internet访问24.13.242.141
.假装它实际上是读:
-agentlib:jdwp=transport=dt_socket,server=n,address=24.13.242.141:5005,suspend=y
我们将使用它来设置Spark进程的调试.
Spark设置有两个可以调试的进程:驱动程序进程(执行SparkContext
实例化的代码)和执行程序进程.最终,您将这些JVM选项传递给特殊参数以spark-submit
使连接发生.要调试驱动程序,请使用
spark-submit --driver-java-options -agentlib:jdwp=transport=dt_socket,server=n,address=24.13.242.141:5005,suspend=y --class ...
对于调试执行程序进程,您将使用配置选项:
spark-submit --conf "spark.executor.extraJavaOptions=-agentlib:jdwp=transport=dt_socket,server=n,address=24.13.242.141:5005,suspend=y" --class ...
调试执行程序是非常棘手的,因为会有多个进程.您无法以IntelliJ中的想象方式真正调试多个进程.此外,您无法在AWS EMR中将执行程序的数量限制为1,即使他们声称您可以.我相信如果其他执行程序失败(他们将无法连接到您的调试会话时),这是可以的.但这一步未经测试.
把它们放在一起您可以spark-submit
使用SDK和Web控制台修改两者的参数.请注意,在SDK中,您不应该尝试自己连接"args" - 将它们作为数组项传递给它,就像它要求的那样.
您需要从群集开始时修改主安全组,以便调试驱动程序(同样使用从属安全组来调试执行程序).创建一个安全组,允许出站连接到调试器的IP地址和端口(即TCP Outbound到24.13.242.141:5005).您应该使用该条目创建一个安全组,并使用AWS SDK(.withAdditionalMasterSecurityGroups(...)
)将其添加到主/从作业流实例配置的安全组.我不知道如何从Web控制台执行此操作.
确保使用Gradle生成带classpath "com.github.jengelman.gradle.plugins:shadow:1.2.4"
插件的阴影罐.另外,启用Zip64
.您将把:shadowJar
任务结果上传到S3以在AWS EMR上实际执行.
buildscript {
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "com.github.jengelman.gradle.plugins:shadow:1.2.4"
}
}
apply plugin: "com.github.johnrengelman.shadow"
shadowJar {
zip64 true
}
确保使用--deploy-mode cluster
和--master yarn
(基本上没有文档)启动Spark应用程序.
为了从EMR中的驱动程序或执行程序内部访问S3,请不要进行修改sc.hadoopConfiguration()
(例如,configuration.set("fs.s3n.impl", "org.apache.hadoop.fs.s3native.NativeS3FileSystem");
).根本不要配置这些属性!hadoop-aws
默认情况下,在EMR环境中正常工作,并自动设置相应的属性.
将log4j
日志记录选项设置为仅报告WARN
和更高.在此SDK中,您将执行以下操作:
.withConfigurations(new Configuration()
.withClassification("spark-log4j")
.addPropertiesEntry("log4j.rootCategory", "WARN, console"))
containers/applications_.../container.../stderr.gz
在打扰调试之前,请检查日志中的错误!
如果您看到此错误,"WARN YarnClusterScheduler:初始作业未接受任何资源;检查您的集群UI以确保工作者已注册并具有足够的资源",请在容器日志中确保添加分类的maximizeResourceAllocation
配置属性spark
.
new Configuration()
.withClassification("spark")
.addPropertiesEntry("maximizeResourceAllocation", "true"))
不要忘记在驱动程序结束时关闭上下文(sc.close()
).否则,Yarn永远不会开始.好笑无言.
shadow JAR中的资源只能由与资源相同的"JAR"内的类加载.换句话说,不要使用ClassLoader.getSystemClassLoader()
.如果class A
通常a.jar
想要访问资源b.jar
,并且class B
是一个类b.jar
,请使用B.class.getClassLoader().getResource...
.此外,使用相对路径(省略资源引用开头的正斜杠).我建议捕捉NullPointerException
并尝试两者,这样无论打包方式如何,你的JAR都能正常工作.
如果您使用实现Function
接口和类似的类,请确保创建一个无参数构造函数,执行您可能依赖的所有初始化.Spark对闭包和函数实例使用Kryo序列化(而不是Java序列化),如果你忽略了使用特定于应用程序的初始化代码(例如,从资源加载)提供无参数构造函数,则不会执行所有操作你期望的初始化.