개요
실전 스프링 부트 책을 읽던 중 shutdown hook 개념과 안전 종료 개념에 대해 배울 수 있었다.
관련 개념에 대해 알아보고,
스프링 부트의 종료 로직을 좀 더 자세히 알아보자!
관련 개념
1. Shutdown Hook
- 개념: JVM이 종료되기 전에 실행되는 스레드
- 이용: 스프링 부트에서는 자동으로 shutdown hook을 등록하여 애플리케이션 컨텍스트를 안전하게 종료한다.
2. 안전 종료(Graceful Shutdown)
- 개념: 진행 중인 요청을 완료한 후 서버를 종료하는 방식
- 지원 버전: 스프링 부트 2.3부터 지원.
- 설정 방법
# application.yml
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 종료 대기 시간 (기본값: 30초)
종료 로직
1. 종료 신호 수신
종료 트리거
// 1-1. 외부 종료 신호
- SIGTERM (kill 명령어)
- SIGINT (Ctrl+C)
- System.exit() 호출
- IDE에서 애플리케이션 중지
- 도커 컨테이너 stop
// 1-2. 애플리케이션 내부 종료
- /actuator/shutdown 엔드포인트 호출 (활성화된 경우)
- SpringApplication.exit() 호출
- ConfigurableApplicationContext.close() 호출
2. Shutdown Hook 실행
JVM Shutdown Hook 등록 및 실행
// 2-1. 스프링 부트가 자동으로 등록한 Shutdown Hook 실행
public class SpringBootShutdownHook extends Thread {
@Override
public void run() {
// 애플리케이션 컨텍스트 종료 시작
applicationContext.close();
}
}
// 2-2. 커스텀 Shutdown Hook들도 동시에 실행됨
Runtime.getRuntime().addShutdownHook(customHook);
3. 안정 종료(Graceful Shutdown) 시작
웹 서버 종료 과정
// 3-1. 새로운 요청 수락 중단
server.shutdown(); // 더 이상 새로운 연결 수락 안함
// 3-2. 진행 중인 요청 완료 대기
// - 활성 연결들의 요청 처리 완료까지 대기
// - spring.lifecycle.timeout-per-shutdown-phase 시간만큼 대기
// 3-3. 타임아웃 후 강제 종료
if (timeout exceeded) {
server.shutdownNow(); // 강제 종료
}
4. ApplicationContext 종료 과정
컨텍스트 종료 이벤트 발행
// 4-1. ContextClosedEvent 발행
@EventListener
public void handleContextClosed(ContextClosedEvent event) {
System.out.println("컨텍스트 종료 이벤트 수신");
}
5. SmartLifecycle Bean 종료
Phase별 순차 종료
// 5-1. SmartLifecycle 구현체들을 Phase 순서로 종료
@Component
public class CustomLifecycle implements SmartLifecycle {
@Override
public int getPhase() {
return 1000; // 낮은 숫자부터 먼저 종료
}
@Override
public void stop() {
System.out.println("Phase " + getPhase() + " 종료");
}
}
// 종료 순서: Phase 값이 낮은 것부터 높은 순으로
// Phase -1000 → Phase 0 → Phase 1000 → Phase Integer.MAX_VALUE
6. 일반 Bean 종료
6-1. @PreDestroy 메서드 실행
// 6-1. 모든 Bean의 @PreDestroy 메서드 실행
@Component
public class ServiceBean {
@PreDestroy
public void cleanup() {
System.out.println("@PreDestroy 실행 - 리소스 정리");
// 캐시 정리, 연결 종료 등
}
}
6-2. DisposableBean 인터페이스 실행
// 6-2. DisposableBean.destroy() 메서드 실행
@Component
public class ResourceBean implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean.destroy() 실행");
// 리소스 해제 로직
}
}
6-3. @Bean destroyMethod 실행
// 6-3. @Bean 어노테이션의 destroyMethod 실행
@Configuration
public class BeanConfig {
@Bean(destroyMethod = "shutdown")
public CustomService customService() {
return new CustomService();
}
}
public class CustomService {
public void shutdown() {
System.out.println("destroyMethod 실행");
}
}
7. 인프라스트럭처 Bean 종료
데이터소스 및 연결 풀 종료
// 7-1. 데이터베이스 연결 풀 종료
HikariDataSource → 연결 풀 종료
JPA EntityManagerFactory → 엔티티 매니저 팩토리 종료
// 7-2. 메시지 브로커 연결 종료
RabbitMQ ConnectionFactory → 연결 종료
Kafka Producer/Consumer → 연결 종료
8. 스레드 풀 종료
ExecutorService 종료
// 8-1. 애플리케이션의 모든 스레드 풀 종료
@Async 스레드 풀 종료
@Scheduled 스레드 풀 종료
커스텀 ExecutorService 종료
// 8-2. 스레드 풀 종료 로직
executorService.shutdown();
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
9. 최종 정리
JVM 리소스 정리
// 9-1. 남은 리소스 정리
- 열린 파일 디스크립터 정리
- 네트워크 소켓 정리
- 임시 파일 정리
// 9-2. JVM 종료
System.exit(exitCode);'Spring Framework > Spring Boot' 카테고리의 다른 글
| [Spring Boot] 스프링 부트 이벤트 리스너 구현과 등록 (2) | 2025.07.17 |
|---|---|
| [Spring Boot] SpringApplication.run() 메서드 인자에 관해서 (0) | 2025.07.17 |
| [Spring Boot] 리액티브 프로그래밍이란? (0) | 2025.07.17 |
| [Spring Boot] SpringApplication.run()을 호출하면 일어나는 일들 (0) | 2025.07.02 |
| [Spring Boot] Jar vs War 배포 방법 비교 (0) | 2025.01.08 |