Spring Webflux - Netty 환경에서의 GracefulShutdown 구현

박다현·2022년 3월 15일
2
post-thumbnail

Eureka 환경에서 client들이 Registry 를 local cache에 저장하는 이유로 무중단 배포에 차질이 있었다. 이를 해결하기 위한 여러 방법을 모색하였고, 그 중 하나가 GracefulShutdown 을 이용하는 것이었다. 하지만 기존 Spring Web - Tomcat 에서의 GracefulShutdown 관련 레퍼런스가 많은 것에 비해 Netty 에서의 GracefulShutdown 의 레퍼런스는 거의 없었다. 이에 대해 조사하다보니 Netty 기본 개념에 대해서도 조사하고 이 문서를 이해하기 위한 내용을 여기 (Netty 기본 개념) 에 정리했다

거두절미 하고 참고한 사이트와 관련 코드는 다음과 같다
https://blog.fearcat.in/a?ID=00001-36c2265c-c4a8-4dde-ac9c-74fc87b0c532
https://www.infoq.cn/article/netty-elegant-exit-mechanism-and-principles

@Component
public class GracefulShutdown {
    @Autowired
    ReactorResourceFactory reactorResourceFactory;

    LoopResources loopResources;

    //SpringBoot 2.1.5 reactor.netty.resources.LoopResources#dispose   subscribe   block  
    @PostConstruct
    void init() throws Exception {
        Field field = TcpResources.class.getDeclaredField("defaultLoops");
        field.setAccessible(true);
        loopResources = (LoopResources) field.get(reactorResourceFactory.getLoopResources());
        field.setAccessible(false);

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Graceful: block long to 20s before real shutdown!");
            loopResources.disposeLater().block(Duration.ofSeconds(20));
        }));
    }
}

코드를 하나하나 분석해보자

  • @PostConstruct
    • 의존성 주입이 이루어진 후 초기화를 수행
    • 생성자가 호출되었을 때 Bean 이 초기화 되지 않는다
    • PostConstruct를 사용하면 Bean이 초기화 됨과 동시에 의존성을 확인할 수 있다
    • 클래스 내에 @Autowired 를 붙여서 사용할 때 생성자가 필요할 때 사용
    • bean lifecycle에서 오직 한 번만 수행
    • 여기서는 서버가 boot 되었을 때 바로 ShutdownHook 을 걸어주기 위해서 사용한 듯 하다
  • Field / getDeclaredField()
    • interface, class 의 필드의 정보나 그 필드에 동적으로 접근하기 위해 제공되는 class
    • getDeclaredField 는 Class.java 에 있는 함수인데, 파라미터로 넘긴 이름의 field 를 reflect 한 Field Class 를 반환한다
    • 여기서는 TcpResources 의 defaultLoops 를 사용하기 위해 사용했다
Field field = TcpResources.class.getDeclaredField("defaultLoops");
        field.setAccessible(true);
        loopResources = (LoopResources) field.get(reactorResourceFactory.getLoopResources());
        field.setAccessible(false);

여기서는 defaultLoop 를 가져와서 잠시 접근가능하게 하고, reactorResourceFactory 를 통해 defaultLoop 의 LoopResource 를 가져온다. 그 후 다시 접근 불가능하게 설정해주었다.

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Graceful: block long to 20s before real shutdown!");
            loopResources.disposeLater().block(Duration.ofSeconds(20));
        }));

이 코드는 다음과 같이 변환 될 수 있다.

Thread t = new Thread(() -> {
	System.out.println("Graceful: block long to 20s before real shutdown!");
    loopResources.disposeLater().block(Duration.ofSeconds(20));
});
Runtime.getRuntime().addShutdownHook(t);

즉, Shutdown Process 를 진행하는 Thread instance 를 생성 후에, 이를 런타임에 ShutdownHook 으로 걸어준다

그리고, 그 Thread 는 LoopResource 의 disposeLater() 함수를 호출하는데, 이미 진행되고 있는 구독이 취소되지 않게 하기위해 DEFAULT_SHUTDOWN_QUIET_PERIOD(2초) 후에 취소되게 한다

정리하자면 Graceful Shutdown Process 에 어떤 Logic 을 추가하고 싶다면 addShutdownHook 에 걸어주는 Thread 내에 로직을 추가하면 된다!!

0개의 댓글