Spring Boot Graceful shutdown 동작과정
개요
Spring Boot Application에서 Controller가 요청을 처리하고 응답이 되지 않았는데 종료요청이 도달하면 어떻게 될까요?
Client는 응답을 받지 못하고 timeout이 발생합니다.
Spring Boot 2.3에서 제공하는 graceful shutdown에 대해 알아보고, Spring Boot Application이 종료되는 과정을 분석해 보겠습니다.
Shutdown
public enum Shutdown {
//요청들이 완료될때 까지 기다렸다가 종료
GRACEFUL,
//즉시 종료
IMMEDIATE;
}
shutdown 전략에는 IMMEDIATE와 GRACEFUL이 존재합니다.
기본값은 IMMEDIATE로 설정되어 있고, GRACEFUL으로 설정할 시에는 새로운 요청을 거부하고 기존 요청에 대해서는 완전히 처리를 진행하고 서버종료를 마무리합니다.
application.yml 예시
server:
shutdown: graceful
타임아웃 지정
spring:
lifecycle:
timeout-per-shutdown-phase: 60s
진행 중인 요청이 영원히 끝나지 않는 작업이라면? 영원히 프로세스가 종료되지 않을 수 있습니다.
이런 상황을 방지하기 위해 타임아웃을 설정할 수 있습니다.
기본값은 30초이고 해당 설정은 60초 동안 요청을 모두 처리하고 서버를 종료하는 것을 의미합니다.
하지만 만약 처리하는데 60초가 초과 되는 경우라면 해당 요청에 대한 응답은 클라이언트가 받지 못하므로 요청의 예상 수행 시간에 따라 적절하게 지정해야 합니다.
TomcatWebserver
public class TomcatWebServer implements WebServer{
...
}
흔히 사용하는 tomcat에서 활용하는 TomcatWebServer 클래스는 WebServer를 상속받아 구현됩니다.
WebServer
public interface WebServer {
void start() throws WebServerException;
void stop() throws WebServerException;
int getPort();
default void shutDownGracefully(GracefulShutdownCallback callback) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
}
}
WebServer는 인터페이스이며 stop메서드를 통해 웹서버를 종료시킬 수 있습니다.
deafult 메서드로 shutDownGracefully도 가지고 있는 모습을 볼 수 있습니다.
shutDownGracefully 메서드는 새 요청 처리를 방지하고 기본 구현은 IMMEDIATE로 즉시 종료 콜백을 호출합니다.
해당 클래스의 stop 및 shutDownGracefully에 디버깅을 걸어보고 kill -15 {PID} 로 구동 중인 Application을 종료해 보겠습니다.
WebServerGracefulShutdownLifecycle
public final class WebServerGracefulShutdownLifecycle implements SmartLifecycle{
@Override
public void stop(Runnable callback) {
this.running = false;
this.webServer.shutDownGracefully((result) -> callback.run());
}
...
}
WebServerGracefulShutdownLifecycle는 SmartLifecycle 인터페이스를 상속받고 있습니다.
DefaultLifecycleProcessor에 의해 stop 메서드가 호출되고 webServer객체의 shtuDownGracefully를 호출합니다.
TomcatWebserver
@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
if (this.gracefulShutdown == null) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
return;
}
this.gracefulShutdown.shutDownGracefully(callback);
}
해당 메서드를 TomcatWebserver에서 오버라이드하고 있으며 gracefulShutdown이 null이 아니라면 callback을 넘겨서 gracefulShutdown을 호출합니다.
GracefulShutdown
GracefulShutdown 객체의 shutDownGracefully 메서드가 호출되며 해당 객체는 tomcat 웹서버의 graceful shutdown을 관리하는 클래스입니다.
private void doShutdown(GracefulShutdownCallback callback) {
List<Connector> connectors = getConnectors();
connectors.forEach(this::close);
try {
for (Container host : this.tomcat.getEngine().findChildren()) {
for (Container context : host.findChildren()) {
while (isActive(context)) {
if (this.aborted) {
logger.info("Graceful shutdown aborted with one or more requests still active");
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
return;
}
Thread.sleep(50);
}
}
}
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
해당 객체의 doShutdown 메서드를 살펴보면 connector 객체들을 모두 가져와서 close 시키며 남아있는 requtest들을 모두 종료시킵니다.
모든 request가 종료되면 이후에는 Graceful shutdown complete 로그를 남깁니다.
WebServerStartStopLifecycle
class WebServerStartStopLifecycle implements SmartLifecycle{
...
@Override
public void stop() {
this.running = false;
this.webServer.stop();
}
}
DefaultLifecycleProcessor클래스에 의해 stop 메서드가 호출되며 해당 클래스 역시 SmartLifecycle 인터페이스를 구현하고 있습니다.
stop 메서드를 통하여 webServer를 종료시키며 TomcatWebserver의 stop 메서드를 호출합니다.
마무리
Spring의 graceful shutdown은 무엇인지, 그리고 더 나아가서 내부동작은 어떻게 구현되는지를 이해해 보았습니다.
참고자료
https://sas-study.tistory.com/459
https://peterica.tistory.com/186
https://hudi.blog/springboot-graceful-shutdown/