ECS部署的Spring WebFlux应用@PreDestroy在实例终止时未触发
我有一个部署在ECS上的Spring WebFlux应用,期望在实例缩容或终止(部署等场景)时,执行Processor.java中的@PreDestroy方法。本地在IntelliJ中运行应用,停止时@PreDestroy注解方法可正常触发,但部署到ECS后该方法不生效。我了解到通过shell脚本运行应用时,shell会吸收信号不传递给应用,因此在entrypoint.sh中使用了exec命令,但问题仍未解决。
相关代码
Dockerfile
ARG OPENJDK_DIGEST FROM openjdk:8-jre@${OPENJDK_DIGEST} LABEL org.opencontainers.image.authors="xxxx.xxx.com" VOLUME /tmp ARG JAR_FILE ARG PROFILE ADD ${JAR_FILE} app.jar ENV PROFILE_ENV=${PROFILE} EXPOSE 8080 COPY entrypoint.sh / COPY job.sh / COPY tini-arm64 / ENTRYPOINT ["/entrypoint.sh"]
entrypoint.sh
# Execute the Java application exec java -Dspring.profiles.active=${PROFILE_ENV} \ -XX:+UseG1GC \ -Djava.security.egd=file:/dev/./urandom \ -Dloader.main=com.paytmlabs.adtech.adtechdecisionengine.AdTechDecisionEngineApplication.java \ -XX:CompressedClassSpaceSize=2g \ -jar app.jar
Processor.java
@Slf4j @Component @DependsOn({"dep1", "dep2", "dep3", "dep4"}) public class AuditSinkEventProcessor extends SinkEventProcessor { . . . @PreDestroy public void destroy() { this.stopSink(); this.processParquetFiles(true); } }
调整ECS任务停止超时时间
ECS默认停止超时为30秒,若@PreDestroy方法执行耗时超过这个时间,ECS会直接发送SIGKILL强制终止进程,导致清理逻辑无法完成。在任务定义中修改stopTimeout参数,设置为足够覆盖清理逻辑的时长(如60秒)。启用Tini处理信号传递
容器中已包含tini-arm64,但未启用。Tini作为PID 1进程能更可靠地转发信号给子进程。修改Dockerfile的ENTRYPOINT:ENTRYPOINT ["/tini-arm64", "--", "/entrypoint.sh"]验证信号是否到达Java进程
在entrypoint.sh开头添加信号捕获日志,确认SIGTERM是否被接收:trap 'echo "Received SIGTERM, passing to Java process"' SIGTERM同时进入容器执行
ps aux,确认Java进程是PID 1(exec命令应保证这点,但需验证)。补充Spring关闭事件监听器
为确保关闭逻辑被触发,添加ContextClosedEvent监听器作为@PreDestroy的补充:@Component public class AppShutdownListener implements ApplicationListener<ContextClosedEvent> { @Autowired private AuditSinkEventProcessor auditProcessor; @Override public void onApplicationEvent(ContextClosedEvent event) { log.info("Triggering shutdown cleanup via ContextClosedEvent"); auditProcessor.destroy(); } }手动测试信号响应
进入运行中的ECS容器,执行kill -TERM <java-pid>,查看应用日志是否触发destroy()方法。如果本地容器测试正常但ECS上无响应,排查ECS任务的网络或权限配置。增强日志排查
在destroy()方法中添加详细日志,比如:@PreDestroy public void destroy() { log.info("Starting pre-destroy cleanup: stopping sink"); this.stopSink(); log.info("Processing remaining parquet files"); this.processParquetFiles(true); log.info("Pre-destroy cleanup completed"); }通过日志判断方法是否被调用、是否执行完成。
内容的提问来源于stack exchange,提问作者Gurucharan Sharma

