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/
'001Project' 카테고리의 다른 글
점진적으로 Vue 프레임워크 적용하기 (Vanilla Js -> Vue) (0) | 2021.04.25 |
---|---|
001프로젝트 / '이벤트'방식으로 레이싱카 구현하기 (0) | 2020.12.03 |
001프로젝트 / 저장소 안에 저장소, Submodule (0) | 2020.09.09 |
001프로젝트 / 설치가능한 웹 어플리케이션, PWA (0) | 2020.06.23 |