Bagaimana cara mematikan Aplikasi Spring Boot dengan cara yang benar?

120

Dalam Dokumen Spring Boot, mereka mengatakan bahwa 'Setiap SpringApplication akan mendaftarkan hook shutdown dengan JVM untuk memastikan bahwa ApplicationContext ditutup dengan anggun saat keluar.'

Ketika saya mengklik ctrl+cperintah shell, aplikasi dapat dimatikan dengan baik. Jika saya menjalankan aplikasi di mesin produksi, saya harus menggunakan perintah java -jar ProApplicaton.jar. Tetapi saya tidak dapat menutup terminal shell, jika tidak maka proses akan ditutup.

Jika saya menjalankan perintah seperti nohup java -jar ProApplicaton.jar &, saya tidak dapat menggunakan ctrl+cuntuk mematikannya dengan baik.

Apa cara yang benar untuk memulai dan menghentikan Aplikasi Spring Boot di lingkungan produksi?

Chris
sumber
Bergantung pada konfigurasi Anda, PID aplikasi ditulis ke file. Anda dapat mengirim sinyal mematikan ke PID tersebut. Lihat juga komentar dalam masalah ini .
M. Deinum
Sinyal mana yang harus saya gunakan, menurut saya kill -9 adalah ide yang bagus, bukan?
Chris
5
Itulah mengapa saya mengarahkan Anda ke utas itu ... Tapi sesuatu seperti kill -SIGTERM <PID>seharusnya berhasil.
M. Deinum
bunuh dengan pid, no -9
Dapeng
1
kill $ (lsof -ti tcp: <port>) - jika Anda tidak ingin menggunakan aktuator dan perlu membunuh cepat
Alex Nolasco

Jawaban:

62

Jika Anda menggunakan modul aktuator, Anda dapat mematikan aplikasi melalui JMXatau HTTPjika titik akhir diaktifkan.

tambahkan ke application.properties:

endpoints.shutdown.enabled = true

URL berikut akan tersedia:

/actuator/shutdown - Mengizinkan aplikasi untuk dimatikan dengan baik (tidak diaktifkan secara default).

Bergantung pada bagaimana titik akhir diekspos, parameter sensitif dapat digunakan sebagai petunjuk keamanan.

Misalnya, titik akhir sensitif akan memerlukan nama pengguna / kata sandi saat diakses HTTP(atau dinonaktifkan jika keamanan web tidak diaktifkan).

Dari dokumentasi boot Spring

Jean-Philippe Bond
sumber
1
Saya tidak ingin menyertakan modul aktuator. Tetapi saya menemukan bahwa, di log konsol saya, Spring Boot mencetak PID di baris pertama. Apakah ada cara untuk membiarkan Spring Boot mencetak PID di file lain tanpa menambahkan modul aktuator (ApplicationPidListener)?
Chris
2
Perhatikan bahwa ini harus berupa postingan http ke titik akhir ini. Anda dapat mengaktifkannya dengan endpoints.shutdown.enabled = true
sparkyspider
54

Berikut adalah opsi lain yang tidak mengharuskan Anda mengubah kode atau mengekspos titik akhir penonaktifan. Buat skrip berikut dan gunakan untuk memulai dan menghentikan aplikasi Anda.

start.sh

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

Memulai aplikasi Anda dan menyimpan id proses dalam sebuah file

stop.sh

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

Menghentikan aplikasi Anda menggunakan id proses yang disimpan

start_silent.sh

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

Jika Anda perlu memulai aplikasi menggunakan ssh dari mesin jarak jauh atau pipeline CI, gunakan skrip ini untuk memulai aplikasi Anda. Menggunakan start.sh secara langsung dapat membuat shell hang.

Setelah mis. menerapkan kembali / menerapkan aplikasi Anda, Anda dapat memulainya kembali menggunakan:

sshpass -p password ssh -oStrictHostKeyChecking=no userName@www.domain.com 'cd /home/user/pathToApp; ./stop.sh; ./start_silent.sh'
Jens
sumber
Ini harus menjadi jawabannya. Saya baru saja mengonfirmasi bahwa sinyal 15 shutdown memberi tahu pegas untuk mati dengan anggun.
Chad
1
Mengapa Anda tidak memanggil eksekusi java-jar dengan nohup di dalam start.sh, daripada memanggil eksekusi java-jar di dalam start.sh yang dipanggil dengan nohup di dalam skrip shell luar yang lain ??
Anand Varkey Philips
2
@AnandVarkeyPhilips Satu-satunya alasan adalah saya kadang-kadang memanggil start.sh dari baris perintah untuk tujuan pengujian, tetapi jika Anda selalu membutuhkannya nohupmaka Anda bisa menggabungkan perintah
Jens
@Jens, Terima kasih atas infonya. Bisakah Anda memberi tahu saya bahwa Anda melakukan ini: foo.out 2> foo.err </ dev / null &
Anand Varkey Philips
1
@jens, terima kasih saya telah berhasil melakukannya dan saya telah memposting skrip awal dan penghentian saya di bawah ini. ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips
52

Mengenai jawaban @Jean-Philippe Bond,

berikut adalah contoh cepat maven bagi pengguna maven untuk mengonfigurasi titik akhir HTTP untuk mematikan aplikasi web boot musim semi menggunakan aktuator spring-boot-starter sehingga Anda dapat menyalin dan menempel:

1.Maven pom.xml:

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

2.aplikasi.properties:

#No auth  protected 
endpoints.shutdown.sensitive=false

#Enable shutdown endpoint
endpoints.shutdown.enabled=true

Semua titik akhir tercantum di sini :

3. Kirim metode posting untuk mematikan aplikasi:

curl -X POST localhost:port/shutdown

Catatan Keamanan:

jika Anda membutuhkan metode shutdown yang dilindungi oleh auth, Anda mungkin juga perlu

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

konfigurasikan detail :

Jaskey
sumber
Setelah langkah kedua, ketika mencoba untuk menyebarkan, bertemu pesan kesalahan ini: Deskripsi: Parameter 0 metode setAuthenticationConfiguration di org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter membutuhkan kacang jenis 'org.springframework.security.config .annotation.authentication.configuration.AuthenticationConfiguration 'yang tidak dapat ditemukan. Tindakan: Pertimbangkan untuk mendefinisikan kacang jenis 'org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration' dalam konfigurasi Anda.
Roberto
2
Harap diperhatikan: Jika saya telah memasukkan sesuatu seperti server.contextPath=/appNamedalam application.properties saya Jadi sekarang perintah untuk mematikan akan menjadi: curl -X POST localhost:8080/appName/shutdown Semoga ini dapat membantu seseorang. Saya harus berjuang keras karena kesalahan ini.
Naveen Kumar
Dengan Spring Boot 1.5.8 disarankan, jika tanpa keamanan, memiliki properti aplikasi endpoints.shutdown.enabled=true management.security.enabled=false.
Stefano Scarpanti
Jika Anda tidak ingin mengekspos titik akhir dan menggunakan file PID untuk berhenti dan memulai melalui skrip shell, coba ini: stackoverflow.com/questions/26547532/…
Anand Varkey Philips
27

Anda dapat membuat aplikasi springboot untuk menulis PID ke dalam file dan Anda dapat menggunakan file pid untuk menghentikan atau memulai ulang atau mendapatkan status menggunakan skrip bash. Untuk menulis PID ke file, daftarkan listener ke SpringApplication menggunakan ApplicationPidFileWriter seperti yang ditunjukkan di bawah ini:

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

Kemudian tulis skrip bash untuk menjalankan aplikasi spring boot. Referensi .

Sekarang Anda dapat menggunakan skrip untuk memulai, menghentikan, atau memulai ulang.

nav3916872
sumber
Skrip shell start dan stop lengkap yang kompatibel dengan Jenkins dan generik dapat ditemukan di sini ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips
14

Semua jawaban tampaknya kehilangan fakta bahwa Anda mungkin perlu menyelesaikan beberapa bagian pekerjaan secara terkoordinasi selama penghentian yang dilakukan dengan baik (misalnya, dalam aplikasi perusahaan).

@PreDestroymemungkinkan Anda untuk mengeksekusi kode shutdown dalam kacang individu. Sesuatu yang lebih canggih akan terlihat seperti ini:

@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();
     }
}
Michal
sumber
Inilah yang saya butuhkan. Setelah menjalankan aplikasi, tekan ctrl-c Terima @Michal
Claudio Moscoso
8

Saya tidak mengekspos titik akhir apa pun dan mulai ( dengan nohup di latar belakang dan tanpa file yang dibuat melalui nohup ) dan berhenti dengan skrip shell (dengan KILL PID dengan anggun dan paksa bunuh jika aplikasi masih berjalan setelah 3 menit ). Saya baru saja membuat jar yang dapat dieksekusi dan menggunakan penulis file PID untuk menulis file PID dan menyimpan Jar dan Pid dalam folder dengan nama yang sama dengan nama aplikasi dan skrip shell juga memiliki nama yang sama dengan start dan stop pada akhirnya. Saya menyebutnya skrip stop dan memulai skrip melalui pipeline jenkins juga. Tidak ada masalah sejauh ini. Bekerja sempurna untuk 8 aplikasi (Skrip yang sangat umum dan mudah diterapkan untuk aplikasi apa pun).

Kelas Utama

@SpringBootApplication
public class MyApplication {

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

FILE YML

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

Berikut ini skrip awal (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."

Berikut ini skrip stop (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
Anand Varkey Philips
sumber
7

Spring Boot menyediakan beberapa aplikasi pendengar saat mencoba membuat konteks aplikasi salah satunya adalah ApplicationFailedEvent. Kita bisa gunakan untuk mengetahui apakah konteks aplikasi diinisialisasi atau tidak.

    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);
           } 
        }
    }

Tambahkan ke kelas pendengar di atas ke SpringApplication.

    new SpringApplicationBuilder(Application.class)
            .listeners(new ApplicationErrorListener())
            .run(args);  
pengguna3137438
sumber
Jawaban terbaik yang saya temukan! Terima kasih!
Vagif
[@ user3137438], Apa bedanya dengan masuk ke dalam anotasi pra perusakan?
Anand Varkey Philips
6

Mulai Spring Boot 2.3 dan yang lebih baru, ada mekanisme pematian anggun bawaan .

Pre-Spring Boot 2.3 , tidak ada mekanisme shutdown anggun yang out-of-the-box. Beberapa permulaan boot musim semi menyediakan fungsionalitas ini:

  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

Saya penulis nr. 1. Starter diberi nama "Hiatus for Spring Boot". Ia bekerja pada tingkat penyeimbang beban, yaitu cukup menandai layanan sebagai OUT_OF_SERVICE, tidak mengganggu konteks aplikasi dengan cara apa pun. Hal ini memungkinkan untuk melakukan penonaktifan yang anggun dan berarti, jika diperlukan, layanan dapat dihentikan layanannya untuk beberapa waktu dan kemudian dihidupkan kembali. Kelemahannya adalah itu tidak menghentikan JVM, Anda harus melakukannya dengan killperintah. Karena saya menjalankan semuanya dalam wadah, ini bukan masalah besar bagi saya, karena saya harus berhenti dan melepaskan wadah itu juga.

Nomor 2 dan 3 kurang lebih didasarkan pada posting ini oleh Andy Wilkinson. Mereka bekerja satu arah - sekali dipicu, mereka akhirnya menutup konteksnya.

jihor
sumber
5

SpringApplication secara implisit mendaftarkan hook shutdown dengan JVM untuk memastikan bahwa ApplicationContext ditutup dengan baik saat keluar. Itu juga akan memanggil semua metode kacang yang diberi anotasi @PreDestroy. Itu berarti kita tidak harus secara eksplisit menggunakan registerShutdownHook()metode a ConfigurableApplicationContextdalam aplikasi boot, seperti yang harus kita lakukan di aplikasi inti musim semi.

@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");
        }
    }
}
Shruti Gupta
sumber
Sebagai alternatif untuk @PostConstructdan @PreDestroysaya menggunakan atribut initMethoddan destroyMethoddalam @Beananotasi. Jadi untuk contoh ini: @Bean(initMethod="init", destroyMethod="destroy").
acker9
Satu hal yang @PreDestroymungkin tidak diketahui oleh beberapa pengembang adalah, metode seperti itu hanya dipanggil untuk kacang dengan cakupan Singleton. Pengembang harus mengelola bagian pembersihan siklus hidup kacang untuk cakupan lain
asgs
2

Ada banyak cara untuk mematikan aplikasi pegas. Salah satunya adalah memanggil close () di ApplicationContext:

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

Pertanyaan Anda menyarankan Anda ingin menutup aplikasi Anda dengan melakukan Ctrl+C, yang sering digunakan untuk menghentikan perintah. Pada kasus ini...

Penggunaan endpoints.shutdown.enabled=truebukanlah resep terbaik. Ini berarti Anda mengekspos titik akhir untuk menghentikan aplikasi Anda. Jadi, tergantung pada kasus penggunaan dan lingkungan Anda, Anda harus mengamankannya ...

Ctrl+Charus bekerja dengan baik dalam kasus Anda. Saya menganggap masalah Anda disebabkan oleh ampersand (&) Penjelasan selengkapnya:

Konteks Aplikasi Musim Semi mungkin telah mendaftarkan hook shutdown dengan runtime JVM. Lihat dokumentasi ApplicationContext .

Saya tidak tahu apakah Spring Boot mengkonfigurasi hook ini secara otomatis seperti yang Anda katakan. Saya berasumsi demikian.

Aktif Ctrl+C, shell Anda mengirimkan INTsinyal ke aplikasi latar depan. Artinya "tolong hentikan eksekusi Anda". Aplikasi dapat menjebak sinyal ini dan melakukan pembersihan sebelum penghentiannya (pengait didaftarkan oleh Spring), atau mengabaikannya (buruk).

nohupadalah perintah yang menjalankan program berikut dengan perangkap untuk mengabaikan sinyal HUP. HUP digunakan untuk menghentikan program ketika Anda menutup telepon (tutup koneksi ssh Anda misalnya). Selain itu, ia mengalihkan keluaran untuk menghindari blok program Anda pada TTY yang menghilang. nohupTIDAK mengabaikan sinyal INT. Jadi TIDAK mencegah Ctrl+Cuntuk bekerja.

Saya berasumsi bahwa masalah Anda disebabkan oleh tanda & (&), bukan oleh nohup. Ctrl+Cmengirimkan sinyal ke proses latar depan. Tanda ampersand menyebabkan aplikasi Anda berjalan di latar belakang. Satu solusi: lakukan

kill -INT pid

Gunakan kill -9atau kill -KILLburuk karena aplikasi (di sini JVM) tidak dapat menjebaknya untuk dihentikan dengan baik.

Solusi lain adalah mengembalikan aplikasi Anda di latar depan. Kemudian Ctrl+Cakan berhasil. Lihat kontrol Bash Job, lebih tepatnya di fg.

keren
sumber
2

Gunakan exit()metode statis di kelas SpringApplication untuk menutup aplikasi boot musim semi Anda dengan baik.

public class SomeClass {
    @Autowire
    private ApplicationContext context

    public void close() {
        SpringApplication.exit(context);
    }
}
rw026
sumber
Ini berhasil untuk saya. Terima kasih banyak.
Khachornchit Songsaen
1

Spring Boot sekarang mendukung penonaktifan anggun (saat ini dalam versi pra-rilis, 2.3.0.BUILD-SNAPSHOT)

Jika diaktifkan, penghentian aplikasi akan menyertakan masa tenggang dengan durasi yang dapat dikonfigurasi. Selama masa tenggang ini, permintaan yang ada akan diizinkan untuk diselesaikan tetapi tidak ada permintaan baru yang akan diizinkan

Anda dapat mengaktifkannya dengan:

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

cmlonder
sumber
0

Jika Anda menggunakan maven, Anda dapat menggunakan plugin assembler Aplikasi Maven .

Daemon mojo (yang menyertakan JSW ) akan menampilkan skrip shell dengan argumen start / stop. Ini stopakan mematikan / mematikan aplikasi Spring Anda dengan anggun.

Skrip yang sama dapat digunakan untuk menggunakan aplikasi maven Anda sebagai layanan linux.

Nicolas Labrot
sumber
0

Jika Anda berada di lingkungan linux, yang harus Anda lakukan adalah membuat symlink ke file .jar Anda dari dalam /etc/init.d/

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

Kemudian Anda dapat memulai aplikasi seperti layanan lainnya

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

Untuk menutup aplikasi

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

Dengan cara ini, aplikasi tidak akan berhenti saat Anda keluar dari terminal. Dan aplikasi akan mati dengan baik dengan perintah stop.

Johna
sumber