再谈为了揭示知法犯法(在一坑里迭倒两次不是不多见),因为业务零碎中大量应用了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开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!