Bir Spring Boot Uygulamasını doğru bir şekilde nasıl kapatabilirim?


121

Spring Boot Belgesinde, 'Her SpringApplication, ApplicationContext'in çıkışta düzgün bir şekilde kapatılmasını sağlamak için JVM ile bir kapatma kancası kaydedecektir' dediler.

ctrl+cKabuk komutuna tıkladığımda , uygulama nazikçe kapatılabiliyor. Uygulamayı bir üretim makinesinde çalıştırırsam, komutu kullanmam gerekir java -jar ProApplicaton.jar. Ancak kabuk terminalini kapatamıyorum, aksi takdirde işlemi kapatır.

Eğer komutu gibi çalıştırırsam nohup java -jar ProApplicaton.jar &, ctrl+conu zarif bir şekilde kapatmak için kullanamam .

Üretim ortamında bir Spring Boot Uygulamasını başlatmanın ve durdurmanın doğru yolu nedir?


Yapılandırmanıza bağlı olarak, uygulamanın PID'si dosyaya yazılır. Bu PID'ye bir öldürme sinyali gönderebilirsiniz. Bu sayıdaki yorumlara da bakınız .
M.Deinum

Hangi sinyali kullanmalıyım, öldür -9'un iyi bir fikir olduğunu sanmıyorum, değil mi?
Chris

5
Bu yüzden seni o konuya işaret ettim ... Ama böyle bir şey kill -SIGTERM <PID>işe yaramalı.
M.Deinum

pid ile öldür, no -9
Dapeng

1
kill $ (lsof -ti tcp: <port>) - aktüatör kullanmak istemiyorsanız ve hızlı bir öldürmeye ihtiyacınız varsa
Alex Nolasco

Yanıtlar:


63

Aktüatör modülünü kullanıyorsanız, uygulamayı JMXveya HTTPuç nokta etkinleştirilmişse kapatabilirsiniz .

ekle application.properties:

endpoints.shutdown.enabled = true

Aşağıdaki URL mevcut olacaktır:

/actuator/shutdown - Uygulamanın nazikçe kapatılmasına izin verir (varsayılan olarak etkin değildir).

Bir uç noktanın nasıl açığa çıktığına bağlı olarak, hassas parametre bir güvenlik ipucu olarak kullanılabilir.

Örneğin, hassas uç noktalar üzerinden erişildiklerinde bir kullanıcı adı / şifre gerekecektir HTTP(veya web güvenliği etkin değilse devre dışı bırakılacaktır).

Gönderen Bahar önyükleme belgelerinde


1
Aktüatör modülünü dahil etmek istemiyorum. Ama konsol günlüğümde Spring Boot'un ilk satırda PID'yi yazdırdığını buldum. Spring Boot'un, aktüatör modülü (ApplicationPidListener) eklemeden PID'yi başka bir dosyaya yazdırmasına izin vermenin bir yolu var mı?
Chris

2
Bunun, bu uç noktaya bir http gönderisi olması gerektiğini unutmayın. Endpoints.shutdown.enabled = true
sparkyspider

54

İşte kodu değiştirmenizi veya bir kapatma bitiş noktasını göstermenizi gerektirmeyen başka bir seçenek. Aşağıdaki komut dosyalarını oluşturun ve bunları uygulamanızı başlatmak ve durdurmak için kullanın.

start.sh

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

Uygulamanızı başlatır ve işlem kimliğini bir dosyaya kaydeder

stop.sh

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

Kaydedilmiş işlem kimliğini kullanarak uygulamanızı durdurur

start_silent.sh

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

Uygulamayı uzaktaki bir makineden veya bir CI ardışık düzeninden ssh kullanarak başlatmanız gerekirse, uygulamanızı başlatmak için bunun yerine bu komut dosyasını kullanın. Start.sh'yi doğrudan kullanmak kabuğu takılmaya bırakabilir.

Örneğin sonra. uygulamanızı yeniden / dağıtarak aşağıdakileri kullanarak yeniden başlatabilirsiniz:

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

Cevap bu olmalı. Az önce bir sinyal 15 kapanmasının bahara zarif bir şekilde batmasını söylediğini doğruladım.
Çad

1
Start.sh içinde java - jar yürütmesini çağırmak yerine, start.sh içinde nohup ile java - jar yürütmesini çağırıp başka bir dış kabuk komut dosyası içinde nohup ile neden çağırmıyorsunuz?
Anand Varkey Philips

2
@AnandVarkeyPhilips Bunun tek nedeni, bazen test amacıyla komut satırından start.sh çağrısı yapmam, ancak her zaman ihtiyacınız nohupolursa komutları birleştirebilirsiniz
Jens

@Jens, bilgi için teşekkürler. Bana bunu yaptığını söyleyebilir misin: foo.out 2> foo.err </ dev / null &
Anand Varkey Philips

1
@jens, teşekkürler Bunu başarılı bir şekilde yaptım ve start ve stop betiğimi burada aşağıya gönderdim. ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips

52

@ Jean-Philippe Bond'un cevabına gelince,

İşte maven kullanıcısının, kopyalayıp yapıştırabilmeniz için yaylı önyükleme web uygulamasını kullanarak yaylı önyükleme web uygulamasını kapatmak için HTTP uç noktasını yapılandırması için hızlı bir maven örneği:

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

Tüm uç noktalar burada listelenmiştir :

3. Uygulamayı kapatmak için bir gönderi yöntemi gönderin:

curl -X POST localhost:port/shutdown

Güvenlik Notu:

yetkilendirme korumalı kapatma yöntemine ihtiyacınız varsa, şunlara da ihtiyacınız olabilir

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

ayrıntıları yapılandırın :


İkinci adımdan sonra, konuşlandırmaya çalışırken şu hata mesajıyla karşılaşıldı: Açıklama: org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter içindeki setAuthenticationConfiguration yönteminin 0 parametresi, 'org.springframework.security.config türünde bir bean gerektirdi .annotation.authentication.configuration.AuthenticationConfiguration 'bulunamadı. Eylem: Yapılandırmanızda 'org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration' türünde bir bean tanımlamayı düşünün.
Roberto

2
Lütfen dikkat: Eğer server.contextPath=/appNameapplication.properties'ime benzer bir şey eklediysem , şimdi kapatma komutu şöyle olacaktır: curl -X POST localhost:8080/appName/shutdown Umarım birine yardımcı olabilir. Bu hata yüzünden çok uğraşmak zorunda kaldım.
Naveen Kumar

Spring Boot 1.5.8 ile, güvenlik yoksa, application.properties dosyasında olması önerilir endpoints.shutdown.enabled=true management.security.enabled=false.
Stefano Scarpanti

Bitiş noktasını açığa çıkarmak istemiyorsanız ve kabuk komut dosyası aracılığıyla durdurmak ve başlatmak için PID dosyasını kullanmak istemiyorsanız, şunu deneyin: stackoverflow.com/questions/26547532/…
Anand Varkey Philips

27

Springboot uygulamasının PID'yi dosyaya yazmasını sağlayabilir ve pid dosyasını durdurmak veya yeniden başlatmak veya bir bash betiği kullanarak durumu almak için kullanabilirsiniz. PID'yi bir dosyaya yazmak için, aşağıda gösterildiği gibi ApplicationPidFileWriter kullanarak SpringApplication'a bir dinleyici kaydedin:

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

Daha sonra spring boot uygulamasını çalıştırmak için bir bash betiği yazın. Referans .

Artık betiği başlatmak, durdurmak veya yeniden başlatmak için kullanabilirsiniz.


Generic ve Jenkins uyumlu tam başlatma ve durdurma kabuğu komut dosyası burada bulunabilir ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips


14

Görünüşe göre tüm yanıtlar, sorunsuz kapatma sırasında (örneğin, bir kurumsal uygulamada) işin bir bölümünü koordineli bir şekilde tamamlamanız gerekebileceği gerçeğini gözden kaçırıyor gibi görünüyor.

@PreDestroyayrı çekirdeklerde kapatma kodunu çalıştırmanıza olanak sağlar. Daha sofistike bir şey şuna benzer:

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

Bu tam da ihtiyacım olan şey. Uygulamayı yürüttükten sonra, ctrl-c teşekkürler @Michal
Claudio Moscoso

8

Herhangi bir uç noktayı açığa çıkarmıyorum ve başlamıyorum ( arka planda nohup ile ve nohup aracılığıyla oluşturulan dosyalar olmadan ) ve kabuk betiğiyle duruyorum ( KILL PID ile incelikle ve uygulama 3 dakika sonra hala çalışıyorsa öldürmeye zorla ). Ben sadece yürütülebilir jar oluşturuyorum ve PID dosyası yazmak için PID dosya yazıcısını kullanıyorum ve Jar ve Pid'i uygulama adıyla aynı ada sahip klasörde depoluyorum ve kabuk komut dosyaları da başlangıç ​​ve bitiş ile aynı ada sahip. Bunları durdurma betiği olarak adlandırıyorum ve betiği jenkins pipeline yoluyla da başlatıyorum. Şimdiye kadar sorun yok. 8 uygulama için mükemmel çalışma (Çok genel komut dosyaları ve herhangi bir uygulama için uygulanması kolay).

Ana sınıf

@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 DOSYASI

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

İşte başlangıç ​​betiği (başlangıç-uygulamaadı.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."

İşte durdurma betiği (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, uygulama bağlamı oluşturmaya çalışırken birkaç uygulama dinleyicisi sağladı, bunlardan biri ApplicationFailedEvent. Uygulama içeriğinin başlatılıp başlatılmadığını hava durumunu bilmek için kullanabiliriz.

    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'a yukarıdaki dinleyici sınıfına ekleyin.

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

Bulduğum en iyi cevap! Teşekkür ederim!
Vagif

[@ user3137438], İmha öncesi ek açıklama içinde oturum açmaktan ne farkı var?
Anand Varkey Philips

6

Spring Boot 2.3 ve sonraki sürümlerden itibaren, yerleşik bir zarif kapatma mekanizması vardır.

Pre-Spring Boot 2.3 , kutudan çıkar çıkmaz zarif bir kapatma mekanizması yoktur. Bazı yaylı başlatıcılar bu işlevi sağlar:

  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 yazarı benim. 1. Başlangıç, "Hiatus for Spring Boot" olarak adlandırılır. Yük dengeleyici düzeyinde çalışır, yani hizmeti yalnızca OUT_OF_SERVICE olarak işaretler, uygulama bağlamına hiçbir şekilde müdahale etmez. Bu, zarif bir kapatma yapılmasına izin verir ve gerekirse, hizmetin bir süre hizmet dışı bırakılıp ardından hayata döndürülebileceği anlamına gelir. Olumsuz tarafı, JVM'yi durdurmamasıdır, bunu killkomutla yapmanız gerekecek . Her şeyi konteynırlarda çalıştırdığım için, bu benim için önemli değildi, çünkü konteyneri yine de durdurup çıkarmak zorunda kalacağım.

2 ve 3 numaralı numaralar aşağı yukarı Andy Wilkinson'ın bu gönderisine dayanıyor . Tek yönlü çalışırlar - bir kez tetiklendiklerinde, sonunda bağlamı kapatırlar.


5

SpringApplication, ApplicationContext'in çıkışta düzgün bir şekilde kapatılmasını sağlamak için JVM ile bir kapatma kancasını örtük olarak kaydeder. Bu ayrıca açıklamalı tüm fasulye yöntemlerini çağıracaktır @PreDestroy. Bu , spring core uygulamasında yapmak zorunda olduğumuz gibi, bir önyükleme uygulamasında a registerShutdownHook()yöntemini açıkça kullanmak zorunda olmadığımız anlamına gelir 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");
        }
    }
}

Bir alternatif olarak için @PostConstructve @PreDestroykullandığım initMethodve destroyMethodiçinde niteliklerini @Beanaçıklama. Bu örnek için So: @Bean(initMethod="init", destroyMethod="destroy").
acker9

Hakkında bir yakalama @PreDestroybilememektedir birkaç geliştiriciler bu tür yöntemleri yalnızca Singleton kapsamı ile fasulye denir vardır. Geliştiriciler, diğer kapsamlar için çekirdek yaşam döngüsünün temizleme bölümünü yönetmelidir
asgs

2

Bir yay uygulamasını kapatmanın birçok yolu vardır. Biri ApplicationContext:

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

Sorunuz uygulamanızı yaparak kapatmak istediğinizi gösteriyor Ctrl+C, bu genellikle bir komutu sonlandırmak için kullanılır. Bu durumda...

Kullanım endpoints.shutdown.enabled=trueen iyi tarif değildir. Bu, başvurunuzu sonlandırmak için bir son nokta açığa çıkardığınız anlamına gelir. Bu nedenle, kullanım durumunuza ve ortamınıza bağlı olarak, onu güvence altına almanız gerekecek ...

Ctrl+Csenin durumunda çok iyi çalışmalı. Sorununuzun "ve" işaretinden (&) kaynaklandığını varsayıyorum. Daha fazla açıklama:

Bir Spring Application Context, JVM çalışma zamanı ile bir kapatma kancası kaydetmiş olabilir. ApplicationContext belgelerine bakın .

Spring Boot bu kancayı dediğiniz gibi otomatik olarak yapılandırır mı bilmiyorum. Sanırım öyle.

Açık Ctrl+C, kabuğunuz INTön plan uygulamasına bir sinyal gönderir . "Lütfen infazınızı yarıda kesin" anlamına gelir. Uygulama bu sinyali yakalayabilir ve sonlandırılmadan önce temizleme yapabilir (Bahar tarafından kaydedilen kanca) veya basitçe görmezden gelebilir (kötü).

nohupHUP sinyalini yok saymak için aşağıdaki programı bir tuzakla çalıştıran komuttur. HUP telefonu kapattığınızda programı sonlandırmak için kullanılır (örneğin ssh bağlantınızı kapatın). Ayrıca, programınızın kaybolan bir TTY'yi engellemesini önlemek için çıktıları yeniden yönlendirir. nohupINT sinyalini ihmal ETMEZ. Yani Ctrl+Cçalışmayı engellemez .

Sorununuzun nohup'tan değil "ve" işaretinden (&) kaynaklandığını varsayıyorum. Ctrl+Cön plan süreçlerine bir sinyal gönderir. Ve işareti, uygulamanızın arka planda çalışmasına neden olur. Tek çözüm: yapmak

kill -INT pid

Kullanın kill -9veya kill -KILLkötüdür çünkü uygulama (burada JVM) onu düzgün bir şekilde sonlandırmak için yakalayamaz.

Diğer bir çözüm de uygulamanızı ön plana çıkarmaktır. Sonra Ctrl+Cçalışacak. Bash Job kontrolüne daha kesin olarak bir göz atın fg.


2

exit()Spring boot uygulamanızı zarif bir şekilde kapatmak için SpringApplication sınıfındaki statik yöntemi kullanın .

public class SomeClass {
    @Autowire
    private ApplicationContext context

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

Bu benim için çalışıyor. Çok teşekkür ederim.
Khachornchit Songsaen

1

Spring Boot artık zarif kapatmayı destekliyor (şu anda sürüm öncesi sürümlerde, 2.3.0.BUILD-SNAPSHOT)

Etkinleştirildiğinde, uygulamanın kapatılması yapılandırılabilir bir süre için yetkisiz kullanım süresi içerecektir. Bu ek süre boyunca, mevcut isteklerin tamamlanmasına izin verilecek, ancak yeni isteklere izin verilmeyecek

Şununla etkinleştirebilirsiniz:

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

Linux ortamındaysanız tek yapmanız gereken, /etc/init.d/ içinden .jar dosyanıza bir sembolik bağlantı oluşturmaktır.

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

Daha sonra herhangi bir hizmet gibi uygulamayı başlatabilirsiniz.

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

Uygulamayı kapatmak için

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

Bu şekilde, terminalden çıktığınızda uygulama sona ermeyecektir. Ve uygulama, durdurma komutuyla nazikçe kapanacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.