In order to remind people to commit a crime knowingly (it is not uncommon to fall twice in a pit), because the business system uses a large number of spring Boot embedded tomcat mode operation, we often see the kill instruction in Linux in some operation and maintenance scripts. However, its use is also exquisite. We should think about how to achieve elegant shutdown.
What is graceful shutdown
This is to ensure that the application process is notified to release the occupied resources when the application is closed
-
Thread pool, shutdown (do not accept new tasks and wait for processing to be completed) or shutdown now (call Thread.interrupt to interrupt)
-
socket links, such as netty and mq
-
Tell the registration center to go offline quickly (the customer service has already jumped up by the heartbeat mechanism), such as eureka
-
Clean up temporary files, such as poi
-
Various in heap and out of heap memory releases
In short, the forced termination of the process will lead to data loss or the terminal cannot recover to the normal state. In the distributed environment, it may also lead to data inconsistency.
kill instruction
Kill-9 PID can simulate an extreme situation such as system downtime and system power failure, while kill-15 PID is waiting for the application to close and perform blocking operation. Sometimes, the application cannot be closed (ideally, the bug should be traced to the source)
#View jvm process pid jps #List all signal names kill -l # Signal constant value under Windows # Abbreviation: full name value # Int SIGINT 2 Ctrl + C interrupt # Ill sigill 4 illegal instruction # FPE SIGFPE 8 floating point exception # Segv sigsegsegv 11 segment violation # Term SIGTERM 5 software termination signal from kill # Break sigbreak 21 Ctrl break sequence # ABRT SIGABRT 22 abnormal termination triggered by abort call(Abort) #linux signal constant value # Abbreviation: full name value # HUP SIGHUP 1 terminal disconnection # Int SIGINT 2 interrupt (same as Ctrl + C) # Quit sigquit 3 exit (same as Ctrl + \) # Kill sigkill 9 forced termination # Term SIGTERM 15 termination # Cont sigcont 18 continue (opposite to STOP, fg/bg command) # Stop sigstop 19 pause (same as Ctrl + Z) #.... #It can be understood that the operating system forcibly kills a process from the kernel level kill -9 pid #It is understood as sending a notification and waiting for the application to close actively kill -15 pid #It also supports the full name or abbreviation of signal constant value (that is, after removing SIG) kill -l KILL
Thinking: how does the jvm accept and process linux semaphores?
Of course, the custom SignalHandler is loaded when the jvm is started, and the corresponding handle is triggered when the jvm is closed.
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() { } //The jvm sets SignalHandler in system Triggered in initializesystemclass static void setup() { if (handler == null) { SignalHandler var0 = new SignalHandler() { public void handle(Signal var1) { Shutdown.exit(var1.getNumber() + 128);//Call shutdown exit } }; handler = var0; try { Signal.handle(new Signal("INT"), var0);//When interrupted } catch (IllegalArgumentException var3) { ; } try { Signal.handle(new Signal("TERM"), var0);//Upon termination } catch (IllegalArgumentException var2) { ; } } } }
Runtime.addShutdownHook
Learn about shutdown Before you exit, look at the runtime getRuntime(). addShutdownHook(shutdownHook); Add a closed hook to the jvm and call it when the jvm is closed.
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); } } //It contains data structure and logical management virtual machine shutdown sequence class Shutdown { /* Shutdown Series status*/ private static final int RUNNING = 0; private static final int HOOKS = 1; private static final int FINALIZERS = 2; private static int state = RUNNING; /* Should I run all finalizers to exit? */ private static boolean runFinalizersOnExit = false; // The system closes the hook and registers a predefined slot // The list of closed hooks is as follows: // (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]; // The index of the hook on which the close hook is currently running private static int currentRunningHook = 0; /* The preceding static fields are protected by this lock */ private static class Lock { }; private static Object lock = new Lock(); /* Provide a lock object for the native halt method */ 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) {//Do not add hook during shutdown if (state > RUNNING)//If you are already performing a shutdown operation, you cannot add a hook throw new IllegalStateException("Shutdown in progress"); } else {//If hooks has been executed, you cannot add any more hooks. If the added slot point is smaller than the currently executed slot point when hooks is being executed, it cannot be added if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook)) throw new IllegalStateException("Shutdown in progress"); } hooks[slot] = hook; } } /* Execute all registered 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; } } } } /* Close the JVM */ static void halt(int status) { synchronized (haltLock) { halt0(status); } } //JNI method static native void halt0(int status); // Execution order of shutdown: runhooks > runfinalizesonexit 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. Execute on exit, runhooks > runfinalizesonexit > 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); } } //The shutdown operation is different from exit in that the halt operation is not performed (the JVM is shut down) 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
In spring, some actions are triggered through the contextclosedevevent event (which can be expanded), mainly through the lifecycle processor Onclose to do stopBeans. It can be seen that spring has also expanded based on 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 { //Publish shutdown events within the app publishEvent(new ContextClosedEvent(this)); } catch (Throwable ex) { logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); } // Stop all Lifecycle beans try { getLifecycleProcessor().onClose(); } catch (Throwable ex) { logger.warn("Exception thrown from LifecycleProcessor on context close", ex); } // Destroying spring's BeanFactory may cache a singleton Bean destroyBeans(); // Close the current application context (BeanFactory) closeBeanFactory(); // Execute the closing logic of the subclass 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
That's the point. The spring boot starter actuator module in spring boot provides a restful interface for graceful shutdown. Execute request curl -X POST http://127.0.0.1:8088/shutdown , and a prompt will be returned after closing successfully.
Note: in the online environment, the url needs to set permissions, which can be used in conjunction with spring security or restrict intranet access in nginx
#Enable shutdown endpoints.shutdown.enabled=true #Disable password authentication endpoints.shutdown.sensitive=false #The paths of all endpoints can be specified uniformly management.context-path=/manage #Specify management port and IP management.port=8088 management.address=127.0.0.1 #Enable spring security for shutdown endpoints.shutdown.sensitive=true #Verify user name security.user.name=admin #Verify password security.user.password=secret #role management.security.role=SUPERUSER
The shutdown principle of spring boot is not complicated. In fact, it is by calling abstractapplicationcontext Close implementation.
@ConfigurationProperties( prefix = "endpoints.shutdown" ) public class ShutdownMvcEndpoint extends EndpointMvcAdapter { public ShutdownMvcEndpoint(ShutdownEndpoint delegate) { super(delegate); } //post request @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); } //Execute close 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(); } //This calls 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; } } }