Tomcat需要很长时间才能识别spring-boot应用程序的启动

Posted

技术标签:

【中文标题】Tomcat需要很长时间才能识别spring-boot应用程序的启动【英文标题】:Tomcat requires long time to recognizes the start of spring-boot application 【发布时间】:2021-09-22 15:33:06 【问题描述】:

在将打包为warSpringBootApplication 部署到tomcat 服务器时遇到一个奇怪的问题。

虽然应用程序启动成功,但有时tomcat服务器无法识别应用程序启动(或者更准确地说,它需要很长时间)。 - 如果我说成功,我的意思是应用程序可以执行备份作业,例如cron 工作,访问其数据库并通过 SOAP 连接到第 3 方服务。

tomcat 服务器无法识别启动时的问题是应用程序的REST 端点无法访问,更糟糕的是,如果我在应用程序放在 webapps 文件夹时重新启动了 tomcat 服务器,整个 tomcat 服务器将被冻结。

说实话,我不知道可能是什么问题,甚至不知道从哪里开始寻找。

我怀疑,不知何故,自定义ThreadPoolTaskScheduler(见下文)会触发此问题。但即使我将池大小设置为 1,也会出现此问题。

从显示启动时间的日志文件中提取:

    正常启动: 应用程序日志文件:Started CrewAlertApplication in 17.358 seconds (JVM running for 58.215) catalina.out: Deployment of web application archive [/srv/prod/cpappp/tomcat/webapps/crew-alert##1.2.1.war] has finished in [21.691] ms 启动缓慢: 应用程序日志文件:Started CrewAlertApplication in 25.161 seconds (JVM running for 75.633) catalina.out: Deployment of web application archive [/srv/prod/cpappp/tomcat/webapps/crew-alert##1.2.1.war] has finished in [1.056.389] ms

这里有一些关于环境的信息:

Tomcat(在VMWare 机器上运行,有 4 个内核)

Server Version:        Apache Tomcat/9.0.43
Server built:          Jan 28 2021 20:25:45 UTC
Server version number: 9.0.43.0
OS Name:               Linux
OS Version:            5.8.0-0.bpo.2-amd64
Architektur:           amd64
Java Home:             /srv/jdk-11.0.9+11
JVM Version:           11.0.9+11
JVM Hersteller:        Eclipse OpenJ9

应用程序本身基于:

SpringBoot:2.0.5 java: 11 如上所述,我们使用自定义ThreadPoolTaskScheduler
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() 
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(5);
    threadPoolTaskScheduler.setThreadNamePrefix("CrewAlertThreadPool");
    threadPoolTaskScheduler.setRemoveOnCancelPolicy(true);
    return threadPoolTaskScheduler;


下图显示了 Tomcat 管理器。启动tomcat,然后部署应用程序(部署在tomcat上的其他应用程序都启动成功)。

一段时间后,tomcat 管理器显示应用程序已成功启动


更新 1:

正如@AndyWilkinson 指出的那样,问题出现在我的@EventListener 方法中,该方法调用了JpaRepository 方法

List<ScheduledNotification> findByScheduledTimeBetween(ZonedDateTime from, ZonedDateTime to)

被“翻译”成

Hibernate: select scheduledn0_.id as id1_6_, scheduledn0_.alert_id as alert_id3_6_, scheduledn0_.scheduled_time as scheduled_time2_6_ 
    from scheduled_notification scheduledn0_
    where scheduledn0_.scheduled_time between ? and ?

以下是实体的摘录:

@Entity(name = "SCHEDULED_NOTIFICATION")
@Table(indexes =  @Index(name = "IDX_SCHED_NOTIF_SCHEDTIME", columnList = "SCHEDULED_TIME") )
public class ScheduledNotification extends AbstractId implements Comparable<ScheduledNotification> 
    @ManyToOne(optional = false)
    @JoinColumn(name = "ALERT_ID", nullable = false)
    private Alert alert;

    @Column(name = "SCHEDULED_TIME", nullable = false, columnDefinition = "TIMESTAMP")
    @Convert(converter = ZonedDateTimeConverterUtc.class)
    private ZonedDateTime scheduledTime;

    @OneToMany(mappedBy = "scheduledNotification", cascade = ALL, orphanRemoval = true, fetch = EAGER)
    private Set<Leg> legs;
    ...

这个电话实际上需要将近 18 分钟。这有点令人惊讶,因为该表从未包含超过 100 个条目(在本例中返回 3 个结果)。

2021-07-14 12:53:34.506  INFO [] --- [Catalina-utility-3] c.lalacomp.alert.CrewAlertApplication    : Started CrewAlertApplication in 26.577 seconds (JVM running for 108237.294)
2021-07-14 12:53:34.509  INFO [] --- [Catalina-utility-3] c.l.a.scheduler.NotificationExecutor     : ApplicationReadyEvent received.
2021-07-14 12:53:34.528 DEBUG [] --- [Catalina-utility-3] c.l.a.s.ScheduledNotificationServiceImpl : findImmediate() - Looking up schedules between: 2021-07-14T10:38:34Z[UTC] and 2021-07-14T11:08:34Z[UTC]
2021-07-14 12:53:34.572  INFO [] --- [Catalina-utility-3] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
...
2021-07-14 13:11:27.637 TRACE [] --- [Catalina-utility-3] c.l.a.s.ScheduledNotificationServiceImpl : We've found: ...
2021-07-14 13:11:27.639  INFO [] --- [Catalina-utility-3] c.l.a.scheduler.NotificationExecutor     : Adding 3 new ScheduledNotifications at startup.
...

等等,时间戳确实匹配,应用程序被识别为正在运行:

14-Jul-2021 13:11:27.662 INFORMATION [Catalina-utility-3] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/srv/prod/cpappp/tomcat/webapps/crew-alert##1.2.2.war] has finished in [1.103.405] ms

所以现在我必须弄清楚,为什么这需要这么长时间?因为对数据库(其他表)的其他查询正常执行。

更新 2:

如果我直接在控制台上运行类似的sql 语句,运行时绝对没问题。

select * from scheduled_notification sn
    join leg l on sn.id = l.SCHEDULED_NOTIFICATION_ID
    join alert a on sn.ALERT_ID = a.id
    where scheduled_time between SYSTIMESTAMP and SYSTIMESTAMP + 2 /24;

Fetched 30 rows in 0.015 secs

我还想知道为什么上面的 Hibernate 语句(更新 1)不包括“加入关系”。

【问题讨论】:

当应用程序似乎已启动但 Tomcat 没有注意到时,您能否进行线程转储? 谢谢@AndyWilkinson,你可以在这里找到转储:dropbox.com/s/qb8edzl976wehnd/… Catalina-utility-3 线程正在部署应用程序。线程转储显示您的应用程序有一个ApplicationReadyEvent 的侦听器。此侦听器调用了一些使用 Hibernate 与您的 Oracle DB 对话的代码,这就是它在执行线程转储时所做的事情。如果这需要很长时间,则不可能从单个转储中判断出来,但这就是我要开始调查的地方。也许您可以启用一些 Hibernate 或 Oracle JDBC 驱动程序日志记录,以了解与 DB 对话时所花费的时间。 正确,@EventListener 方法花费了这么多时间 - 或者更准确地说是调用 JpaRepository 方法 findByScheduledTimeBetween(ZonedDateTime from, ZonedDateTime to) - 这有点令人惊讶,因为这个表只包含最多最大限度。 100 个条目。该方法在将近 18 分钟后返回。我将使用日志文件中的摘要更新问题。 【参考方案1】:

正如@AndyWilkinson 指出的,问题出在ApplicationReadyEvent。在连接到此事件的方法中,我从数据库中获取数据,由于一些昂贵的关系,这花费了很长时间。

我更新了实体并简化了其中的一些关系。现在该方法按预期执行。

非常感谢 Andy!

【讨论】:

以上是关于Tomcat需要很长时间才能识别spring-boot应用程序的启动的主要内容,如果未能解决你的问题,请参考以下文章

Tomcat 上的战争部署需要很长时间

数据库项目需要很长时间才能打开

分享意图需要很长时间才能出现

简单的选择需要很长时间才能执行

PHP页面需要很长时间才能加载

PayPal API 需要很长时间才能响应