求求你们别再用kill -9了,这才是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://github.com/yunaiv/ruoyi-vue-pro 视频教程:https://doc.iocoder.cn/video/ kill指令 kill -9 pid 可以模拟了一次系统宕机,系统断电等极端情况,而kill -15 pid 则是等待应用关闭,执行阻塞操作,有时候也会出现无法关闭应用的情况(线上理想情况下,是bug就该寻根溯源)
#查看jvm进程pidjps#列出所有信号名称kill -l > 基于 spring cloud alibaba + gateway + nacos + rocketmq + vue & element 实现的后台管理系统 + 用户小程序,支持 rbac 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能>> * 项目地址:> * 视频教程:# 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 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  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做了拓展。
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 ,待关闭成功则返回提示。
注:线上环境该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实现的。


OpenAI API功能升级:ChatGPT支持描述函数调用
如何进行别墅智能化设计
“精装房”时代,照明与房地产能擦出怎样的火花?
加密货币相关的思维面试问题解答
闹钟怎样更换铃声
求求你们别再用kill -9了,这才是Spring Boot停机的正确方式!
满洲里市接受音圈马达呼吸机捐赠
整个机箱悬空起来?国外MOD玩家搞怪看,computer chassis
TDA8357J/8359J的引脚排列及功能介绍
卷积神经网络为什么会这么有效?分析卷积神经网络背后的奥秘
如何更加高效的提升存储系统性能?
Mophie发布三合一充电站 支持多种接口
可制造性分析对印刷电路板有何意义
华为P10闪存门最新消息:华为P10闪存门背后的华为Mate9保时捷版又怎样?华为Mate9保时捷版是UFS内存嘛?
利用氮化镓芯片组实现高效率、超紧凑的反激式电源
心率监视手表的制作
如何配置plc硬件?
俄罗斯全新苏-57战机出世,将与美国F-22猛禽战机一决高下
9项数据揭示AI未来发展趋势
由海尔智家主导立项的2项国家标准修订