【发布时间】:2021-09-22 15:33:06
【问题描述】:
在将打包为war 的SpringBootApplication 部署到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上的其他应用程序都启动成功)。
更新 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 分钟后返回。我将使用日志文件中的摘要更新问题。
标签: java spring-boot tomcat tomcat9