再谈为了揭示知法犯法(在一坑里迭倒两次不是不多见),因为业务零碎中大量应用了spring Boot embedded tomcat
的模式运行,在一些运维脚本中常常看到Linux 中 kill
指令,然而它的应用也有些考究,要思考如何能做到优雅停机。
何为优雅关机
就是为确保利用敞开时,告诉利用过程开释所占用的资源
- 线程池,shutdown(不承受新工作期待解决完)还是shutdownNow(调用
Thread.interrupt
进行中断) - socket 链接,比方:netty、mq
- 告知注册核心疾速下线(靠心跳机制客服早都跳起来了),比方:eureka
- 清理临时文件,比方:poi
- 各种堆内堆外内存开释
总之,过程强行终止会带来数据失落或者终端无奈复原到失常状态,在分布式环境下还可能导致数据不统一的状况。
kill指令
kill -9 pid
能够模仿了一次零碎宕机,零碎断电等极其状况,而kill -15 pid
则是期待利用敞开,执行阻塞操作,有时候也会呈现无奈敞开利用的状况(线上现实状况下,是bug就该寻根溯源)
#查看jvm过程pidjps#列出所有信号名称kill -l # Windows下信号常量值# 简称 全称 数值 # INT SIGINT 2 Ctrl+C中断# ILL SIGILL 4 非法指令# FPE SIGFPE 8 floating point exception(浮点异样)# SEGV SIGSEGV 11 segment violation(段谬误)# TERM SIGTERM 5 Software termination signal from kill(Kill收回的软件终止)# BREAK SIGBREAK 21 Ctrl-Break sequence(Ctrl+Break中断)# ABRT SIGABRT 22 abnormal termination triggered by abort call(Abort) #linux信号常量值# 简称 全称 数值 # HUP SIGHUP 1 终端断线 # INT SIGINT 2 中断(同 Ctrl + C) # QUIT SIGQUIT 3 退出(同 Ctrl + \) # KILL SIGKILL 9 强制终止 # TERM SIGTERM 15 终止 # CONT SIGCONT 18 持续(与STOP相同, fg/bg命令) # STOP SIGSTOP 19 暂停(同 Ctrl + Z) #.... #能够了解为操作系统从内核级别强行杀死某个过程kill -9 pid #了解为发送一个告诉,期待利用被动敞开kill -15 pid#也反对信号常量值全称或简写(就是去掉SIG后)kill -l KILL
思考:jvm是如何承受解决linux信号量的?
当然是在jvm启动时就加载了自定义SignalHandler
,敞开jvm时触发对应的handle。
public interface SignalHandler { SignalHandler SIG_DFL = new NativeSignalHandler(0L); SignalHandler SIG_IGN = new NativeSignalHandler(1L); void handle(Signal var1);}class Terminator { private static SignalHandler handler = null; Terminator() { } //jvm设置SignalHandler,在System.initializeSystemClass中触发 static void setup() { if (handler == null) { SignalHandler var0 = new SignalHandler() { public void handle(Signal var1) { Shutdown.exit(var1.getNumber() + 128);//调用Shutdown.exit } }; handler = var0; try { Signal.handle(new Signal("INT"), var0);//中断时 } catch (IllegalArgumentException var3) { ; } try { Signal.handle(new Signal("TERM"), var0);//终止时 } catch (IllegalArgumentException var2) { ; } } }}
Runtime.addShutdownHook
在理解Shutdown.exit
之前,先看Runtime.getRuntime().addShutdownHook(shutdownHook);
则是为jvm中减少一个敞开的钩子,当jvm敞开的时候调用。
public class Runtime { public void addShutdownHook(Thread hook) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("shutdownHooks")); } ApplicationShutdownHooks.add(hook); }}class ApplicationShutdownHooks { /* The set of registered hooks */ private static IdentityHashMap<Thread, Thread> hooks; static synchronized void add(Thread hook) { if(hooks == null) throw new IllegalStateException("Shutdown in progress"); if (hook.isAlive()) throw new IllegalArgumentException("Hook already running"); if (hooks.containsKey(hook)) throw new IllegalArgumentException("Hook previously registered"); hooks.put(hook, hook); }}//它含数据结构和逻辑治理虚拟机敞开序列class Shutdown { /* Shutdown 系列状态*/ private static final int RUNNING = 0; private static final int HOOKS = 1; private static final int FINALIZERS = 2; private static int state = RUNNING; /* 是否应该运行所以finalizers来exit? */ private static boolean runFinalizersOnExit = false; // 零碎敞开钩子注册一个预约义的插槽. // 敞开钩子的列表如下: // (0) Console restore hook // (1) Application hooks // (2) DeleteOnExit hook private static final int MAX_SYSTEM_HOOKS = 10; private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS]; // 以后运行敞开钩子的钩子的索引 private static int currentRunningHook = 0; /* 后面的动态字段由这个锁爱护 */ private static class Lock { }; private static Object lock = new Lock(); /* 为native halt办法提供锁对象 */ private static Object haltLock = new Lock(); static void add(int slot, boolean registerShutdownInProgress, Runnable hook) { synchronized (lock) { if (hooks[slot] != null) throw new InternalError("Shutdown hook at slot " + slot + " already registered"); if (!registerShutdownInProgress) {//执行shutdown过程中不增加hook if (state > RUNNING)//如果曾经在执行shutdown操作不能增加hook throw new IllegalStateException("Shutdown in progress"); } else {//如果hooks曾经执行结束不能再增加hook。如果正在执行hooks时,增加的槽点小于以后执行的槽点地位也不能增加 if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook)) throw new IllegalStateException("Shutdown in progress"); } hooks[slot] = hook; } } /* 执行所有注册的hooks */ private static void runHooks() { 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; } } } } /* 敞开JVM的操作 */ static void halt(int status) { synchronized (haltLock) { halt0(status); } } //JNI办法 static native void halt0(int status); // shutdown的执行程序:runHooks > runFinalizersOnExit private static void sequence() { 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时执行,runHooks > runFinalizersOnExit > halt static void exit(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操作(敞开JVM) static void shutdown() { 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中通过ContextClosedEvent
事件来触发一些动作(能够拓展),次要通过LifecycleProcessor.onClose
来做stopBeans
。由此可见spring也基于jvm做了拓展。
举荐一个开源收费的 Spring Boot 最全教程:
https://github.com/javastacks/spring-boot-best-practice
public abstract class AbstractApplicationContext extends DefaultResourceLoader { public void registerShutdownHook() { if (this.shutdownHook == null) { // No shutdown hook registered yet. this.shutdownHook = new Thread() { @Override public void run() { doClose(); } }; Runtime.getRuntime().addShutdownHook(this.shutdownHook); } } protected void doClose() { 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 { //公布利用内的敞开事件 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可能会缓存单例的 Bean. destroyBeans(); // 敞开以后利用上下文(BeanFactory) closeBeanFactory(); // 执行子类的敞开逻辑 onClose(); synchronized (this.activeMonitor) { this.active = false; } } } }public interface LifecycleProcessor extends Lifecycle { /** * Notification of context refresh, e.g. for auto-starting components. */ void onRefresh(); /** * Notification of context close phase, e.g. for auto-stopping components. */ void onClose();}
spring boot
到这里就进入重点了,spring boot中有spring-boot-starter-actuator
模块提供了一个 restful 接口,用于优雅停机。执行申请 curl -X POST http://127.0.0.1:8088/shutdown
,待敞开胜利则返回提醒。
Spring Boot 根底就不介绍了,举荐看这个收费教程:
https://github.com/javastacks/spring-boot-best-practice
注:线上环境该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")public class ShutdownMvcEndpoint extends EndpointMvcAdapter { public ShutdownMvcEndpoint(ShutdownEndpoint delegate) { super(delegate); } //post申请 @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")public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>> implements ApplicationContextAware { private static final Map<String, Object> NO_CONTEXT_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap("message", "No context to shutdown.")); private static final Map<String, Object> SHUTDOWN_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap("message", "Shutting down, bye...")); private ConfigurableApplicationContext context; public ShutdownEndpoint() { super("shutdown", true, false); } //执行敞开 public Map<String, Object> invoke() { if (this.context == null) { return NO_CONTEXT_MESSAGE; } else { boolean var6 = false; Map var1; class NamelessClass_1 implements Runnable { NamelessClass_1() { } public void run() { try { Thread.sleep(500L); } catch (InterruptedException var2) { Thread.currentThread().interrupt(); } //这个调用的就是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; } }}
版权申明:本文为CSDN博主「布道」的原创文章,遵循CC 4.0 BY-SA版权协定,转载请附上原文出处链接及本申明。原文链接:https://blog.csdn.net/alex_xf...
近期热文举荐:
1.1,000+ 道 Java面试题及答案整顿(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!