关于java:求求你们别再用-kill-9-了这才是-Spring-Boot-停机的正确方式

6次阅读

共计 9399 个字符,预计需要花费 24 分钟才能阅读完成。

再谈为了揭示知法犯法(在一坑里迭倒两次不是不多见),因为业务零碎中大量应用了 spring Boot embedded tomcat 的模式运行,在一些运维脚本中常常看到 Linux 中 kill 指令,然而它的应用也有些考究,要思考如何能做到优雅停机。


何为优雅关机

就是为确保利用敞开时,告诉利用过程开释所占用的资源

  • 线程池,shutdown(不承受新工作期待解决完)还是 shutdownNow(调用 Thread.interrupt进行中断)
  • socket 链接,比方:netty、mq
  • 告知注册核心疾速下线(靠心跳机制客服早都跳起来了),比方:eureka
  • 清理临时文件,比方:poi
  • 各种堆内堆外内存开释

总之,过程强行终止会带来数据失落或者终端无奈复原到失常状态,在分布式环境下还可能导致数据不统一的状况。

kill 指令

kill -9 pid 能够模仿了一次零碎宕机,零碎断电等极其状况,而kill -15 pid 则是期待利用敞开,执行阻塞操作,有时候也会呈现无奈敞开利用的状况(线上现实状况下,是 bug 就该寻根溯源)

# 查看 jvm 过程 pid
jps
#列出所有信号名称
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 中限度内网拜访

# 启用 shutdown
endpoints.shutdown.enabled=true
#禁用明码验证
endpoints.shutdown.sensitive=false
#可对立指定所有 endpoints 的门路
management.context-path=/manage
#指定治理端口和 IP
management.port=8088
management.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 开发手册(嵩山版)》最新公布,速速下载!

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

正文完
 0