0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

求求你們別再用kill -9了,這才是Spring Boot停機(jī)的正確方式!

jf_ro2CN3Fa ? 來源:CSDN ? 2023-05-15 14:56 ? 次閱讀


再談為了提醒明知故犯(在一坑里迭倒兩次不是不多見),由于業(yè)務(wù)系統(tǒng)中大量使用了spring Boot embedded tomcat的模式運(yùn)行,在一些運(yùn)維腳本中經(jīng)??吹?a href="http://ttokpm.com/v/tag/538/" target="_blank">Linuxkill 指令,然而它的使用也有些講究,要思考如何能做到優(yōu)雅停機(jī)。

何為優(yōu)雅關(guān)機(jī)

就是為確保應(yīng)用關(guān)閉時(shí),通知應(yīng)用進(jìn)程釋放所占用的資源

  • 線程池,shutdown(不接受新任務(wù)等待處理完)還是shutdownNow(調(diào)用 Thread.interrupt進(jìn)行中斷)
  • socket 鏈接,比如:netty、mq
  • 告知注冊(cè)中心快速下線(靠心跳機(jī)制客服早都跳起來了),比如:eureka
  • 清理臨時(shí)文件,比如:poi
  • 各種堆內(nèi)堆外內(nèi)存釋放

總之,進(jìn)程強(qiáng)行終止會(huì)帶來數(shù)據(jù)丟失或者終端無法恢復(fù)到正常狀態(tài),在分布式環(huán)境下還可能導(dǎo)致數(shù)據(jù)不一致的情況。

基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

kill指令

kill -9 pid 可以模擬了一次系統(tǒng)宕機(jī),系統(tǒng)斷電等極端情況,而kill -15 pid 則是等待應(yīng)用關(guān)閉,執(zhí)行阻塞操作,有時(shí)候也會(huì)出現(xiàn)無法關(guān)閉應(yīng)用的情況(線上理想情況下,是bug就該尋根溯源)

#查看jvm進(jìn)程pid
jps
#列出所有信號(hào)名稱
kill-l


>基于SpringCloudAlibaba+Gateway+Nacos+RocketMQ+Vue&Element實(shí)現(xiàn)的后臺(tái)管理系統(tǒng)+用戶小程序,支持RBAC動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
>
>*項(xiàng)目地址://github.com/YunaiV/yudao-cloud>
>*視頻教程//doc.iocoder.cn/video/>

#Windows下信號(hào)常量值
#簡(jiǎn)稱全稱數(shù)值
#INTSIGINT2Ctrl+C中斷
#ILLSIGILL4非法指令
#FPESIGFPE8floatingpointexception(浮點(diǎn)異常)
#SEGVSIGSEGV11segmentviolation(段錯(cuò)誤)
#TERMSIGTERM5Softwareterminationsignalfromkill(Kill發(fā)出的軟件終止)
#BREAKSIGBREAK21Ctrl-Breaksequence(Ctrl+Break中斷)
#ABRTSIGABRT22abnormalterminationtriggeredbyabortcall(Abort)

#linux信號(hào)常量值
#簡(jiǎn)稱全稱數(shù)值
#HUPSIGHUP1終端斷線
#INTSIGINT2中斷(同Ctrl+C)
#QUITSIGQUIT3退出(同Ctrl+)
#KILLSIGKILL9強(qiáng)制終止
#TERMSIGTERM15終止
#CONTSIGCONT18繼續(xù)(與STOP相反,fg/bg命令)
#STOPSIGSTOP19暫停(同Ctrl+Z)
#....

#可以理解為操作系統(tǒng)從內(nèi)核級(jí)別強(qiáng)行殺死某個(gè)進(jìn)程
kill-9pid
#理解為發(fā)送一個(gè)通知,等待應(yīng)用主動(dòng)關(guān)閉
kill-15pid
#也支持信號(hào)常量值全稱或簡(jiǎn)寫(就是去掉SIG后)
kill-lKILL

思考:jvm是如何接受處理linux信號(hào)量的?

當(dāng)然是在jvm啟動(dòng)時(shí)就加載了自定義SignalHandler,關(guān)閉jvm時(shí)觸發(fā)對(duì)應(yīng)的handle。

publicinterfaceSignalHandler{
SignalHandlerSIG_DFL=newNativeSignalHandler(0L);
SignalHandlerSIG_IGN=newNativeSignalHandler(1L);

voidhandle(Signalvar1);
}
classTerminator{
privatestaticSignalHandlerhandler=null;

Terminator(){
}
//jvm設(shè)置SignalHandler,在System.initializeSystemClass中觸發(fā)
staticvoidsetup(){
if(handler==null){
SignalHandlervar0=newSignalHandler(){
publicvoidhandle(Signalvar1){
Shutdown.exit(var1.getNumber()+128);//調(diào)用Shutdown.exit
}
};
handler=var0;

try{
Signal.handle(newSignal("INT"),var0);//中斷時(shí)
}catch(IllegalArgumentExceptionvar3){
;
}

try{
Signal.handle(newSignal("TERM"),var0);//終止時(shí)
}catch(IllegalArgumentExceptionvar2){
;
}

}
}
}

Runtime.addShutdownHook

在了解Shutdown.exit之前,先看Runtime.getRuntime().addShutdownHook(shutdownHook);則是為jvm中增加一個(gè)關(guān)閉的鉤子,當(dāng)jvm關(guān)閉的時(shí)候調(diào)用。

publicclassRuntime{
publicvoidaddShutdownHook(Threadhook){
SecurityManagersm=System.getSecurityManager();
if(sm!=null){
sm.checkPermission(newRuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
}
classApplicationShutdownHooks{
/*Thesetofregisteredhooks*/
privatestaticIdentityHashMaphooks;
staticsynchronizedvoidadd(Threadhook){
if(hooks==null)
thrownewIllegalStateException("Shutdowninprogress");

if(hook.isAlive())
thrownewIllegalArgumentException("Hookalreadyrunning");

if(hooks.containsKey(hook))
thrownewIllegalArgumentException("Hookpreviouslyregistered");

hooks.put(hook,hook);
}
}
//它含數(shù)據(jù)結(jié)構(gòu)和邏輯管理虛擬機(jī)關(guān)閉序列
classShutdown{
/*Shutdown系列狀態(tài)*/
privatestaticfinalintRUNNING=0;
privatestaticfinalintHOOKS=1;
privatestaticfinalintFINALIZERS=2;
privatestaticintstate=RUNNING;
/*是否應(yīng)該運(yùn)行所以finalizers來exit?*/
privatestaticbooleanrunFinalizersOnExit=false;
//系統(tǒng)關(guān)閉鉤子注冊(cè)一個(gè)預(yù)定義的插槽.
//關(guān)閉鉤子的列表如下:
//(0)Consolerestorehook
//(1)Applicationhooks
//(2)DeleteOnExithook
privatestaticfinalintMAX_SYSTEM_HOOKS=10;
privatestaticfinalRunnable[]hooks=newRunnable[MAX_SYSTEM_HOOKS];
//當(dāng)前運(yùn)行關(guān)閉鉤子的鉤子的索引
privatestaticintcurrentRunningHook=0;
/*前面的靜態(tài)字段由這個(gè)鎖保護(hù)*/
privatestaticclassLock{};
privatestaticObjectlock=newLock();

/*為nativehalt方法提供鎖對(duì)象*/
privatestaticObjecthaltLock=newLock();

staticvoidadd(intslot,booleanregisterShutdownInProgress,Runnablehook){
synchronized(lock){
if(hooks[slot]!=null)
thrownewInternalError("Shutdownhookatslot"+slot+"alreadyregistered");

if(!registerShutdownInProgress){//執(zhí)行shutdown過程中不添加hook
if(state>RUNNING)//如果已經(jīng)在執(zhí)行shutdown操作不能添加hook
thrownewIllegalStateException("Shutdowninprogress");
}else{//如果hooks已經(jīng)執(zhí)行完畢不能再添加hook。如果正在執(zhí)行hooks時(shí),添加的槽點(diǎn)小于當(dāng)前執(zhí)行的槽點(diǎn)位置也不能添加
if(state>HOOKS||(state==HOOKS&&slot<=?currentRunningHook))
????????????????????thrownewIllegalStateException("Shutdowninprogress");
}

hooks[slot]=hook;
}
}
/*執(zhí)行所有注冊(cè)的hooks
*/
privatestaticvoidrunHooks(){
for(inti=0;itry{
Runnablehook;
synchronized(lock){
//acquirethelocktomakesurethehookregisteredduring
//shutdownisvisiblehere.
currentRunningHook=i;
hook=hooks[i];
}
if(hook!=null)hook.run();
}catch(Throwablet){
if(tinstanceofThreadDeath){
ThreadDeathtd=(ThreadDeath)t;
throwtd;
}
}
}
}
/*關(guān)閉JVM的操作
*/
staticvoidhalt(intstatus){
synchronized(haltLock){
halt0(status);
}
}
//JNI方法
staticnativevoidhalt0(intstatus);
//shutdown的執(zhí)行順序:runHooks>runFinalizersOnExit
privatestaticvoidsequence(){
synchronized(lock){
/*Guardagainstthepossibilityofadaemonthreadinvokingexit
*afterDestroyJavaVMinitiatestheshutdownsequence
*/
if(state!=HOOKS)return;
}
runHooks();
booleanrfoe;
synchronized(lock){
state=FINALIZERS;
rfoe=runFinalizersOnExit;
}
if(rfoe)runAllFinalizers();
}
//Runtime.exit時(shí)執(zhí)行,runHooks>runFinalizersOnExit>halt
staticvoidexit(intstatus){
booleanrunMoreFinalizers=false;
synchronized(lock){
if(status!=0)runFinalizersOnExit=false;
switch(state){
caseRUNNING:/*Initiateshutdown*/
state=HOOKS;
break;
caseHOOKS:/*Stallandhalt*/
break;
caseFINALIZERS:
if(status!=0){
/*Haltimmediatelyonnonzerostatus*/
halt(status);
}else{
/*Compatibilitywitholdbehavior:
*Runmorefinalizersandthenhalt
*/
runMoreFinalizers=runFinalizersOnExit;
}
break;
}
}
if(runMoreFinalizers){
runAllFinalizers();
halt(status);
}
synchronized(Shutdown.class){
/*Synchronizeontheclassobject,causinganyotherthread
*thatattemptstoinitiateshutdowntostallindefinitely
*/
sequence();
halt(status);
}
}
//shutdown操作,與exit不同的是不做halt操作(關(guān)閉JVM)
staticvoidshutdown(){
synchronized(lock){
switch(state){
caseRUNNING:/*Initiateshutdown*/
state=HOOKS;
break;
caseHOOKS:/*Stallandthenreturn*/
caseFINALIZERS:
break;
}
}
synchronized(Shutdown.class){
sequence();
}
}
}

spring 3.2.12

在spring中通過ContextClosedEvent事件來觸發(fā)一些動(dòng)作(可以拓展),主要通過LifecycleProcessor.onClose來做stopBeans。由此可見spring也基于jvm做了拓展。

publicabstractclassAbstractApplicationContextextendsDefaultResourceLoader{
publicvoidregisterShutdownHook(){
if(this.shutdownHook==null){
//Noshutdownhookregisteredyet.
this.shutdownHook=newThread(){
@Override
publicvoidrun(){
doClose();
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
protectedvoiddoClose(){
booleanactuallyClose;
synchronized(this.activeMonitor){
actuallyClose=this.active&&!this.closed;
this.closed=true;
}

if(actuallyClose){
if(logger.isInfoEnabled()){
logger.info("Closing"+this);
}

LiveBeansView.unregisterApplicationContext(this);

try{
//發(fā)布應(yīng)用內(nèi)的關(guān)閉事件
publishEvent(newContextClosedEvent(this));
}
catch(Throwableex){
logger.warn("ExceptionthrownfromApplicationListenerhandlingContextClosedEvent",ex);
}

//停止所有的Lifecyclebeans.
try{
getLifecycleProcessor().onClose();
}
catch(Throwableex){
logger.warn("ExceptionthrownfromLifecycleProcessoroncontextclose",ex);
}

//銷毀spring的BeanFactory可能會(huì)緩存單例的Bean.
destroyBeans();

//關(guān)閉當(dāng)前應(yīng)用上下文(BeanFactory)
closeBeanFactory();

//執(zhí)行子類的關(guān)閉邏輯
onClose();

synchronized(this.activeMonitor){
this.active=false;
}
}
}
}
publicinterfaceLifecycleProcessorextendsLifecycle{
/**
*Notificationofcontextrefresh,e.g.forauto-startingcomponents.
*/
voidonRefresh();

/**
*Notificationofcontextclosephase,e.g.forauto-stoppingcomponents.
*/
voidonClose();
}

spring boot

到這里就進(jìn)入重點(diǎn)了,spring boot中有spring-boot-starter-actuator 模塊提供了一個(gè) restful 接口,用于優(yōu)雅停機(jī)。執(zhí)行請(qǐng)求 curl -X POST http://127.0.0.1:8088/shutdown ,待關(guān)閉成功則返回提示。

注:線上環(huán)境該url需要設(shè)置權(quán)限,可配合 spring-security使用或在nginx中限制內(nèi)網(wǎng)訪問

#啟用shutdown
endpoints.shutdown.enabled=true
#禁用密碼驗(yàn)證
endpoints.shutdown.sensitive=false
#可統(tǒng)一指定所有endpoints的路徑
management.context-path=/manage
#指定管理端口和IP
management.port=8088
management.address=127.0.0.1

#開啟shutdown的安全驗(yàn)證(spring-security)
endpoints.shutdown.sensitive=true
#驗(yàn)證用戶名
security.user.name=admin
#驗(yàn)證密碼
security.user.password=secret
#角色
management.security.role=SUPERUSER

spring boot的shutdown原理也不復(fù)雜,其實(shí)還是通過調(diào)用AbstractApplicationContext.close實(shí)現(xiàn)的。

@ConfigurationProperties(
prefix="endpoints.shutdown"
)
publicclassShutdownMvcEndpointextendsEndpointMvcAdapter{
publicShutdownMvcEndpoint(ShutdownEndpointdelegate){
super(delegate);
}
//post請(qǐng)求
@PostMapping(
produces={"application/vnd.spring-boot.actuator.v1+json","application/json"}
)
@ResponseBody
publicObjectinvoke(){
return!this.getDelegate().isEnabled()?newResponseEntity(Collections.singletonMap("message","Thisendpointisdisabled"),HttpStatus.NOT_FOUND):super.invoke();
}
}
@ConfigurationProperties(
prefix="endpoints.shutdown"
)
publicclassShutdownEndpointextendsAbstractEndpoint<Map<String,Object>>implementsApplicationContextAware{
privatestaticfinalMapNO_CONTEXT_MESSAGE=Collections.unmodifiableMap(Collections.singletonMap("message","Nocontexttoshutdown."));
privatestaticfinalMapSHUTDOWN_MESSAGE=Collections.unmodifiableMap(Collections.singletonMap("message","Shuttingdown,bye..."));
privateConfigurableApplicationContextcontext;

publicShutdownEndpoint(){
super("shutdown",true,false);
}
//執(zhí)行關(guān)閉
publicMapinvoke(){
if(this.context==null){
returnNO_CONTEXT_MESSAGE;
}else{
booleanvar6=false;

Mapvar1;

classNamelessClass_1implementsRunnable{
NamelessClass_1(){
}

publicvoidrun(){
try{
Thread.sleep(500L);
}catch(InterruptedExceptionvar2){
Thread.currentThread().interrupt();
}
//這個(gè)調(diào)用的就是AbstractApplicationContext.close
ShutdownEndpoint.this.context.close();
}
}

try{
var6=true;
var1=SHUTDOWN_MESSAGE;
var6=false;
}finally{
if(var6){
Threadthread=newThread(newNamelessClass_1());
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
}
}

Threadthread=newThread(newNamelessClass_1());
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
returnvar1;
}
}
}


審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • spring
    +關(guān)注

    關(guān)注

    0

    文章

    338

    瀏覽量

    14295
  • Boot
    +關(guān)注

    關(guān)注

    0

    文章

    149

    瀏覽量

    35751
  • kill
    +關(guān)注

    關(guān)注

    0

    文章

    9

    瀏覽量

    2093

原文標(biāo)題:求求你們別再用 kill -9 了,這才是 Spring Boot 停機(jī)的正確方式?。?!

文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Spring Boot如何實(shí)現(xiàn)異步任務(wù)

    Spring Boot 提供多種方式來實(shí)現(xiàn)異步任務(wù),這里介紹三種主要實(shí)現(xiàn)方式。 1、基于注解 @Async @Async 注解是
    的頭像 發(fā)表于 09-30 10:32 ?1380次閱讀

    啟動(dòng)Spring Boot項(xiàng)目應(yīng)用的三種方法

    ,從而使開發(fā)人員不再需要定義樣板化的配置。用我的話來理解,就是spring boot其實(shí)不是什么新的框架,它默認(rèn)配置很多框架的使用方式,就像maven整合
    發(fā)表于 01-14 17:33

    Spring Boot嵌入式Web容器原理是什么

    ,不需要配置任何特殊的XML配置,為了這個(gè)目標(biāo),Spring BootSpring 4.0框架之上提供很多特性,幫助應(yīng)用以“約定優(yōu)于配置”“開箱即用”的
    發(fā)表于 12-16 07:57

    千萬(wàn)別再用這臺(tái)示波器,我怕你會(huì)愛上它!

    千萬(wàn)別再用這臺(tái)示波器,我怕你會(huì)愛上它!
    發(fā)表于 05-30 21:01

    Spring Boot框架錯(cuò)誤處理

    》 《strong》翻譯《/strong》:雁驚寒《/p》 《/blockquote》《p》《em》摘要:本文通過實(shí)例介紹使用Spring Boot在設(shè)計(jì)API的時(shí)候如何正確地對(duì)異常
    發(fā)表于 09-28 15:31 ?0次下載

    Spring Boot從零入門1 詳述

    在開始學(xué)習(xí)Spring Boot之前,我之前從未接觸過Spring相關(guān)的項(xiàng)目,Java基礎(chǔ)還是幾年前自學(xué)的,現(xiàn)在估計(jì)也忘得差不多了吧,寫Spring
    的頭像 發(fā)表于 12-10 22:18 ?596次閱讀

    還在使用kill -9 pid結(jié)束spring boot項(xiàng)目嗎?

    kill可將指定的信息送至程序。預(yù)設(shè)的信息為SIGTERM(15),可將指定程序終止。若仍無法終止該程序,可使用SIGKILL(9)信息嘗試強(qiáng)制刪除程序。程序或工作的編號(hào)可利用ps指令或jobs指令
    的頭像 發(fā)表于 04-13 16:01 ?1343次閱讀
    還在使用<b class='flag-5'>kill</b> -<b class='flag-5'>9</b> pid結(jié)束<b class='flag-5'>spring</b> <b class='flag-5'>boot</b>項(xiàng)目嗎?

    還在使用kill -9 pid結(jié)束spring boot項(xiàng)目嗎?

    kill可將指定的信息送至程序。預(yù)設(shè)的信息為SIGTERM(15),可將指定程序終止。若仍無法終止該程序,可使用SIGKILL(9)信息嘗試強(qiáng)制刪除程序。程序或工作的編號(hào)可利用ps指令或jobs指令
    的頭像 發(fā)表于 04-13 16:01 ?1484次閱讀
    還在使用<b class='flag-5'>kill</b> -<b class='flag-5'>9</b> pid結(jié)束<b class='flag-5'>spring</b> <b class='flag-5'>boot</b>項(xiàng)目嗎?

    Spring Boot特有的實(shí)踐

    Spring Boot是最流行的用于開發(fā)微服務(wù)的Java框架。在本文中,我將與你分享自2016年以來我在專業(yè)開發(fā)中使用Spring Boot所采用的最佳實(shí)踐。這些內(nèi)容是基于我的個(gè)人經(jīng)驗(yàn)
    的頭像 發(fā)表于 09-29 10:24 ?861次閱讀

    強(qiáng)大的Spring Boot 3.0要來了

    來源:OSC開源社區(qū)(ID:oschina2013) Spring Boot 3.0 首個(gè) RC 已發(fā)布,此外還為兩個(gè)分支發(fā)布更新:2.7.5 2.6.13。 3.0.0-RC1: https
    的頭像 發(fā)表于 10-31 11:17 ?1658次閱讀

    Spring Boot Web相關(guān)的基礎(chǔ)知識(shí)

    上一篇文章我們已經(jīng)學(xué)會(huì)了如何通過IDEA快速建立一個(gè)Spring Boot項(xiàng)目,還介紹Spring Boot項(xiàng)目的結(jié)構(gòu),介紹
    的頭像 發(fā)表于 03-17 15:03 ?615次閱讀

    Spring Boot Actuator快速入門

    一下 Spring Boot Actuator ,學(xué)習(xí)如何在 Spring Boot 2.x 中使用、配置和擴(kuò)展這個(gè)監(jiān)控工具。 Spring
    的頭像 發(fā)表于 10-09 17:11 ?592次閱讀

    Spring Boot啟動(dòng) Eureka流程

    在上篇中已經(jīng)說過了 Eureka-Server 本質(zhì)上是一個(gè) web 應(yīng)用的項(xiàng)目,今天就來看看 Spring Boot 是怎么啟動(dòng) Eureka 的。 Spring Boot 啟動(dòng) E
    的頭像 發(fā)表于 10-10 11:40 ?841次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>啟動(dòng) Eureka流程

    Spring Boot的啟動(dòng)原理

    來指定依賴,才能夠運(yùn)行。我們今天就來分析講解一下 Spring Boot 的啟動(dòng)原理。 1. Spring Boot 打包插件 Spring
    的頭像 發(fā)表于 10-13 11:44 ?597次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>的啟動(dòng)原理

    Spring Boot 的設(shè)計(jì)目標(biāo)

    ,這樣我們就可以盡快的上手。 使用 Spring Boot 來不僅可以創(chuàng)建基于 war 方式部署的傳統(tǒng)Java應(yīng)用程序,也可以通過創(chuàng)建獨(dú)立的不依賴任何容器(如 tomcat 等)
    的頭像 發(fā)表于 10-13 14:56 ?542次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b> 的設(shè)計(jì)目標(biāo)