springBoot小程序開發的項目,後台如何優雅的停止進程

發布時間:2022-04-01 11:35:17 作者:King 來源:本站 浏覽量(3060) 點贊(107)
摘要:目錄何爲優雅關機kill 指令Runtime.addShutdownHookSpring 3.2.12SpringBoot再談爲了提醒明知(zhī)故犯(在一坑裏叠倒兩次不是(shì)不多見(jiàn)),在小程序開發的業務,由于業務系統中大量使用了 SpringBoot embedded tomcat 的模式運行,在一些運維腳本中經常看到 Linux 中 kill 指令,然而它的使用也有些講究,要思考如何能做到優雅停機

目錄

  • 何爲優雅關機
  • kill 指令
  • Runtime.addShutdownHook
  • Spring 3.2.12
  • SpringBoot

再談爲了提醒明知(zhī)故犯(在一坑裏叠倒兩次不是(shì)不多見(jiàn)),在小程序開發的業務,由于業務系統中大量使用了 SpringBoot embedded tomcat 的模式運行,在一些運維腳本中經常看到 Linux 中 kill 指令,然而它的使用也有些講究,要思考如何能做到優雅停機。

springBoot小程序開發的項目,後台如何優雅的停止進程

何爲優雅關機

就是(shì)爲确保應用關閉時,通知(zhī)應用進程釋放(fàng)所占用的資源:

  • 線(xiàn)程池,shutdown(不接受新任務等待處理完)還是(shì) shutdownNow(調用 Thread.interrupt 進行中斷)
  • Socket 鏈接,比如:Netty、MQ
  • 告知(zhī)注冊中心快速下線(xiàn)(靠心跳(tiào)機制客服早都跳(tiào)起來了),比如:Eureka
  • 清理臨時文件,比如:POI
  • 各種堆内堆外内存釋放(fàng)

總之,小程序開發的程序進程強行終止會帶來數據丢失或者終端無法恢複到正常狀态,在分布式環境下還可能導緻數據不一緻的情況。

kill 指令

kill -9 pid 可以模拟了一次系統宕機,系統斷電等極端情況,而 kill -15 pid 則是(shì)等待應用關閉,執行阻塞操作,有時候也會出現(xiàn)無法關閉應用的情況(線(xiàn)上理想情況下,是(shì) 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    終端斷線(xiàn)  
# INT   SIGINT      2    中斷(同 Ctrl + C)        
# QUIT  SIGQUIT     3    退出(同 Ctrl + )        
# KILL  SIGKILL     9    強制終止        
# TERM  SIGTERM     15    終止        
# CONT  SIGCONT     18    繼續(與STOP相(xiàng)反, fg/bg命令)        
# STOP  SIGSTOP     19    暫停(同 Ctrl + Z)        
#....

#可以理解爲操作系統從内核級别強行殺死某個進程
kill -9 pid
#理解爲發送一個通知(zhī),等待應用主動關閉
kill -15 pid
#也支持信号常量值全稱或簡寫(就是(shì)去(qù)掉SIG後)
kill -l KILL

思考:JVM 是(shì)如何接受處理 Linux 信号量的?

當然是(shì)在 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);

則是(shì)爲 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;
    /* 是(shì)否應該運行所以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不同的是(shì)不做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。

由此可見(jiàn) 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();
}

SpringBoot

到這裏就進入重點了,SpringBoot 中有 spring-boot-starter-actuator 模塊提供了一個 restful 接口,用于優雅停機。

執行請求 curl -X POST http://127.0.0.1:8088/shutdown ,待關閉成功則返回提示。

注:線(xiàn)上環境該 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

SpringBoot 的 shutdown 原理也不複雜(zá),其實還是(shì)通過調用 AbstractApplicationContext.close 實現(xiàn)的。

@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<StringObject>> 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"truefalse);
    }
    //執行關閉
    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();
                    }
                    //這個調用的就是(shì)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;
        }
    }
}

經過以上操作,可以優雅的排除使用springBoot小程序開發的程序在運行過程中,導緻數據丢失或者意想不到的BUG問題,小夥伴們,有沒有GET到知(zhī)識點呢。

微信

掃一掃,關注我們

感興趣嗎(ma)?

歡迎聯系我們,我們願意爲您解答任何有關網站疑難問題!

【如有開發需求】那就聯系我們吧

搜索千萬次不如咨詢1次

承接:網站建設,手機網站,響應式網站,小程序開發,原生android開發等業務

立即咨詢 16605125102