求求你们别再用 kill -9 了,这才是 Spring Boot 停机的正确方式!!!_Java技术栈的技术博客_51CTO博客

求求你们别再用 kill -9 了,这才是 Spring Boot 停机的正确方式!!!_spring

再谈为了提醒明知故犯(在一坑里迭倒两次不是不多见),由于业务系统中大量使用了​​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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
思考: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) {  
            ;  
        }  

    }  
}  

}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

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();  
    }  
}  

}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.

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();
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

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;  
    }  
}  

}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.

原网址: 访问
创建于: 2023-04-27 17:26:35
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论