Як правильно вимкнути програму 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.