Scala 如何确保我的Apache Spark安装代码只运行一次?

Scala 如何确保我的Apache Spark安装代码只运行一次?,scala,apache-spark,Scala,Apache Spark,我正在用Scala编写一个Spark作业,它读取S3上的拼花文件,进行一些简单的转换,然后将它们保存到DynamoDB实例。每次它运行时,我们都需要在Dynamo中创建一个新表,因此我编写了一个Lambda函数,负责创建表。Spark作业所做的第一件事是生成一个表名,调用Lambda函数(将新表名传递给它),等待创建表,然后正常进行ETL步骤 然而,我的Lambda函数似乎一直被调用两次。我无法解释。下面是代码示例: def main(spark:SparkSession,pathToParqu

我正在用Scala编写一个Spark作业,它读取S3上的拼花文件,进行一些简单的转换,然后将它们保存到DynamoDB实例。每次它运行时,我们都需要在Dynamo中创建一个新表,因此我编写了一个Lambda函数,负责创建表。Spark作业所做的第一件事是生成一个表名,调用Lambda函数(将新表名传递给它),等待创建表,然后正常进行ETL步骤

然而,我的Lambda函数似乎一直被调用两次。我无法解释。下面是代码示例:

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*
    以实现最低特权原则)
  • 对于第二次执行,请删除
    dynamodb:description*
    ,并检查您是否为g
    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")
    }