如何让 Spring Boot 应用程序在 tomcat 失败时退出

Posted

技术标签:

【中文标题】如何让 Spring Boot 应用程序在 tomcat 失败时退出【英文标题】:How to make a Spring Boot application quit on tomcat failure 【发布时间】:2022-01-19 11:53:48 【问题描述】:

我们有一堆基于 Spring Boot 2.5.4 的微服务,还包括 spring-kafka:2.7.6spring-boot-actuator:2.5.4。所有服务都使用 Tomcat 作为 servlet 容器并启用优雅关闭。这些微服务使用 docker 进行容器化。 由于配置错误,昨天我们在其中一个容器上遇到了问题,因为它占用了一个已经从另一个容器绑定的端口。 日志状态:

Stopping service [Tomcat]
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
***************************
APPLICATION FAILED TO START
***************************

Description:

Web server failed to start. Port 8080 was already in use.

但是,由于 kafka 消费者/流,JVM 仍在运行。

我需要销毁所有东西,或者至少做一个System.exit(error-code) 来触发 docker 重启策略。我怎么能做到这一点?如果可能,使用配置的解决方案比需要开发的解决方案更好。

我开发了一个由SpringBootApplicationKafkaConsumer 类组成的最小测试应用程序,以确保问题与我们的微服务无关。结果一样。

POM 文件

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.5.4</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.kafka</groupId>
  <artifactId>spring-kafka</artifactId>
</dependency>

卡夫卡监听器

@Component
public class KafkaConsumer 

  @KafkaListener(topics = "test", groupId = "test")
  public void process(String message) 

  

application.yml

spring:
  kafka:
    bootstrap-servers: kafka:9092

日志文件

2021-12-17 11:12:24.955  WARN 29067 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'; nested exception is org.springframework.boot.web.server.PortInUseException: Port 8080 is already in use
2021-12-17 11:12:24.959  INFO 29067 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2021-12-17 11:12:24.969  INFO 29067 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-12-17 11:12:24.978 ERROR 29067 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Web server failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

2021-12-17 11:12:25.151  WARN 29067 --- [ntainer#0-0-C-1] org.apache.kafka.clients.NetworkClient   : [Consumer clientId=consumer-test-1, groupId=test] Error while fetching metadata with correlation id 2 : test=LEADER_NOT_AVAILABLE
2021-12-17 11:12:25.154  INFO 29067 --- [ntainer#0-0-C-1] org.apache.kafka.clients.Metadata        : [Consumer clientId=consumer-test-1, groupId=test] Cluster ID: NwbnlV2vSdiYtDzgZ81TDQ
2021-12-17 11:12:25.156  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.AbstractCoordinator  : [Consumer clientId=consumer-test-1, groupId=test] Discovered group coordinator kafka:9092 (id: 2147483636 rack: null)
2021-12-17 11:12:25.159  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.AbstractCoordinator  : [Consumer clientId=consumer-test-1, groupId=test] (Re-)joining group
2021-12-17 11:12:25.179  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.AbstractCoordinator  : [Consumer clientId=consumer-test-1, groupId=test] (Re-)joining group
2021-12-17 11:12:27.004  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.AbstractCoordinator  : [Consumer clientId=consumer-test-1, groupId=test] Successfully joined group with generation GenerationgenerationId=2, memberId='consumer-test-1-c5924ab5-afc8-4720-a5d7-f8107ace3aad', protocol='range'
2021-12-17 11:12:27.009  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-test-1, groupId=test] Finished assignment for group at generation 2: consumer-test-1-c5924ab5-afc8-4720-a5d7-f8107ace3aad=Assignment(partitions=[test-0])
2021-12-17 11:12:27.021  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.AbstractCoordinator  : [Consumer clientId=consumer-test-1, groupId=test] Successfully synced group in generation GenerationgenerationId=2, memberId='consumer-test-1-c5924ab5-afc8-4720-a5d7-f8107ace3aad', protocol='range'
2021-12-17 11:12:27.022  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-test-1, groupId=test] Notifying assignor about the new Assignment(partitions=[test-0])
2021-12-17 11:12:27.025  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-test-1, groupId=test] Adding newly assigned partitions: test-0
2021-12-17 11:12:27.029  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-test-1, groupId=test] Found no committed offset for partition test-0
2021-12-17 11:12:27.034  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-test-1, groupId=test] Found no committed offset for partition test-0
2021-12-17 11:12:27.040  INFO 29067 --- [ntainer#0-0-C-1] o.a.k.c.c.internals.SubscriptionState    : [Consumer clientId=consumer-test-1, groupId=test] Resetting offset for partition test-0 to position FetchPositionoffset=0, offsetEpoch=Optional.empty, currentLeader=LeaderAndEpochleader=Optional[kafka:9092 (id: 11 rack: null)], epoch=0.
2021-12-17 11:12:27.045  INFO 29067 --- [ntainer#0-0-C-1] o.s.k.l.KafkaMessageListenerContainer    : test: partitions assigned: [test-0]

【问题讨论】:

当正确集成时,这不应该发生。所以我想知道你是如何设置东西来听 kafka 的,看起来你是在 Spring(Boot)的范围/管理之外做的。 嗨@M.Deinum 我已经更新了我的问题,包括一个新启动应用程序的测试用例 【参考方案1】:

既然你已经把所有东西都容器化了,那就更简单了。

只需使用 Spring Web 设置一个小型健康检查端点,用于查看服务器是否仍在运行,例如:

@RestController(
public class HealtcheckController 

  @Get("/monitoring")
  public String getMonitoring() 
    return "200: OK";
   


然后在DockerfileHEALTHCHECK 部分中引用它。如果服务器停止,则容器将被调度为unhealthy,并重新启动:

FROM ...

ENTRYPOINT ...
HEALTHCHECK localhost:8080/monitoring

如果您不想开发任何东西,那么您可以使用任何其他您知道它应该成功回答为 HEALTCHECK 的端点,但我建议您明确地为此设置一个端点。

【讨论】:

感谢您提出的解决方案。在发布之前,我认为我们可以将actuator(已包含)与它的健康端点一起使用,但如果可能的话,我想在 spring-boot 应用程序中解决它【参考方案2】:

我向 Spring kafka 的人们提出了一个问题,结果是一个错误。无需配置,spring 应用程序应按预期退出。参考:https://github.com/spring-projects/spring-kafka/issues/2055

正如问题中所述,修复是针对 spring-kafka 2.7.x、2.6.x、2.5.x 进行的。我将升级到第一个包含修复的 2.7.x 补丁版本。

【讨论】:

以上是关于如何让 Spring Boot 应用程序在 tomcat 失败时退出的主要内容,如果未能解决你的问题,请参考以下文章

如何让 Spring Boot 应用程序在 tomcat 失败时退出

如何让 Spring Boot 使用 MultiTenantSpringLiquibase?

如何让Javamelody使用不同的端口(Spring Boot+暴露的两个HTTP端口)

如何让我的 Spring Boot 应用程序使用给定的用户名和密码登录到 keycloak?

如何让IDEA像STS那样高效调试Spring Boot程序

如何让 Spring Boot Actuator LdapHealthIndicator 与 Spring Security 的 Ldap 一起运行?