본문 바로가기

001Project

001프로젝트 / ThreadPoolTaskExecutor를 이용한 Spring에서 Graceful Shutdown 구현해 보기

001 Project?
001 Project란 어떠한 과제가 있을 때 그의 기초 단계인 0.0.1v을 만들어 보는 개념입니다.
Graceful Shutdown의 기초적인 부분을 다루고 있습니다.
모든 코드는 github에 있습니다.

🤔 Graceful Shutdown?

graceful shutdown 이란 실행 중인 작업이 완료된 후 애플리케이션을 종료하는 것을 의미합니다.

 

예를 들어 다음과 같이 15초가 소요되는 작업이 있습니다.

@RestController
public class MyController {
    @GetMapping("/task")
    public String task() throws InterruptedException {
        job(1);
        return "end";
    }

    private void job(int i) throws InterruptedException {
        System.out.println("job start " + i);
        Thread.sleep(15000);
        System.out.println("job end " + i);
    }
}

만약 작업을 수행하는 도중에 스프링을 종료시킨다면 어떻게 될까요? 작업을 마치지 못하고 종료가 될 것입니다. 하지만 graceful Shutdown을 적용한다면 스프링 종료 명령이 전달되더라도 진행되던 작업까지 마무리한 후 종료를 진행할 수 있습니다.

 

이번 포스팅에서는 Spring에서 graceful shutdown을 구현하는 방법에 대해서 간단하게 살펴보겠습니다.

Graceful shutdown 적용 전

먼저 graceful shutdown을 적용하기 전에 모습을 잠시 살펴보고 가겠습니다.
앞으로 자주 반복해서 진행할 테스트 방법은 다음과 같습니다.

1. curl -i localhost:8080/task 로 15초가 소요되는 job을 실행시킨다.
2. 15초가 지나기 전에 kill `pgrep java` 명령어로 spring을 종료시킨다.
3. spring이 종료되는 단계를 관찰한다.

먼저 graceful shutdown을 적용하기 전 위의 단계를 진행해보겠습니다.

job이 끝나기 전에 어플리케이션이 종료되었습니다.

Graceful shutdown 적용

이번에는 graceful shutdown을 적용한 후 위의 단계를 실행해보겠습니다.

ThreadPoolTaskExecutor

아래처럼 먼저 job을 실행할 executor 객체를 빈으로 띄워주도록 합니다.

@Configuration
public class MyThreadPoolExecutor {
    @Bean
    public ThreadPoolTaskExecutor myExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(20);
        executor.initialize();
        return executor;
    }
}

여기서 핵심은 setWaitForTasksToCompleteOnShutdown 와 setAwaitTerminationSeconds입니다.

setWaitForTasksToCompleteOnShutdown: 진행 중이던 작업이 완료된 후 Thread를 종료한다.
setAwaitTerminationSeconds: 작업을 마칠 때까지 기다려줄 시간을 설정

setWaitForTasksToCompleteOnShutdown을 true 값을 주고 setAwaitTerminationSeconds 값을 20으로 설정하여 20초 동안 작업을 마저 진행할 시간을 주도록 하겠습니다.

테스트

자 그럼 myExecutor를 이용해서 위에서 단계를 다시 실행해보도록 하겠습니다.

task() 코드는 다음과 같습니다.

@RestController
public class MyController {
    @Autowired
    private ThreadPoolTaskExecutor myExecutor;

    @GetMapping("/task")
    public String task() {
        myExecutor.execute(
                ()-> {
                    try {
                        job(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        );
        return "end";
    }

    private void job(int i) throws InterruptedException {
        System.out.println("job start " + i);
        Thread.sleep(15000);
        System.out.println("job end " + i);
    }
}

결과

이번에는 대기시간을 100초로 늘리고 for문으로 job을 실행해보겠습니다.

    @Bean
    public ThreadPoolTaskExecutor myExecutor(){
...
        executor.setAwaitTerminationSeconds(100);
...
    }
@GetMapping("/task")
public String task() {
    for (int i = 1; i <= 6; i++) {
        final int number = i;
        myExecutor.execute(
                () -> {
                    try {
                        job(number);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        );
    }
    return "end";
}

결과

작업 큐의 모든 작업들이 완료된 후 어플리케이션이 종료되었습니다. 만약 setAwaitTerminationSeconds의 시간 동안 모든 작업이 완료되지 않았다면 그 상태에서 강제적으로 종료되니 참고하시기 바랍니다.

 

이상으로 spring에서 graceful shutdown에 대해서 알아보았습니다. 잘못된 부분(표현)이나 다른 의견이 있으시다면 글 남겨주시면 감사하겠습니다!

 

혹시 ThreadPoolTaskExecutor를 활용하지 않은 graceful shut down 에 대해 궁금하신 분들은 아래 링크를 참고하시기 바랍니다.

참고 링크: blog.marcosbarbero.com/graceful-shutdown-spring-boot-apps/

반응형