重启 Spring Boot 应用程序后第一次调用缓慢

Posted

技术标签:

【中文标题】重启 Spring Boot 应用程序后第一次调用缓慢【英文标题】:Slow first call after restarting Spring Boot application 【发布时间】:2019-12-10 06:30:07 【问题描述】:

我们有一个使用 Postgres、Hibernate、Jackson 和 Spring Data Rest 的 React - Spring Boot 应用程序。

在每次应用程序重新启动后,第一次调用到达我们后端服务器的 post 路由的时间很长(超过 4 秒)。对同一路由的每次后续调用都在 100 毫秒以下。

我们的目标是保证我们的用户在每次重新部署后都不会受到这些缓慢调用的影响。

我们正在考虑在每次部署后自动触发调用,以便应用程序“预热”并且我们的客户不会进行长时间的调用。

由于负载均衡器后面有多个后端服务器,我们希望直接从后端而不是客户端触发这些调用。

我们希望更好地了解可能发生的事情,以便我们更有效地做到这一点:它会是 Hibernate 吗? Spring延迟加载bean?杰克逊?

休眠二级缓存未激活。

我们希望每次都具有相同的快速响应时间(

【问题讨论】:

只需点击端点进行预热。您只是在谈论为部署/重新启动添加额外的几秒钟 - 很重要。恕我直言,不值得解决根本原因。 @Bohemian 我倾向于同意你的观点,是否有最佳实践在启动时触发应用程序内的调用? (OP 在我的团队中) @rod 我不知道任何最佳实践方法,但标准软件工程最佳实践表明将其放在应用程序之外的某个地方。我认为合乎逻辑的地方是启动 shell 脚本。您可能希望制止使负载均衡器可以看到您的服务器,以防止它在预热之前进行事务处理。我会计算出所需的平均时间/活动,然后使用两倍。 @Bohemian 有道理,谢谢! 【参考方案1】:

我们按照@willermo 的回答进行了快速更新,再加上来自另一个论坛的一些提示,使我们找到了解决问题的正确方向。

我们使用-verbose:class 标志记录了类加载,这清楚地表明问题在于类在第一次调用时被延迟加载。

为了预加载这些类,我们使用ApplicationRunner 来触发应用程序启动时的调用,正如@willermo 所建议的那样;这使我们能够确定地预热负载均衡器后面的所有服务器,每个服务器只需一次调用。

还有一些额外的障碍很容易解决:

添加 ApplicationRunner 破坏了我们所有的测试,因此我们不得不将其从测试配置文件中排除。 我们不想将这些“虚假”调用对数据库的影响持久化,因此我们将它们包装在一个最终回滚的事务中。

这是我们的最终解决方案:

@Component
@Profile("!test")
public class AppStartupRunner implements ApplicationRunner 

  // [Constructor with injected dependencies]

  @Transactional
  @Override
  public void run(ApplicationArguments args) throws Exception 
    // [Make the calls]
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();    
  


【讨论】:

我建议不要使用ApplicationRunner,而是创建一个@Component 实现ApplicationListener<ApplicationReadyEvent>。当您的应用程序准备好为请求提供服务时,将调用组件的 onApplicationEvent 函数。现在,您可以使用您的逻辑来进行初始化调用。【参考方案2】:

您可以尝试使用应用程序运行程序进行热身。

公共接口ApplicationRunner

用于指示 bean 在 SpringApplication 中包含时应该运行的接口。多个 ApplicationRunner bean 可以在同一个应用程序上下文中定义,并且可以使用 Ordered 接口或 @Order 注释进行排序。

例如

@Component
public class AppStartupRunner implements ApplicationRunner 


  @Override
  public void run(ApplicationArguments args) throws Exception 
    System.out.println("Running");
    //Make the first call here 
  

Application Runner

【讨论】:

感谢@willermo,这为我们的问题提供了正确的方向。我将我们的最终解决方案作为另一个答案发布。【参考方案3】:

我会看看你的 JDBC 池。 Spring 在填充池之前是否正在等待第一个请求?创建数据库连接是一项昂贵的操作。

如果您使用的是 Tomcat 池,请查看 initial-size 和 min-idle 属性。对于 HikariCP 池,请查看 minimum-idle。

您应该能够在测试环境中观察是否在对应用程序进行任何调用之前打开了与数据库的 JDBC 连接。

内置的 Spring Boot Actuator 健康检查端点还具有检查数据库状态的选项,并且可能有助于引导池。

【讨论】:

谢谢@jlar310,这些看起来不错,但我们发现罪魁祸首是Spring类加载。【参考方案4】:

在我的情况下,数据库连接池在第一次请求时需要时间进行初始化。

我在数据库字符串中添加了minpoolsize=10,现在第一个请求和后续请求一样快。

默认的 minpoolsize 为 0。

【讨论】:

以上是关于重启 Spring Boot 应用程序后第一次调用缓慢的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot使用Maven工具自动重启SpringBoot项目 | 热部署

在 Spring Boot Web 应用程序中刷新后的 Whitelabel 错误页面

spring boot在Eclipse中,修改代码后无需重启就生效的配置

性能 - Spring Boot - 服务器响应时间

IntelliJ IDEA 中的 Spring Boot 项目在重启后停止工作

Spring Boot 热部署