Scala 如何确保我的Apache Spark安装代码只运行一次?
我正在用Scala编写一个Spark作业,它读取S3上的拼花文件,进行一些简单的转换,然后将它们保存到DynamoDB实例。每次它运行时,我们都需要在Dynamo中创建一个新表,因此我编写了一个Lambda函数,负责创建表。Spark作业所做的第一件事是生成一个表名,调用Lambda函数(将新表名传递给它),等待创建表,然后正常进行ETL步骤 然而,我的Lambda函数似乎一直被调用两次。我无法解释。下面是代码示例:Scala 如何确保我的Apache Spark安装代码只运行一次?,scala,apache-spark,Scala,Apache Spark,我正在用Scala编写一个Spark作业,它读取S3上的拼花文件,进行一些简单的转换,然后将它们保存到DynamoDB实例。每次它运行时,我们都需要在Dynamo中创建一个新表,因此我编写了一个Lambda函数,负责创建表。Spark作业所做的第一件事是生成一个表名,调用Lambda函数(将新表名传递给它),等待创建表,然后正常进行ETL步骤 然而,我的Lambda函数似乎一直被调用两次。我无法解释。下面是代码示例: def main(spark:SparkSession,pathToParqu
def main(spark:SparkSession,pathToParquet:String){
//生成唯一的表名
val tableName=generateTableName()
//调用lambda函数
val结果=callLambdaFunction(表名)
//等待创建表
waitForTableCreation(表名)
//普通ETL管道
var parquetRDD=spark.read.parquet(pathToParquet)
val transformeddd=parquetRDD.map((行:行)=>transformData(行),编码器=kryo[(文本,dynamodbitemwriteable)])
transformeddd.saveAsHadoopDataset(getConfiguration(tableName))
spark.sparkContext.stop()
}
等待表创建的代码非常简单,如您所见:
def waitForTableCreation(表名:字符串){
val client:AmazonDynamoDB=AmazonDynamoDBClientBuilder.defaultClient()
val waiter:waiter[descripbetablerequest]=客户端.waiters().tableExists()
试一试{
run(新的WaiterParameters[DescriptableRequest](新的DescriptableRequest(tableName)))
}抓住{
案例示例:WaiterTimedOutException=>
LOGGER.error(“等待创建表时超时:“+tableName”)
投手
案例t:Throwable=>throw t
}
}
lambda调用同样简单:
def callLambdaFunction(表名:字符串){
val myLambda=LambdaInvokerFactory.builder()
.lambdClient(AWSLambdaClientBuilder.defaultClient)
.lambdaFunctionNameResolver(新的LambdaByName(LAMBDA_函数_名称))
.build(类[MyLambdaContract])
调用(新的MyLambdaInput(表名))
}
正如我所说,当我在这段代码上运行spark submit
时,它肯定会命中Lambda函数。但我无法解释为什么它会击中两次。结果是我得到了在DynamoDB中配置的两个表
在将此作为Spark作业运行的上下文中,等待步骤似乎也失败了。但是,当我对我的等待代码进行单元测试时,它本身似乎工作得很好。它将成功阻止,直到表准备就绪
起初,我从理论上推测,可能是spark submit
正在将此代码发送到所有工作节点,而这些节点正在独立运行整个过程。最初,我有一个火花簇,有一个主火花簇和两个工作火花簇。然而,我在另一个有1个主节点和5个工作节点的集群上测试了这一点,它再次准确地命中Lambda函数两次,然后显然没有等待表的创建,因为它在调用Lambda后不久就死了
有人知道Spark可能在做什么吗?我错过了什么明显的东西吗
更新:这是我的spark提交参数,在EMR的步骤选项卡上可见
spark提交--部署模式集群--类com.mypackage.spark.mymain类s3://my bucket/my-spark-job.jar
下面是我的getConfiguration
函数的代码:
def getConfiguration(tableName:String):JobConf={
val conf=新配置()
conf.set(“dynamodb.servicename”、“dynamodb”)
conf.set(“dynamodb.input.tableName”,tableName)
conf.set(“dynamodb.output.tableName”,tableName)
conf.set(“dynamodb.endpoint”https://dynamodb.us-east-1.amazonaws.com")
形态集合(“区域性发电机”,“美国东部-1”)
conf.set(“mapred.output.format.class”,“org.apache.hadoop.dynamodb.write.DynamoDBOutputFormat”)
conf.set(“mapred.input.format.class”,“org.apache.hadoop.dynamodb.read.DynamoDBInputFormat”)
新作业配置(配置)
}
这里还包含了我在尝试运行时看到的一些异常日志。感谢@soapergem添加日志和选项。我添加了一个答案(一个尝试性的答案),因为它可能比评论长一点:) 总结:
和配置选项没有什么奇怪之处spark submit
- 在中,您可以看到应用程序执行了两次。它两次从接受状态转移到运行状态。这与EMR默认值是一致的()。为了确认这一点,您可以检查是否在执行该步骤后创建了2个表(我假设您在这里生成的表具有动态名称;每次执行一个不同的名称,在重试的情况下,该名称应提供2个不同的名称)
spark submit
的机器与EMR作业流具有不同的IAM策略。我在这里的假设是,不允许您的jobflow角色dynamodb:description*
,这就是为什么您会得到500 code
的例外(根据您的要点):
为了确认这一假设,您可以执行创建表的部分并在本地等待创建(这里没有Spark代码,只需主函数的一个简单的java
命令),并且:
- 对于第一次执行,请确保您拥有所有权限。在我看来,这将是
关于dynamodb:description*
(如果这是原因,那么您应该使用一些资源:
以实现最低特权原则)资源:在生产中测试Emr*
- 对于第二次执行,请删除
,并检查您是否为gdynamodb:description*
Caused by: com.amazonaws.services.dynamodbv2.model.ResourceNotFoundException: Requested resource not found: Table: EmrTest_20190708143902 not found (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ResourceNotFoundException; Request ID: V0M91J7KEUVR4VM78MF5TKHLEBVV4KQNSO5AEMVJF66Q9ASUAAJG) at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1712) at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1367) at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1113) at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:770) at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:744) at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:726) at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:686) at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:668) at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:532) at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:512) at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.doInvoke(AmazonDynamoDBClient.java:4243) at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.invoke(AmazonDynamoDBClient.java:4210) at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.executeDescribeTable(AmazonDynamoDBClient.java:1890) at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.describeTable(AmazonDynamoDBClient.java:1857) at org.apache.hadoop.dynamodb.DynamoDBClient$1.call(DynamoDBClient.java:129) at org.apache.hadoop.dynamodb.DynamoDBClient$1.call(DynamoDBClient.java:126) at org.apache.hadoop.dynamodb.DynamoDBFibonacciRetryer.runWithRetry(DynamoDBFibonacciRetryer.java:80)
def main(args: Array[String]) = { // generate a unique table name val tableName = generateTableName() // call the lambda function val result = callLambdaFunction(tableName) // wait for the table to be created waitForTableCreation(tableName) val latch = new CountDownLatch(1); val handle = new SparkLauncher(env) .setAppResource("/path/to/spark-app.jar") .setMainClass("com.company.SparkApp") .setMaster("yarn") .setDeployMode("cluster") .setConf("spark.executor.instances", "2") .setConf("spark.executor.cores", "2") // other conf ... .setVerbose(true) .startApplication(new SparkAppHandle.Listener { override def stateChanged(sparkAppHandle: SparkAppHandle): Unit = { latch.countDown() } override def infoChanged(sparkAppHandle: SparkAppHandle): Unit = { } }) println("app is launching...") latch.await() println("app exited") }