再談為了提醒明知故犯(在一坑里迭倒兩次不是不多見),由于業(yè)務(wù)系統(tǒng)中大量使用了spring Boot embedded tomcat
的模式運(yùn)行,在一些運(yùn)維腳本中經(jīng)??吹?a href="http://ttokpm.com/v/tag/538/" target="_blank">Linux 中 kill
指令,然而它的使用也有些講究,要思考如何能做到優(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;
}
}
}
審核編輯 :李倩
-
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)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論