Перейти к основному содержимому

Изящное завершение работы приложения Spring Boot

· 3 мин. чтения

1. Обзор

При выключении по умолчанию Spring TaskExecutor просто прерывает все запущенные задачи, но вместо этого может быть неплохо дождаться завершения всех запущенных задач. Это дает возможность каждой задаче принять меры для обеспечения безопасного завершения работы.

В этом кратком руководстве мы узнаем, как сделать это более изящное закрытие приложения Spring Boot, когда оно включает задачи, выполняемые с использованием пулов потоков.

2. Простой пример

Давайте рассмотрим простое приложение Spring Boot. Мы автоматически подключим bean-компонент TaskExecutor по умолчанию :

@Autowired
private TaskExecutor taskExecutor;

При запуске приложения давайте выполним 1-минутный процесс, используя поток из пула потоков:

taskExecutor.execute(() -> {
Thread.sleep(60_000);
});

Когда инициируется завершение работы , например, через 20 секунд после запуска, поток в этом примере прерывается, и приложение немедленно закрывается.

3. Дождитесь завершения задач

Давайте изменим поведение исполнителя задач по умолчанию, создав собственный bean-компонент ThreadPoolTaskExecutor .

Этот класс предоставляет флаг setWaitForTasksToCompleteOnShutdown для предотвращения прерывания запущенных задач. Давайте установим его в true :

@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(2);
taskExecutor.setMaxPoolSize(2);
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.initialize();
return taskExecutor;
}

И мы перепишем предыдущую логику, чтобы создать 3 потока, каждый из которых выполняет 1-минутную задачу.

@PostConstruct
public void runTaskOnStartup() {
for (int i = 0; i < 3; i++) {
taskExecutor.execute(() -> {
Thread.sleep(60_000);
});
}
}

Давайте теперь инициируем завершение работы в течение первых 60 секунд после запуска.

Мы видим, что приложение закрывается только через 120 секунд после запуска. Размер пула 2 позволяет одновременно выполнять только две задачи, поэтому третья ставится в очередь.

Установка флага гарантирует, что как текущие выполняемые задачи, так и поставленные в очередь задачи будут завершены .

Обратите внимание, что когда получен запрос на завершение работы, исполнитель задач закрывает очередь , чтобы нельзя было добавлять новые задачи.

4. Максимальное время ожидания до завершения

Хотя мы настроили ожидание завершения текущих и поставленных в очередь задач, Spring продолжает закрытие остальной части контейнера . Это может высвободить ресурсы, необходимые нашему исполнителю задач, и привести к сбою задач.

Чтобы заблокировать отключение остальной части контейнера, мы можем указать максимальное время ожидания в ThreadPoolTaskExecutor:

taskExecutor.setAwaitTerminationSeconds(30);

Это гарантирует, что на указанный период времени процесс выключения на уровне контейнера будет заблокирован .

Когда мы устанавливаем для флага setWaitForTasksToCompleteOnShutdown значение true , нам нужно указать значительно больший тайм-аут, чтобы все оставшиеся задачи в очереди также выполнялись.

5. Вывод

В этом кратком руководстве мы увидели, как безопасно закрыть приложение Spring Boot, настроив bean-компонент исполнителя задач для выполнения запущенных и отправленных задач до конца. Это гарантирует, что все задачи будут иметь указанное количество времени для выполнения своей работы.

Одним очевидным побочным эффектом является то, что это также может привести к более длительной фазе выключения . Поэтому нам нужно решить, использовать его или нет в зависимости от характера приложения.

Как всегда, примеры из этой статьи доступны на GitHub .