Як правильно вимкнути програму Spring Boot?


121

У документі Spring Boot вони сказали, що "Кожна програма SpringApplication зареєструє гачок відключення в JVM, щоб гарантувати, що ApplicationContext закритий при виході".

Коли я натискаю ctrl+cна команду shell, додаток можна вимкнено вимкнути. Якщо я запускаю програму у виробничій машині, я повинен використовувати команду java -jar ProApplicaton.jar . Але я не можу закрити термінал оболонки, інакше це закриє процес.

Якщо я запускаю команди, як nohup java -jar ProApplicaton.jar &, я не можу використовуватиctrl+c її витончено.

Який правильний спосіб запустити та зупинити додаток Spring Boot у виробничих умовах?


Залежно від вашої конфігурації, PID програми записується у файл. Ви можете надіслати сигнал вбивства до цього PID. Дивіться також коментарі до цього випуску .
— М. Дейнум

Який сигнал я повинен використовувати, я не думаю, що kill -9 - це гарна ідея, правда?
— Кріс

5
Ось чому я вказав вам на цю нитку ... Але щось на кшталт kill -SIGTERM <PID>повинно зробити трюк.
— М. Дейнум

вбити з pid, ні -9
— Dapeng

1
kill $ (lsof -ti tcp: <port>) - якщо ви не хочете користуватися приводом і вам потрібно швидко вбити
— Алекс Ноласко

Відповіді:


63

Якщо ви використовуєте модуль приводу, ви можете вимкнути програму через JMXабо HTTPякщо кінцева точка ввімкнена.

додати до application.properties:

endpoints.shutdown.enabled = true

Наступна URL-адреса буде доступна:

/actuator/shutdown - Дозволяє програмі граціозно вимкнутись (не включено за замовчуванням).

Залежно від того, яким чином виставлена ​​кінцева точка, чутливий параметр може використовуватися як підказка безпеки.

Наприклад, для чутливих кінцевих точок потрібен ім'я користувача / пароль при доступі до них HTTP (або просто вимкнено, якщо захист веб-сторінок не ввімкнено).

З документації про завантаження весни


1
Я не хочу включати привідний модуль. Але я виявив, що у своєму журналі консолі Spring Boot друкує PID у першому рядку. Чи є спосіб дозволити Spring Boot друкувати PID в іншому файлі без додавання виконавчого модуля (ApplicationPidListener)?
— Кріс

2
Зауважте, що ця кінцева точка повинна бути http-повідомленням. Ви можете ввімкнути це за допомогою endpoints.shutdown.enabled = true
— sparkyspider

54

Ось ще один варіант, який не вимагає від вас змінити код або виставити кінцеву точку зупинки. Створіть такі сценарії та використовуйте їх для запуску та зупинки програми.

start.sh

#!/bin/bash
java -jar myapp.jar & echo $! > ./pid.file &

Запускає додаток і зберігає ідентифікатор процесу у файлі

стоп.ш

#!/bin/bash
kill $(cat ./pid.file)

Зупиняє додаток, використовуючи збережений ідентифікатор процесу

start_silent.sh

#!/bin/bash
nohup ./start.sh > foo.out 2> foo.err < /dev/null &

Якщо вам потрібно запустити додаток, використовуючи ssh з віддаленої машини або конвеєра CI, тоді замість цього запустіть додаток. Використання start.sh безпосередньо може залишити оболонку для висіння.

Після напр. пере / розгортаючи додаток, ви можете перезапустити його, використовуючи:

sshpass -p password ssh -oStrictHostKeyChecking=no userName@www.domain.com 'cd /home/user/pathToApp; ./stop.sh; ./start_silent.sh'

Це має бути відповіддю. Я щойно підтвердив, що сигнал 15 відключення сповіщає весну до вимикання витончено.
— Чад

1
Чому б вам не закликати виконання Java - jar з nohup всередині start.sh, а не викликати виконання java - jar всередині start.sh, яке викликається nohup всередині іншого сценарію зовнішньої оболонки ??
— Anand Varkey Philips

2
@AnandVarkeyPhilips Єдина причина полягає в тому, що я іноді викликаю start.sh з командного рядка для тестування, але якщо вам це завжди потрібно, nohupви можете просто об'єднати команди
— Jens

@Jens, спасибі за інформацію Чи можете ви сказати мені, що ви робите це: foo.out 2> foo.err </ dev / null &
— Anand Varkey Philips

1
@jens, дякую, що я це зробив успішно, і я розмістив свій сценарій запуску та зупинки тут нижче. ( stackoverflow.com/questions/26547532/… )
— Anand Varkey Philips

52

Щодо відповіді @ Жана-Філіпа Бонда,

Ось швидкий приклад Maven для користувача Maven, щоб налаштувати кінцеву точку HTTP для відключення веб-програми весняного завантаження за допомогою виконавчого механізму spring-boot-starter, щоб ви могли скопіювати та вставити:

1.Maven pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2.application.properties:

#No auth  protected 
endpoints.shutdown.sensitive=false

#Enable shutdown endpoint
endpoints.shutdown.enabled=true

Усі кінцеві точки перераховані тут :

3.Надішліть метод публікації, щоб вимкнути додаток:

curl -X POST localhost:port/shutdown

Примітка безпеки:

якщо вам потрібен метод закриття, захищений автором, також може знадобитися

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

налаштування деталей :


Після другого кроку при спробі розгортання зустрічається з цим повідомленням про помилку: Опис: Параметр 0 методу setAuthenticationConfiguration в org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter потрібен боб типу 'org.springframework.security.config .annotation.authentication.configuration.AuthenticationConfiguration ', яку не вдалося знайти. Дія: Подумайте про те, щоб у своїй конфігурації визначити bean типу 'org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration'.
— Роберто

2
Будь ласка, зауважте: Якщо я включив щось подібне server.contextPath=/appNameдо своєї програми.властивості, то тепер команда для відключення стане такою: curl -X POST localhost:8080/appName/shutdown Сподіваюся, це може комусь допомогти. Мені довелося багато боротися через цю помилку.
— Naveen Kumar

З Spring Boot 1.5.8 пропонується, якщо без безпеки, мати в застосуванні властивості endpoints.shutdown.enabled=true management.security.enabled=false.
— Стефано Скарпанті

Якщо ви не хочете розкрити кінцеву точку та використовувати PID-файл для зупинки та запуску через скрипт оболонки, спробуйте це: stackoverflow.com/questions/26547532/…
— Anand Varkey Philips

27

Ви можете зробити програму Springboot записати PID у файл, а ви можете використовувати pid-файл для зупинки чи перезавантаження або отримання статусу за допомогою скрипта bash. Щоб записати PID у файл, зареєструйте слухача SpringApplication за допомогою ApplicationPidFileWriter, як показано нижче:

SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./bin/app.pid"));
application.run();

Потім напишіть сценарій bash для запуску програми для весняного завантаження. Довідково .

Тепер ви можете використовувати сценарій для запуску, зупинки або перезавантаження.


Тут можна знайти повний скрипт оболонки для запуску та зупинки оболонки Generic та Jenkins ( stackoverflow.com/questions/26547532/… )
— Anand Varkey Philips,


14

У всіх відповідях, здається, не вистачає факту, що вам може знадобитися виконати якусь частину роботи узгоджено під час витонченого відключення (наприклад, у додатку підприємства).

@PreDestroyдозволяє виконувати код відключення в окремих бобах. Щось більш витончене виглядатиме так:

@Component
public class ApplicationShutdown implements ApplicationListener<ContextClosedEvent> {
     @Autowired ... //various components and services

     @Override
     public void onApplicationEvent(ContextClosedEvent event) {
         service1.changeHeartBeatMessage(); // allows loadbalancers & clusters to prepare for the impending shutdown
         service2.deregisterQueueListeners();
         service3.finishProcessingTasksAtHand();
         service2.reportFailedTasks();
         service4.gracefullyShutdownNativeSystemProcessesThatMayHaveBeenLaunched(); 
         service1.eventLogGracefulShutdownComplete();
     }
}

Це саме те, що мені було потрібно. Виконавши програму, натисніть ctrl-c Спасибо @Michal
— Клаудіо Москосо

8

Я не виставляю жодних кінцевих точок і запускаю ( з nohup у фоновому режимі і без файлів, створених через nohup ) і зупиняюся на скрипті оболонки (з KILL PID витончено і змушуйте вбивати, якщо програма все ще працює через 3 хвилини ). Я просто створюю виконувану банку і використовую програму PID-файлу, щоб писати файл PID і зберігати Jar і Pid у папці з такою ж назвою, як і назва програми та сценарії оболонки, також мають те саме ім’я з запуском і зупинкою в кінці. Я закликаю ці сценарій зупинки і запускаю сценарій через конвеєр Jenkin. Поки що жодних питань. Ідеально працює для 8 програм (Дуже загальні сценарії та простий у застосуванні для будь-якого додатка).

Основний клас

@SpringBootApplication
public class MyApplication {

    public static final void main(String[] args) {
        SpringApplicationBuilder app = new SpringApplicationBuilder(MyApplication.class);
        app.build().addListeners(new ApplicationPidFileWriter());
        app.run();
    }
}

YML FILE

spring.pid.fail-on-write-error: true
spring.pid.file: /server-path-with-folder-as-app-name-for-ID/appName/appName.pid

Ось сценарій запуску (start-appname.sh):

#Active Profile(YAML)
ACTIVE_PROFILE="preprod"
# JVM Parameters and Spring boot initialization parameters
JVM_PARAM="-Xms512m -Xmx1024m -Dspring.profiles.active=${ACTIVE_PROFILE} -Dcom.webmethods.jms.clientIDSharing=true"
# Base Folder Path like "/folder/packages"
CURRENT_DIR=$(readlink -f "$0")
BASE_PACKAGE="${CURRENT_DIR%/bin/*}"
# Shell Script file name after removing path like "start-yaml-validator.sh"
SHELL_SCRIPT_FILE_NAME=$(basename -- "$0")
# Shell Script file name after removing extension like "start-yaml-validator"
SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT="${SHELL_SCRIPT_FILE_NAME%.sh}"
# App name after removing start/stop strings like "yaml-validator"
APP_NAME=${SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT#start-}

PIDS=`ps aux |grep [j]ava.*-Dspring.profiles.active=$ACTIVE_PROFILE.*$APP_NAME.*jar | awk {'print $2'}`
if [ -z "$PIDS" ]; then
  echo "No instances of $APP_NAME with profile:$ACTIVE_PROFILE is running..." 1>&2
else
  for PROCESS_ID in $PIDS; do
        echo "Please stop the process($PROCESS_ID) using the shell script: stop-$APP_NAME.sh"
  done
  exit 1
fi

# Preparing the java home path for execution
JAVA_EXEC='/usr/bin/java'
# Java Executable - Jar Path Obtained from latest file in directory
JAVA_APP=$(ls -t $BASE_PACKAGE/apps/$APP_NAME/$APP_NAME*.jar | head -n1)
# To execute the application.
FINAL_EXEC="$JAVA_EXEC $JVM_PARAM -jar $JAVA_APP"
# Making executable command using tilde symbol and running completely detached from terminal
`nohup $FINAL_EXEC  </dev/null >/dev/null 2>&1 &`
echo "$APP_NAME start script is  completed."

Ось стоп-скрипт (stop-appname.sh):

#Active Profile(YAML)
ACTIVE_PROFILE="preprod"
#Base Folder Path like "/folder/packages"
CURRENT_DIR=$(readlink -f "$0")
BASE_PACKAGE="${CURRENT_DIR%/bin/*}"
# Shell Script file name after removing path like "start-yaml-validator.sh"
SHELL_SCRIPT_FILE_NAME=$(basename -- "$0")
# Shell Script file name after removing extension like "start-yaml-validator"
SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT="${SHELL_SCRIPT_FILE_NAME%.*}"
# App name after removing start/stop strings like "yaml-validator"
APP_NAME=${SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT:5}

# Script to stop the application
PID_PATH="$BASE_PACKAGE/config/$APP_NAME/$APP_NAME.pid"

if [ ! -f "$PID_PATH" ]; then
   echo "Process Id FilePath($PID_PATH) Not found"
else
    PROCESS_ID=`cat $PID_PATH`
    if [ ! -e /proc/$PROCESS_ID -a /proc/$PROCESS_ID/exe ]; then
        echo "$APP_NAME was not running with PROCESS_ID:$PROCESS_ID.";
    else
        kill $PROCESS_ID;
        echo "Gracefully stopping $APP_NAME with PROCESS_ID:$PROCESS_ID..."
        sleep 5s
    fi
fi
PIDS=`/bin/ps aux |/bin/grep [j]ava.*-Dspring.profiles.active=$ACTIVE_PROFILE.*$APP_NAME.*jar | /bin/awk {'print $2'}`
if [ -z "$PIDS" ]; then
  echo "All instances of $APP_NAME with profile:$ACTIVE_PROFILE has has been successfully stopped now..." 1>&2
else
  for PROCESS_ID in $PIDS; do
    counter=1
    until [ $counter -gt 150 ]
        do
            if ps -p $PROCESS_ID > /dev/null; then
                echo "Waiting for the process($PROCESS_ID) to finish on it's own for $(( 300 - $(( $counter*5)) ))seconds..."
                sleep 2s
                ((counter++))
            else
                echo "$APP_NAME with PROCESS_ID:$PROCESS_ID is stopped now.."
                exit 0;
            fi
    done
    echo "Forcefully Killing $APP_NAME with PROCESS_ID:$PROCESS_ID."
    kill -9 $PROCESS_ID
  done
fi

7

Spring Boot надав декілька слухачів додатків, намагаючись створити контекст програми, одним із них є ApplicationFailedEvent. Ми можемо використовувати, щоб знати про погоду, контекст програми ініціалізований чи ні.

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.context.event.ApplicationFailedEvent; 
    import org.springframework.context.ApplicationListener;

    public class ApplicationErrorListener implements 
                    ApplicationListener<ApplicationFailedEvent> {

        private static final Logger LOGGER = 
        LoggerFactory.getLogger(ApplicationErrorListener.class);

        @Override
        public void onApplicationEvent(ApplicationFailedEvent event) {
           if (event.getException() != null) {
                LOGGER.info("!!!!!!Looks like something not working as 
                                expected so stoping application.!!!!!!");
                         event.getApplicationContext().close();
                  System.exit(-1);
           } 
        }
    }

Додайте до класу слухачів вище SpringApplication.

    new SpringApplicationBuilder(Application.class)
            .listeners(new ApplicationErrorListener())
            .run(args);  

Найкраща відповідь, яку я знайшов! Дякую!
— Вагіф

[@ user3137438], чим він відрізняється від входу всередину попередньо знищити анотацію?
— Anand Varkey Philips

6

Станом на Spring Boot 2.3 і пізніших версій є вбудований витончений механізм відключення .

Перед весняним завантаженням 2.3 , немає витонченого механізму відключення поза коробкою. Деякі пускачі на базі весняного завантаження забезпечують цю функціональність:

  1. https://github.com/jihor/hiatus-spring-boot
  2. https://github.com/gesellix/graceful-shutdown-spring-boot
  3. https://github.com/corentin59/spring-boot-graceful-shutdown

Я автор nr. 1. Стартер названий "Hiatus for Spring Boot". Він працює на рівні балансира навантаження, тобто просто позначає послугу OUT_OF_SERVICE, жодним чином не втручаючись у контекст програми. Це дозволяє зробити витончене відключення і означає, що при необхідності послугу можна витягнути з експлуатації на деякий час, а потім повернути до життя. Мінус у тому, що це не зупиняє JVM, вам доведеться це робити з killкомандою. Оскільки я запускаю все в контейнерах, для мене це не було великим завданням, оскільки мені доведеться все-таки зупинятись і виймати контейнер.

№2 та 3 більш-менш базуються на цій публікації Енді Вілкінсона. Вони працюють в одну сторону - одного разу спрацьовуючи, вони з часом закривають контекст.


5

SpringApplication неявно реєструє гачок відключення разом із JVM, щоб гарантувати, що ApplicationContext закритий при виході. Це також називатиме всі методи квасолі, зазначаються у @PreDestroy. Це означає, що нам не потрібно явно використовувати registerShutdownHook()метод ConfigurableApplicationContextзавантажувальної програми, як це потрібно робити у весняному ядрі.

@SpringBootConfiguration
public class ExampleMain {
    @Bean
    MyBean myBean() {
        return new MyBean();
    }

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(ExampleMain.class, args);
        MyBean myBean = context.getBean(MyBean.class);
        myBean.doSomething();

        //no need to call context.registerShutdownHook();
    }

    private static class MyBean {

        @PostConstruct
        public void init() {
            System.out.println("init");
        }

        public void doSomething() {
            System.out.println("in doSomething()");
        }

        @PreDestroy
        public void destroy() {
            System.out.println("destroy");
        }
    }
}

В якості альтернативи , @PostConstructі @PreDestroyя використав initMethodі destroyMethodатрибути в @Beanанотації. Так що для цього прикладу: @Bean(initMethod="init", destroyMethod="destroy").
— acker9

Один улов про @PreDestroyмало хто з розробників, можливо, не знає, що такі методи використовуються лише для бобів із синглом Singleton. Розробникам доводиться керувати очищенням частини життєвого циклу бобів для інших областей
— асгс

2

Їх існує багато способів вимкнути пружинний додаток. Перший - викликати close () на ApplicationContext:

ApplicationContext ctx =
    SpringApplication.run(HelloWorldApplication.class, args);
// ...
ctx.close()

Ваше запитання передбачає, що ви хочете закрити свою програму, роблячи це Ctrl+C, що часто використовується для припинення команди. В цьому випадку...

Вживання endpoints.shutdown.enabled=true- не найкращий рецепт. Це означає, що ви виставляєте кінцеву точку, щоб скасувати заявку. Отже, залежно від випадку використання та оточення, вам доведеться захистити його ...

Ctrl+Cмає працювати дуже добре у вашому випадку. Я припускаю, що ваша проблема викликана ampersand (&) Більше пояснення:

У контексті весняних програм може бути зареєстрований гак відключення за час виконання JVM. Дивіться документацію ApplicationContext .

Я не знаю, чи Spring Boot налаштовує цей гачок автоматично, як ви сказали. Я припускаю, що так.

Увімкнено Ctrl+C, ваша оболонка надсилає INTсигнал додатку переднього плану. Це означає "будь ласка, перервіть ваше виконання". Додаток може захопити цей сигнал і зробити очищення до його закінчення (гачок, зареєстрований Spring), або просто проігнорувати його (погано).

nohupце команда, яка виконує наступну програму з пасткою для ігнорування сигналу HUP. HUP використовується для припинення програми під час відключення (закрийте, наприклад, ssh-з'єднання). Більше того, він перенаправляє виходи, щоб уникнути того, що ваша програма блокується на зниклому TTY. nohupНЕ ігнорує сигнал INT. Так що НЕ заважає Ctrl+Cпрацювати.

Я припускаю, що ваша проблема викликана ampersand (&), а не nohup. Ctrl+Cпосилає сигнал процесам переднього плану. "Амперсанд" призводить до запуску програми у фоновому режимі. Одне рішення: робити

kill -INT pid

Використовуйте kill -9або kill -KILLпогано, оскільки додаток (тут JVM) не може захопити його, щоб закінчитися граціозно.

Ще одне рішення - повернути свою програму на перший план. Тоді Ctrl+Cбуде працювати. Погляньте на контроль Bash Job, точніше на fg.


2

Використовуйте статичний exit()метод у класі SpringApplication, щоб граціозно закрити додаток для весняного завантаження.

public class SomeClass {
    @Autowire
    private ApplicationContext context

    public void close() {
        SpringApplication.exit(context);
    }
}

Це працює для мене. Дуже дякую.
— Khachornchit Songsaen

1

Spring Boot тепер підтримує витончене вимкнення (зараз у попередній версії, 2.3.0.BUILD-SNAPSHOT)

Якщо це ввімкнено, вимкнення програми буде включати пільговий період з можливістю налаштування. Протягом цього пільгового періоду існуючі запити будуть дозволені, але нові запити не дозволятимуться

Ви можете ввімкнути це за допомогою:

server.shutdown.grace-period=30s

https://docs.spring.io/spring-boot/docs/2.3.0.BUILD-SNAPSHOT/reference/html/spring-boot-features.html#boot-features-graceful-shutdown


0

Якщо ви використовуєте maven, ви можете використовувати плагін для асемблера програми Maven .

Демон-моджо (який вбудовує JSW ) виведе сценарій оболонки з аргументом start / stop. stopВідключення буде / вбити граціозно додаток Spring.

Цей же сценарій можна використовувати для використання програми Maven як служби Linux.


0

Якщо ви перебуваєте в середовищі Linux, все, що вам потрібно зробити, - це створити симпосилання на ваш .jar-файл зсередини /etc/init.d/

sudo ln -s /path/to/your/myboot-app.jar /etc/init.d/myboot-app

Тоді ви можете запустити додаток, як і будь-який інший сервіс

sudo /etc/init.d/myboot-app start

Щоб закрити програму

sudo /etc/init.d/myboot-app stop

Таким чином, додаток не припиняється при виході з терміналу. І додаток витончено завершиться командою stop.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.