Java精選面試題(微信小程序):5000+道面試題和選擇題,真實(shí)面經(jīng),簡(jiǎn)歷模版,包含Java基礎(chǔ)、并發(fā)、JVM、線程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架構(gòu)設(shè)計(jì)、大廠真題等,在線隨時(shí)刷題!
再談為了提醒明知故犯(在一坑里迭倒兩次不是不多見(jiàn)),由于業(yè)務(wù)系統(tǒng)中大量使用了spring Boot embedded tomcat的模式運(yùn)行,在一些運(yùn)維腳本中經(jīng)常看到Linux 中 kill 指令,然而它的使用也有些講究,要思考如何能做到優(yōu)雅停機(jī)。
1. 何為優(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ī)制客服早都跳起來(lái)了),比如:eureka
清理臨時(shí)文件,比如:poi
各種堆內(nèi)堆外內(nèi)存釋放
總之,進(jìn)程強(qiáng)行終止會(huì)帶來(lái)數(shù)據(jù)丟失或者終端無(wú)法恢復(fù)到正常狀態(tài),在分布式環(huán)境下還可能導(dǎo)致數(shù)據(jù)不一致的情況。
2. kill 指令
kill -9 pid可以模擬了一次系統(tǒng)宕機(jī),系統(tǒng)斷電等極端情況,而kill -15 pid則是等待應(yīng)用關(guān)閉,執(zhí)行阻塞操作,有時(shí)候也會(huì)出現(xiàn)無(wú)法關(guān)閉應(yīng)用的情況(線上理想情況下,是bug就該尋根溯源)
#查看jvm進(jìn)程pid jps #列出所有信號(hào)名稱 kill -l > 基于 Spring Cloud Alibaba + 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ù)值 # INT SIGINT 2 Ctrl+C中斷 # ILL SIGILL 4 非法指令 # FPE SIGFPE 8 floating point exception(浮點(diǎn)異常) # SEGV SIGSEGV 11 segment violation(段錯(cuò)誤) # TERM SIGTERM 5 Software termination signal from kill(Kill發(fā)出的軟件終止) # BREAK SIGBREAK 21 Ctrl-Break sequence(Ctrl+Break中斷) # ABRT SIGABRT 22 abnormal termination triggered by abort call(Abort) #linux信號(hào)常量值 # 簡(jiǎn)稱 全稱 數(shù)值 # HUP SIGHUP 1 終端斷線 # INT SIGINT 2 中斷(同 Ctrl + C) # QUIT SIGQUIT 3 退出(同 Ctrl + \) # KILL SIGKILL 9 強(qiáng)制終止 # TERM SIGTERM 15 終止 # CONT SIGCONT 18 繼續(xù)(與STOP相反, fg/bg命令) # STOP SIGSTOP 19 暫停(同 Ctrl + Z) #.... #可以理解為操作系統(tǒng)從內(nèi)核級(jí)別強(qiáng)行殺死某個(gè)進(jìn)程 kill -9 pid #理解為發(fā)送一個(gè)通知,等待應(yīng)用主動(dòng)關(guān)閉 kill -15 pid #也支持信號(hào)常量值全稱或簡(jiǎn)寫(就是去掉SIG后) kill -l KILL思考:jvm是如何接受處理linux信號(hào)量的?
當(dāng)然是在jvm啟動(dòng)時(shí)就加載了自定義SignalHandler,關(guān)閉jvm時(shí)觸發(fā)對(duì)應(yīng)的handle。
public interface SignalHandler{ SignalHandler SIG_DFL = new NativeSignalHandler(0L); SignalHandler SIG_IGN = new NativeSignalHandler(1L); voidhandle(Signal var1); } class Terminator{ privatestatic SignalHandler handler = null; Terminator() { } //jvm設(shè)置SignalHandler,在System.initializeSystemClass中觸發(fā) staticvoidsetup(){ if (handler == null) { SignalHandler var0 = new SignalHandler() { publicvoidhandle(Signal var1){ Shutdown.exit(var1.getNumber() + 128);//調(diào)用Shutdown.exit } }; handler = var0; try { Signal.handle(new Signal("INT"), var0);//中斷時(shí) } catch (IllegalArgumentException var3) { ; } try { Signal.handle(new Signal("TERM"), var0);//終止時(shí) } catch (IllegalArgumentException var2) { ; } } } }Runtime.addShutdownHook
在了解Shutdown.exit之前,先看Runtime.getRuntime().addShutdownHook(shutdownHook);則是為jvm中增加一個(gè)關(guān)閉的鉤子,當(dāng)jvm關(guān)閉的時(shí)候調(diào)用。
publicclassRuntime{ publicvoidaddShutdownHook(Thread hook){ SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("shutdownHooks")); } ApplicationShutdownHooks.add(hook); } } classApplicationShutdownHooks{ /* The set of registered hooks */ privatestatic IdentityHashMap hooks; staticsynchronizedvoidadd(Thread hook){ if(hooks == null) thrownew IllegalStateException("Shutdown in progress"); if (hook.isAlive()) thrownew IllegalArgumentException("Hook already running"); if (hooks.containsKey(hook)) thrownew IllegalArgumentException("Hook previously registered"); hooks.put(hook, hook); } } //它含數(shù)據(jù)結(jié)構(gòu)和邏輯管理虛擬機(jī)關(guān)閉序列 classShutdown{ /* Shutdown 系列狀態(tài)*/ privatestaticfinalint RUNNING = 0; privatestaticfinalint HOOKS = 1; privatestaticfinalint FINALIZERS = 2; privatestaticint state = RUNNING; /* 是否應(yīng)該運(yùn)行所以finalizers來(lái)exit? */ privatestaticboolean runFinalizersOnExit = false; // 系統(tǒng)關(guān)閉鉤子注冊(cè)一個(gè)預(yù)定義的插槽. // 關(guān)閉鉤子的列表如下: // (0) Console restore hook // (1) Application hooks // (2) DeleteOnExit hook privatestaticfinalint MAX_SYSTEM_HOOKS = 10; privatestaticfinal Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS]; // 當(dāng)前運(yùn)行關(guān)閉鉤子的鉤子的索引 privatestaticint currentRunningHook = 0; /* 前面的靜態(tài)字段由這個(gè)鎖保護(hù) */ privatestaticclassLock{ }; privatestatic Object lock = new Lock(); /* 為native halt方法提供鎖對(duì)象 */ privatestatic Object haltLock = new Lock(); staticvoidadd(int slot, boolean registerShutdownInProgress, Runnable hook){ synchronized (lock) { if (hooks[slot] != null) thrownew InternalError("Shutdown hook at slot " + slot + " already registered"); if (!registerShutdownInProgress) {//執(zhí)行shutdown過(guò)程中不添加hook if (state > RUNNING)//如果已經(jīng)在執(zhí)行shutdown操作不能添加hook thrownew IllegalStateException("Shutdown in progress"); } else {//如果hooks已經(jīng)執(zhí)行完畢不能再添加hook。如果正在執(zhí)行hooks時(shí),添加的槽點(diǎn)小于當(dāng)前執(zhí)行的槽點(diǎn)位置也不能添加 if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook)) thrownew IllegalStateException("Shutdown in progress"); } hooks[slot] = hook; } } /* 執(zhí)行所有注冊(cè)的hooks */ privatestaticvoidrunHooks(){ for (int i=0; i < MAX_SYSTEM_HOOKS; i++) { try { Runnable hook; synchronized (lock) { // acquire the lock to make sure the hook registered during // shutdown is visible here. currentRunningHook = i; hook = hooks[i]; } if (hook != null) hook.run(); } catch(Throwable t) { if (t instanceof ThreadDeath) { ThreadDeath td = (ThreadDeath)t; throw td; } } } } /* 關(guān)閉JVM的操作 */ staticvoidhalt(int status){ synchronized (haltLock) { halt0(status); } } //JNI方法 staticnativevoidhalt0(int status); // shutdown的執(zhí)行順序:runHooks > runFinalizersOnExit privatestaticvoidsequence(){ synchronized (lock) { /* Guard against the possibility of a daemon thread invoking exit * after DestroyJavaVM initiates the shutdown sequence */ if (state != HOOKS) return; } runHooks(); boolean rfoe; synchronized (lock) { state = FINALIZERS; rfoe = runFinalizersOnExit; } if (rfoe) runAllFinalizers(); } //Runtime.exit時(shí)執(zhí)行,runHooks > runFinalizersOnExit > halt staticvoidexit(int status){ boolean runMoreFinalizers = false; synchronized (lock) { if (status != 0) runFinalizersOnExit = false; switch (state) { case RUNNING: /* Initiate shutdown */ state = HOOKS; break; case HOOKS: /* Stall and halt */ break; case FINALIZERS: if (status != 0) { /* Halt immediately on nonzero status */ halt(status); } else { /* Compatibility with old behavior: * Run more finalizers and then halt */ runMoreFinalizers = runFinalizersOnExit; } break; } } if (runMoreFinalizers) { runAllFinalizers(); halt(status); } synchronized (Shutdown.class) { /* Synchronize on the class object, causing any other thread * that attempts to initiate shutdown to stall indefinitely */ sequence(); halt(status); } } //shutdown操作,與exit不同的是不做halt操作(關(guān)閉JVM) staticvoidshutdown(){ synchronized (lock) { switch (state) { case RUNNING: /* Initiate shutdown */ state = HOOKS; break; case HOOKS: /* Stall and then return */ case FINALIZERS: break; } } synchronized (Shutdown.class) { sequence(); } } }spring 3.2.12
在spring中通過(guò)ContextClosedEvent事件來(lái)觸發(fā)一些動(dòng)作(可以拓展),主要通過(guò)LifecycleProcessor.onClose來(lái)做stopBeans。由此可見(jiàn)spring也基于jvm做了拓展。
publicabstractclassAbstractApplicationContextextendsDefaultResourceLoader{ publicvoidregisterShutdownHook(){ if (this.shutdownHook == null) { // No shutdown hook registered yet. this.shutdownHook = new Thread() { @Override publicvoidrun(){ doClose(); } }; Runtime.getRuntime().addShutdownHook(this.shutdownHook); } } protectedvoiddoClose(){ boolean actuallyClose; 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(new ContextClosedEvent(this)); } catch (Throwable ex) { logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); } // 停止所有的Lifecycle beans. try { getLifecycleProcessor().onClose(); } catch (Throwable ex) { logger.warn("Exception thrown from LifecycleProcessor on context close", 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{ /** * Notification of context refresh, e.g. for auto-starting components. */ voidonRefresh(); /** * Notification of context close phase, e.g. for auto-stopping components. */ 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)訪問(wèn)。
#啟用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 #開(kāi)啟shutdown的安全驗(yàn)證(spring-security) endpoints.shutdown.sensitive=true #驗(yàn)證用戶名 security.user.name=admin #驗(yàn)證密碼 security.user.password=secret #角色 management.security.role=SUPERUSERspring boot的shutdown原理也不復(fù)雜,其實(shí)還是通過(guò)調(diào)用AbstractApplicationContext.close實(shí)現(xiàn)的。
@ConfigurationProperties( prefix = "endpoints.shutdown" ) publicclassShutdownMvcEndpointextendsEndpointMvcAdapter{ publicShutdownMvcEndpoint(ShutdownEndpoint delegate){ super(delegate); } //post請(qǐng)求 @PostMapping( produces = {"application/vnd.spring-boot.actuator.v1+json", "application/json"} ) @ResponseBody public Object invoke(){ return !this.getDelegate().isEnabled() ? new ResponseEntity(Collections.singletonMap("message", "This endpoint is disabled"), HttpStatus.NOT_FOUND) : super.invoke(); } } @ConfigurationProperties( prefix = "endpoints.shutdown" ) publicclassShutdownEndpointextendsAbstractEndpoint
> implementsApplicationContextAware{ privatestaticfinal Map NO_CONTEXT_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap( "message", "No context to shutdown.")); privatestaticfinal Map SHUTDOWN_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap( "message", "Shutting down, bye...")); private ConfigurableApplicationContext context; publicShutdownEndpoint(){ super("shutdown", true, false); } //執(zhí)行關(guān)閉 public Map invoke(){ if (this.context == null) { return NO_CONTEXT_MESSAGE; } else { boolean var6 = false; Map var1; classNamelessClass_1implementsRunnable{ NamelessClass_1() { } publicvoidrun(){ try { Thread.sleep(500L); } catch (InterruptedException var2) { Thread.currentThread().interrupt(); } //這個(gè)調(diào)用的就是AbstractApplicationContext.close ShutdownEndpoint.this.context.close(); } } try { var6 = true; var1 = SHUTDOWN_MESSAGE; var6 = false; } finally { if (var6) { Thread thread = new Thread(new NamelessClass_1()); thread.setContextClassLoader(this.getClass().getClassLoader()); thread.start(); } } Thread thread = new Thread(new NamelessClass_1()); thread.setContextClassLoader(this.getClass().getClassLoader()); thread.start(); return var1; } } }
引用資料:https://linux.die.net/man/1/kill
作者:布道
來(lái)源:https://juejin.cn/post/7394073179483947008
公眾號(hào)“Java精選”所發(fā)表內(nèi)容注明來(lái)源的,版權(quán)歸原出處所有(無(wú)法查證版權(quán)的或者未注明出處的均來(lái)自網(wǎng)絡(luò),系轉(zhuǎn)載,轉(zhuǎn)載的目的在于傳遞更多信息,版權(quán)屬于原作者。如有侵權(quán),請(qǐng)聯(lián)系,筆者會(huì)第一時(shí)間刪除處理!
最近有很多人問(wèn),有沒(méi)有讀者交流群!加入方式很簡(jiǎn)單,公眾號(hào)Java精選,回復(fù)“加群”,即可入群!
文章有幫助的話,點(diǎn)在看,轉(zhuǎn)發(fā)吧!
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.