Spring Boot 冷启动在 AWS Lambda 上花费的时间太长,并且启动过程会执行两次。

huangapple go评论80阅读模式
英文:

spring boot cold start taking too long on aws lambda and boot initializes twice

问题

这很奇怪,但是我的 Spring Boot API 在部署到 AWS Lambda 上后,执行时间比预期要长得多。

在 CloudWatch 日志中,我看到 Spring Boot 启动了两次,首次使用默认配置文件,其次使用我设置的配置文件。

为什么会启动两次呢?这显著增加了时间成本。

源代码:
lambdahandler.java

public class LambdaHandler implements RequestStreamHandler {

    private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;

    static {
        try {
            handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
            handler.activateSpringProfiles("lambda");
        } catch (ContainerInitializationException e) {
            // Re-throw the exception to force another cold start
            e.printStackTrace();
            throw new RuntimeException("Could not initialize Spring Boot application", e);
        }
    }
}

application.java

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

这两个文件位于同一个包中。

config.java

@Configuration
@EnableWebMvc
@Profile("lambda")
public class Config {

    /**
     * 创建所需的 HandlerMapping,以避免创建多个默认的 HandlerMapping 实例
     */
    @Bean
    public HandlerMapping handlerMapping() {
        return new RequestMappingHandlerMapping();
    }

    /**
     * 创建所需的 HandlerAdapter,以避免创建多个默认的 HandlerAdapter 实例
     */
    @Bean
    public HandlerAdapter handlerAdapter() {
        return new RequestMappingHandlerAdapter();
    }

    // ...
    // ...
}

pom.xml

<dependency>
    <groupId>com.amazonaws.serverless</groupId>
    <artifactId>aws-serverless-java-container-spring</artifactId>
    <version>[0.1,)</version>
</dependency>
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-lambda-java-core</artifactId>
    <version>1.2.1</version>
</dependency>
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-lambda-java-events</artifactId>
    <version>3.1.0</version>
</dependency>

CloudWatch 日志:

07:16:51.546 [main] INFO com.amazonaws.serverless.proxy.internal.LambdaContainerHandler - Starting Lambda Container Handler
:: Spring Boot ::
2020-09-05 07:16:52.724  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : Starting LambdaRTEntry on 169.254.184.173 with PID 1 (/var/runtime/lib/LambdaJavaRTEntry-1.0.jar started by sbx_user1051 in /)
2020-09-05 07:16:52.726  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : No active profile set, falling back to default profiles: default
2020-09-05 07:16:52.906  INFO 1 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1e81f4dc: startup date [Sat Sep 05 07:16:52 UTC 2020]; root of context hierarchy

...
...

2020-09-05 07:16:57.222  INFO 1 --- [           main] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 40 ms
:: Spring Boot ::
2020-09-05 07:16:57.442  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : Starting LambdaRTEntry on 169.254.184.173 with PID 1 (/var/runtime/lib/LambdaJavaRTEntry-1.0.jar started by sbx_user1051 in /)
2020-09-05 07:16:57.442  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : The following profiles are active: lambda
2020-09-05 07:16:57.445  INFO 1 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5ef60048: startup date [Sat Sep 05 07:16:57 UTC 2020]; root of context hierarchy
英文:

this is strange but my spring boot api taking much longer that expected when deployed on aws lambda.
in the cloudwatch log, i see spring boot is starting up twice first with default profile and second with a profile i set.
Why should it boot twice.. that is significantly costing time..
Source Code:
lambdahandler.java

public class LambdaHandler implements RequestStreamHandler {

    private static SpringBootLambdaContainerHandler&lt;AwsProxyRequest, AwsProxyResponse&gt; handler;

    static {
        try {
            handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
            handler.activateSpringProfiles(&quot;lambda&quot;);
        } catch (ContainerInitializationException e) {
            // Re-throw the exception to force another cold start
            e.printStackTrace();
            throw new RuntimeException(&quot;Could not initialize Spring Boot application&quot;, e);
        }
    }

application.java

@SpringBootApplication
    public class Application extends SpringBootServletInitializer {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }

both these files are in the same package

config.java

@Configuration
@EnableWebMvc
@Profile(&quot;lambda&quot;)
public class Config {

    /**
     * Create required HandlerMapping, to avoid several default HandlerMapping instances being created
     */
    @Bean
    public HandlerMapping handlerMapping() {
        return new RequestMappingHandlerMapping();
    }

    /**
     * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created
     */
    @Bean
    public HandlerAdapter handlerAdapter() {
        return new RequestMappingHandlerAdapter();
    }
..
..

}

pom.xml

&lt;dependency&gt;
      &lt;groupId&gt;com.amazonaws.serverless&lt;/groupId&gt;
      &lt;artifactId&gt;aws-serverless-java-container-spring&lt;/artifactId&gt;
      &lt;version&gt;[0.1,)&lt;/version&gt;
    &lt;/dependency&gt;
&lt;dependency&gt;
      &lt;groupId&gt;com.amazonaws&lt;/groupId&gt;
      &lt;artifactId&gt;aws-lambda-java-core&lt;/artifactId&gt;
      &lt;version&gt;1.2.1&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;com.amazonaws&lt;/groupId&gt;
      &lt;artifactId&gt;aws-lambda-java-events&lt;/artifactId&gt;
      &lt;version&gt;3.1.0&lt;/version&gt;
    &lt;/dependency&gt;

cloudwatch log

    07:16:51.546 [main] INFO com.amazonaws.serverless.proxy.internal.LambdaContainerHandler - Starting Lambda Container Handler
:: Spring Boot ::                        
    2020-09-05 07:16:52.724  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : Starting LambdaRTEntry on 169.254.184.173 with PID 1 (/var/runtime/lib/LambdaJavaRTEntry-1.0.jar started by sbx_user1051 in /)
    2020-09-05 07:16:52.726  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : No active profile set, falling back to default profiles: default
    2020-09-05 07:16:52.906  INFO 1 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1e81f4dc: startup date [Sat Sep 05 07:16:52 UTC 2020]; root of context hierarchy
    
    ..
    ..
    2020-09-05 07:16:57.222  INFO 1 --- [           main] o.s.web.servlet.DispatcherServlet        : FrameworkServlet &#39;dispatcherServlet&#39;: initialization completed in 40 ms
    :: Spring Boot ::                        
    2020-09-05 07:16:57.442  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : Starting LambdaRTEntry on 169.254.184.173 with PID 1 (/var/runtime/lib/LambdaJavaRTEntry-1.0.jar started by sbx_user1051 in /)
    2020-09-05 07:16:57.442  INFO 1 --- [           main] lambdainternal.LambdaRTEntry             : The following profiles are active: lambda
    2020-09-05 07:16:57.445  INFO 1 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5ef60048: startup date [Sat Sep 05 07:16:57 UTC 2020]; root of context hierarchy

答案1

得分: 2

为什么需要启动两次?

我怀疑您的代码更改与 activateSpringProfiles 强制重新初始化有关。

handler.activateSpringProfiles(&quot;lambda&quot;);

尝试通过将活动配置文件与环境变量 SPRING_PROFILES_ACTIVE 一起设置为 Lambda 配置文件的一部分。

Java 与无服务器

如果您将 Java 用于像 AWS Lambda 这样的无服务器应用程序,我建议寻找一个支持提前编译的框架,这将极大地提升应用程序的启动速度。

例如,可以查看 Micronaut、Quarkus 并与 Graalvm 一起使用。
Spring Boot 不是与 AWS Lambda 直接配合使用的最佳选项。

英文:

Why should it boot twice ?

I suspect your code change with activateSpringProfiles force reinitialisation.

handler.activateSpringProfiles(&quot;lambda&quot;);

https://github.com/awslabs/aws-serverless-java-container/blob/master/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java#L149

Try setting active profile with env variable SPRING_PROFILES_ACTIVE as part of lambda configuration file.

Java and serverless

If you use java for serverless application like AWS lambdas I would recommend for looking a framework which supports Ahead-of-Time compilation which will boost a lot your application start.

For instance have a look at Micronaut, Quarkus using with Graalvm.
Spring Boot is not the best option using with directly with AWS lambdas.

huangapple
  • 本文由 发表于 2020年9月5日 15:44:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/63751588.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定