close

點擊上方「Java基基」,選擇「設為星標」

做積極的人,而不是積極廢人!

每天14:00更新文章,每天掉億點點頭髮...

源碼精品專欄

原創 | Java 2021超神之路,很肝~

中文詳細注釋的開源項目

RPC 框架 Dubbo 源碼解析

網絡應用框架 Netty 源碼解析

消息中間件 RocketMQ 源碼解析

數據庫中間件 Sharding-JDBC 和 MyCAT 源碼解析

作業調度中間件 Elastic-Job 源碼解析

分布式事務中間件 TCC-Transaction 源碼解析

Eureka 和 Hystrix 源碼解析

Java 並發源碼

來源:blog.csdn.net/alex_xfboy/

article/details/90404691/

何為優雅關機
kill指令
Runtime.addShutdownHook
spring 3.2.12
spring boot

再談為了提醒明知故犯(在一坑裡迭倒兩次不是不多見),由於業務系統中大量使用了spring Boot embedded tomcat的模式運行,在一些運維腳本中經常看到Linux 中 kill 指令,然而它的使用也有些講究,要思考如何能做到優雅停機。

何為優雅關機

就是為確保應用關閉時,通知應用進程釋放所占用的資源

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

總之,進程強行終止會帶來數據丟失或者終端無法恢復到正常狀態,在分布式環境下還可能導致數據不一致的情況。

基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後台管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

項目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
kill指令

kill -9 pid 可以模擬了一次系統宕機,系統斷電等極端情況,而kill -15 pid 則是等待應用關閉,執行阻塞操作,有時候也會出現無法關閉應用的情況(線上理想情況下,是bug就該尋根溯源)

#查看jvm進程pidjps#列出所有信號名稱kill-l>基於SpringCloudAlibaba+Gateway+Nacos+RocketMQ+Vue&Element實現的後台管理系統+用戶小程序,支持RBAC動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能>>*項目地址:<https://gitee.com/zhijiantianya/yudao-cloud>>*視頻教程:<https://doc.iocoder.cn/video/>#Windows下信號常量值#簡稱全稱數值#INTSIGINT2Ctrl+C中斷#ILLSIGILL4非法指令#FPESIGFPE8floatingpointexception(浮點異常)#SEGVSIGSEGV11segmentviolation(段錯誤)#TERMSIGTERM5Softwareterminationsignalfromkill(Kill發出的軟件終止)#BREAKSIGBREAK21Ctrl-Breaksequence(Ctrl+Break中斷)#ABRTSIGABRT22abnormalterminationtriggeredbyabortcall(Abort)#linux信號常量值#簡稱全稱數值#HUPSIGHUP1終端斷線#INTSIGINT2中斷(同Ctrl+C)#QUITSIGQUIT3退出(同Ctrl+\)#KILLSIGKILL9強制終止#TERMSIGTERM15終止#CONTSIGCONT18繼續(與STOP相反,fg/bg命令)#STOPSIGSTOP19暫停(同Ctrl+Z)#....#可以理解為操作系統從內核級別強行殺死某個進程kill-9pid#理解為發送一個通知,等待應用主動關閉kill-15pid#也支持信號常量值全稱或簡寫(就是去掉SIG後)kill-lKILL

思考:jvm是如何接受處理linux信號量的?

當然是在jvm啟動時就加載了自定義SignalHandler,關閉jvm時觸發對應的handle。

publicinterfaceSignalHandler{SignalHandlerSIG_DFL=newNativeSignalHandler(0L);SignalHandlerSIG_IGN=newNativeSignalHandler(1L);voidhandle(Signalvar1);}classTerminator{privatestaticSignalHandlerhandler=null;Terminator(){}//jvm設置SignalHandler,在System.initializeSystemClass中觸發staticvoidsetup(){if(handler==null){SignalHandlervar0=newSignalHandler(){publicvoidhandle(Signalvar1){Shutdown.exit(var1.getNumber()+128);//調用Shutdown.exit}};handler=var0;try{Signal.handle(newSignal("INT"),var0);//中斷時}catch(IllegalArgumentExceptionvar3){;}try{Signal.handle(newSignal("TERM"),var0);//終止時}catch(IllegalArgumentExceptionvar2){;}}}}Runtime.addShutdownHook

在了解Shutdown.exit之前,先看Runtime.getRuntime().addShutdownHook(shutdownHook);則是為jvm中增加一個關閉的鈎子,當jvm關閉的時候調用。

publicclassRuntime{publicvoidaddShutdownHook(Threadhook){SecurityManagersm=System.getSecurityManager();if(sm!=null){sm.checkPermission(newRuntimePermission("shutdownHooks"));}ApplicationShutdownHooks.add(hook);}}classApplicationShutdownHooks{/*Thesetofregisteredhooks*/privatestaticIdentityHashMap<Thread,Thread>hooks;staticsynchronizedvoidadd(Threadhook){if(hooks==null)thrownewIllegalStateException("Shutdowninprogress");if(hook.isAlive())thrownewIllegalArgumentException("Hookalreadyrunning");if(hooks.containsKey(hook))thrownewIllegalArgumentException("Hookpreviouslyregistered");hooks.put(hook,hook);}}//它含數據結構和邏輯管理虛擬機關閉序列classShutdown{/*Shutdown系列狀態*/privatestaticfinalintRUNNING=0;privatestaticfinalintHOOKS=1;privatestaticfinalintFINALIZERS=2;privatestaticintstate=RUNNING;/*是否應該運行所以finalizers來exit?*/privatestaticbooleanrunFinalizersOnExit=false;//系統關閉鈎子註冊一個預定義的插槽.//關閉鈎子的列表如下://(0)Consolerestorehook//(1)Applicationhooks//(2)DeleteOnExithookprivatestaticfinalintMAX_SYSTEM_HOOKS=10;privatestaticfinalRunnable[]hooks=newRunnable[MAX_SYSTEM_HOOKS];//當前運行關閉鈎子的鈎子的索引privatestaticintcurrentRunningHook=0;/*前面的靜態字段由這個鎖保護*/privatestaticclassLock{};privatestaticObjectlock=newLock();/*為nativehalt方法提供鎖對象*/privatestaticObjecthaltLock=newLock();staticvoidadd(intslot,booleanregisterShutdownInProgress,Runnablehook){synchronized(lock){if(hooks[slot]!=null)thrownewInternalError("Shutdownhookatslot"+slot+"alreadyregistered");if(!registerShutdownInProgress){//執行shutdown過程中不添加hookif(state>RUNNING)//如果已經在執行shutdown操作不能添加hookthrownewIllegalStateException("Shutdowninprogress");}else{//如果hooks已經執行完畢不能再添加hook。如果正在執行hooks時,添加的槽點小於當前執行的槽點位置也不能添加if(state>HOOKS||(state==HOOKS&&slot<=currentRunningHook))thrownewIllegalStateException("Shutdowninprogress");}hooks[slot]=hook;}}/*執行所有註冊的hooks*/privatestaticvoidrunHooks(){for(inti=0;i<MAX_SYSTEM_HOOKS;i++){try{Runnablehook;synchronized(lock){//acquirethelocktomakesurethehookregisteredduring//shutdownisvisiblehere.currentRunningHook=i;hook=hooks[i];}if(hook!=null)hook.run();}catch(Throwablet){if(tinstanceofThreadDeath){ThreadDeathtd=(ThreadDeath)t;throwtd;}}}}/*關閉JVM的操作*/staticvoidhalt(intstatus){synchronized(haltLock){halt0(status);}}//JNI方法staticnativevoidhalt0(intstatus);// shutdown的執行順序:runHooks > runFinalizersOnExitprivatestaticvoidsequence(){synchronized(lock){/*Guardagainstthepossibilityofadaemonthreadinvokingexit*afterDestroyJavaVMinitiatestheshutdownsequence*/if(state!=HOOKS)return;}runHooks();booleanrfoe;synchronized(lock){state=FINALIZERS;rfoe=runFinalizersOnExit;}if(rfoe)runAllFinalizers();}//Runtime.exit時執行,runHooks>runFinalizersOnExit>haltstaticvoidexit(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操作(關閉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事件來觸發一些動作(可以拓展),主要通過LifecycleProcessor.onClose來做stopBeans。由此可見spring也基於jvm做了拓展。

publicabstractclassAbstractApplicationContextextendsDefaultResourceLoader{publicvoidregisterShutdownHook(){if(this.shutdownHook==null){//Noshutdownhookregisteredyet.this.shutdownHook=newThread(){@Overridepublicvoidrun(){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{//發布應用內的關閉事件publishEvent(newContextClosedEvent(this));}catch(Throwableex){logger.warn("ExceptionthrownfromApplicationListenerhandlingContextClosedEvent",ex);}//停止所有的Lifecyclebeans.try{getLifecycleProcessor().onClose();}catch(Throwableex){logger.warn("ExceptionthrownfromLifecycleProcessoroncontextclose",ex);}//銷毀spring的BeanFactory可能會緩存單例的Bean.destroyBeans();//關閉當前應用上下文(BeanFactory)closeBeanFactory();//執行子類的關閉邏輯onClose();synchronized(this.activeMonitor){this.active=false;}}}}publicinterfaceLifecycleProcessorextendsLifecycle{/***Notificationofcontextrefresh,e.g.forauto-startingcomponents.*/voidonRefresh();/***Notificationofcontextclosephase,e.g.forauto-stoppingcomponents.*/voidonClose();}spring boot

到這裡就進入重點了,spring boot中有spring-boot-starter-actuator 模塊提供了一個 restful 接口,用於優雅停機。執行請求 curl -X POST http://127.0.0.1:8088/shutdown ,待關閉成功則返回提示。

註:線上環境該url需要設置權限,可配合 spring-security使用或在nginx中限制內網訪問

#啟用shutdownendpoints.shutdown.enabled=true#禁用密碼驗證endpoints.shutdown.sensitive=false#可統一指定所有endpoints的路徑management.context-path=/manage#指定管理端口和IPmanagement.port=8088management.address=127.0.0.1#開啟shutdown的安全驗證(spring-security)endpoints.shutdown.sensitive=true#驗證用戶名security.user.name=admin#驗證密碼security.user.password=secret#角色management.security.role=SUPERUSER

spring boot的shutdown原理也不複雜,其實還是通過調用AbstractApplicationContext.close實現的。

@ConfigurationProperties(prefix="endpoints.shutdown")publicclassShutdownMvcEndpointextendsEndpointMvcAdapter{publicShutdownMvcEndpoint(ShutdownEndpointdelegate){super(delegate);}//post請求@PostMapping(produces={"application/vnd.spring-boot.actuator.v1+json","application/json"})@ResponseBodypublicObjectinvoke(){return!this.getDelegate().isEnabled()?newResponseEntity(Collections.singletonMap("message","Thisendpointisdisabled"),HttpStatus.NOT_FOUND):super.invoke();}}@ConfigurationProperties(prefix="endpoints.shutdown")publicclassShutdownEndpointextendsAbstractEndpoint<Map<String,Object>>implementsApplicationContextAware{privatestaticfinalMap<String,Object>NO_CONTEXT_MESSAGE=Collections.unmodifiableMap(Collections.singletonMap("message","Nocontexttoshutdown."));privatestaticfinalMap<String,Object>SHUTDOWN_MESSAGE=Collections.unmodifiableMap(Collections.singletonMap("message","Shuttingdown,bye..."));privateConfigurableApplicationContextcontext;publicShutdownEndpoint(){super("shutdown",true,false);}//執行關閉publicMap<String,Object>invoke(){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();}//這個調用的就是AbstractApplicationContext.closeShutdownEndpoint.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;}}}

歡迎加入我的知識星球,一起探討架構,交流源碼。加入方式,長按下方二維碼噢:

已在知識星球更新源碼解析如下:

最近更新《芋道 SpringBoot 2.X 入門》系列,已經 101 余篇,覆蓋了MyBatis、Redis、MongoDB、ES、分庫分表、讀寫分離、SpringMVC、Webflux、權限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能測試等等內容。

提供近 3W 行代碼的 SpringBoot 示例,以及超 6W 行代碼的電商微服務項目。

獲取方式:點「在看」,關注公眾號並回復666領取,更多內容陸續奉上。

文章有幫助的話,在看,轉發吧。

謝謝支持喲 (*^__^*)

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()