使用AWS Java SDK V2调用的AWS Lambda函数超时

0

【以下的问题经过翻译处理】 我创建了一个使用Python编写的AWS Lambda函数,它从一个S3存储桶中读取一个tar.gz文件,并将其解压,然后将提取的文件写入另一个S3存储桶中。GZ文件中的Tar大小大于1GB,因此Lambda需要更长时间来完成任务。我从Java客户端调用此Lambda函数。我使用AWS SDK V2 for Java(software.amazon.awssdk.*),使用Lambda同步客户端 software.amazon.awssdk.services.lambda.LambdaClient。

尽管Lambda调用有效(lambdaClient.invoke(invokeRequest)),但它会失败,并显示“读取超时”的错误。在后台(在AWS中),Lambda在一段时间后完成执行。

以下是Lambda客户端bean创建代码:

LambdaClient lambdaClient = LambdaClient.builder()
        		.credentialsProvider(awsCredentialsProvider)
                .region(Region.US_EAST_1)
                .overrideConfiguration(ClientOverrideConfiguration.builder()
                		.apiCallTimeout(Duration.ofMinutes(30))
                		.apiCallAttemptTimeout(Duration.ofMinutes(30))
                        .build()
                )
                .build();

以下是Lambda调用代码:

//这是一个自定义的pojo对象,它将映射到Lambda输入json负载
UntarLambdaPayload untarLambdaPayload = UntarLambdaPayload.builder()
                .sourceBucket(lambdaProps.getSourceBucket())
                .destinationBucket(lambdaProps.getDestinationBucket())
                .sourceKey("myTarFile.tar.gz")
                .build();

ObjectMapper mapper = new ObjectMapper();
String jsonRequest = mapper.writeValueAsString(untarLambdaPayload);
SdkBytes payload = SdkBytes.fromUtf8String(jsonRequest);
            
InvokeRequest invokeRequest = InvokeRequest.builder()
                    .functionName(lambdaProps.getFunctionName())
                    .overrideConfiguration(AwsRequestOverrideConfiguration.builder()
                            .apiCallTimeout(Duration.ofMinutes(30))
                            .apiCallAttemptTimeout(Duration.ofMinutes(30)).build())
                    .payload(payload)
                    .build();
            
InvokeResponse res = lambdaClient.invoke(invokeRequest);

我收到以下exception:

software.amazon.awssdk.core.exception.SdkClientException: Unable to execute HTTP request: Read timed out
	at software.amazon.awssdk.core.exception.SdkClientException$BuilderImpl.build(SdkClientException.java:102)
	at software.amazon.awssdk.core.exception.SdkClientException.create(SdkClientException.java:47)
	at software.amazon.awssdk.core.internal.http.pipeline.stages.utils.RetryableStageHelper.setLastException(RetryableStageHelper.java:204)
	at software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage.execute(RetryableStage.java:83)
	at software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage.execute(RetryableStage.java:36)
	at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206)
	at software.amazon.awssdk.core.internal.http.StreamManagingStage.execute(StreamManagingStage.java:56)
	at software.amazon.awssdk.core.internal.http.StreamManagingStage.execute(StreamManagingStage.java:36)
	at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.executeWithTimer(ApiCallTimeoutTrackingStage.java:80)
	at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.execute(ApiCallTimeoutTrackingStage.java:60)
	at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.execute(ApiCallTimeoutTrackingStage.java:42)
	at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage.execute(ApiCallMetricCollectionStage.java:48)
	at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage.execute(ApiCallMetricCollectionStage.java:31)
	at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206)
	at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206)
	at software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage.execute(ExecutionFailureExceptionReportingStage.java:37)
	at software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage.execute(ExecutionFailureExceptionReportingStage.java:26)
	at software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient$RequestExecutionBuilderImpl.execute(AmazonSyncHttpClient.java:193)
	at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.invoke(BaseSyncClientHandler.java:103)
	at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.doExecute(BaseSyncClientHandler.java:167)
	at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.lambda$execute$1(BaseSyncClientHandler.java:82)
	at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.measureApiCallSuccess(BaseSyncClientHandler.java:175)
	at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.execute(BaseSyncClientHandler.java:76)
	at software.amazon.awssdk.core.client.handler.SdkSyncClientHandler.execute(SdkSyncClientHandler.java:45)
	at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler.execute(AwsSyncClientHandler.java:56)
	at software.amazon.awssdk.services.lambda.DefaultLambdaClient.invoke(DefaultLambdaClient.java:2355)

我的代码的进一步逻辑依赖于 Lambda 函数的成功完成。如果 Lambda 超时,则代码无法继续处理 S3 bucket#2 中的解压文件。我尝试使用 InvokeRequest 中的 overrideConfiguration,包括 apiCallTimeout 和 apiCallAttemptTimeout(以及在 Lambda 客户端中),但没有成功。我将要研究 Lambda 客户端的等待功能,目前还没有找到关于如何与 Lambda 一起使用的帮助。 我该如何让 lambdaClient.invoke(invokeRequest) 在 Lambda 在 AWS 中完成执行之前等待?

profile picture
专家
已提问 5 个月前37 查看次数
1 回答
0

【以下的回答经过翻译处理】 你的代码客户端在哪里运行的(使用Java SDK的应用)?可能正在发生的是你正在遇到TCP Keepalive超时。在SDK等待Lambda回复时,TCP连接没有任何流量传送。可能你途经使用的网络设备在一段时间后关闭空闲连接。例如,如果你的应用程序在VPC环境中运行,并使用NAT网关或VPC终端节点用于对Lambda的出站连接,则空闲连接将在350秒后被关闭。在这种情况下,行为将与你所观察到的一致 - Lambda一侧将继续运行,而客户端一侧最终会超时。

如果上述描述符合你的情况,你可以尝试通过以下两个步骤来解决问题 - (1)减少操作系统级别的TCP Keepalive时间 (2)启用Java SDK中的TCP Keepalive。以下我将详细介绍这两个步骤。

注意:这些步骤使用高级技术,改变了操作系统级别TCP keepalive设置和应用级别HTTP客户端配置的默认行为。请仅将以下描述用作参考,并确保如果您决定将其应用于实际环境中,确保您知道自己在做什么。这是一篇很好的文章,解释了TCP Keepalive

1.默认情况下,Linux环境通常配置为具有7200秒(2小时)的TCP Keepalive间隔,在你的场景中显然过高。你可以通过运行以下命令查看配置:

cat /proc/sys/net/ipv4/tcp_keepalive_time
cat /proc/sys/net/ipv4/tcp_keepalive_intvl

您可以通过将以下属性添加到/etc/sysctl.conf并重新启动系统来更新这些设置。您可能希望使用不同的值。如果您的路径上有NAT网关或VPC端点,请确保您的保持连接时间低于350秒。其他网络设备可能具有不同的超时时间。下面的配置意味着在开始发送保持连接探测之前等待120秒,然后以30秒的间隔发送它们。

net.ipv4.tcp_keepalive_time=120
net.ipv4.tcp_keepalive_intvl=30

重新启动后,运行相同的cat命令以确保应用了新的设置。 2.构建一个自定义的ApacheHttpClient,启用TCP Keepalive,并在构建Lambda客户端时使用它。请参阅下面的参考资料。

ApacheHttpClient.Builder apacheHttpClientBuilder = ApacheHttpClient.builder();
        apacheHttpClientBuilder.connectionMaxIdleTime(Duration.ofSeconds(900));
        apacheHttpClientBuilder.connectionTimeToLive(Duration.ofSeconds(900));
        apacheHttpClientBuilder.socketTimeout(Duration.ofSeconds(900));
        apacheHttpClientBuilder.tcpKeepAlive(true);
        SdkHttpClient sdkHttpClient = apacheHttpClientBuilder.build();

        RetryPolicy retryPolicy = RetryPolicy.builder().numRetries(0).build();
        ClientOverrideConfiguration clientOverrideConfiguration = ClientOverrideConfiguration.builder()
                .apiCallAttemptTimeout(Duration.ofSeconds(900))
                .apiCallTimeout(Duration.ofSeconds(900))
                .retryPolicy(retryPolicy)
                .build();

        LambdaClientBuilder lambdaClientBuilder = LambdaClient.builder();

        lambdaClientBuilder.overrideConfiguration(clientOverrideConfiguration);
        lambdaClientBuilder.httpClient(sdkHttpClient);

        LambdaClient lambdaClient = lambdaClientBuilder.build();
        InvokeRequest invokeRequest = InvokeRequest.builder().functionName(FUNCTION_NAME).build();
        InvokeResponse invokeResponse = lambdaClient.invoke(invokeRequest);

ApacheHttpClient class 来源于 https://mvnrepository.com/artifact/software.amazon.awssdk/apache-client

profile picture
专家
已回答 5 个月前

您未登录。 登录 发布回答。

一个好的回答可以清楚地解答问题和提供建设性反馈,并能促进提问者的职业发展。

回答问题的准则