美文网首页
Spring 源码分析 —— 服务优雅关闭

Spring 源码分析 —— 服务优雅关闭

作者: 想起个帅气的头像 | 来源:发表于2021-01-27 23:05 被阅读0次

    一. spring服务如何感知关闭信号

    我们想停掉一个spring的进程,一般通过kill命令完成,常用的命令如kill -2 pid(ctrl + C)kill -9 pidkill -15 pid

    kill -9 可以认为操作系统从内核级别直接强行kill进程,对服务来说没有任何的准备时间和清场时间,直接被kill掉,且无法被监听。
    kill -2 和 -15 则是操作系统给该进程发送一个信号通知,告知应用主动关闭,应用可以监听并接收到信号,可以完成一些关闭回收等动作,然后自我停止。

    准确来说,这些关闭信号并不是由spring感知,而是由java线程Signal Dispatcher监听,此线程将收到的信号交给JVM,JVM判断信号种类,如果是-2/-15等关闭类型,则交由java.lang.Shutdown完成关闭,关闭前会触发所有的shutdown hook。

    Runtime.getRuntime().addShutdownHook()可以添加自定义的shutdown hook。

    关于jvm关闭详细的原理分析过程请参考 java进程关闭事件监听

    二. spring关闭进程源码分析

    spring在启动过程中也是通过Runtime.getRuntime().addShutdownHook()来注册hook回调。

    SpringApplication

    private void refreshContext(ConfigurableApplicationContext context) {
      //registerShutdownHook 默认true
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
        refresh((ApplicationContext) context);
    }
    

    SpringApplicationrun方法中,会调用refreshContext,默认情况下会调用context.registerShutdownHook();

    AbstractApplicationContext

    @Override
    public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            // No shutdown hook registered yet.
            this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
                @Override
                public void run() {
                    synchronized (startupShutdownMonitor) {
                        doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }
    }
    

    AbstractApplicationContext. registerShutdownHook(),实现了添加shutdown hook,对应的回调实现为doClose();

    /**
     * Actually performs context closing: publishes a ContextClosedEvent and
     * destroys the singletons in the bean factory of this application context.
     * <p>Called by both {@code close()} and a JVM shutdown hook, if any.
     * @see org.springframework.context.event.ContextClosedEvent
     * @see #destroyBeans()
     * @see #close()
     * @see #registerShutdownHook()
     */
    protected void doClose() {
        // Check whether an actual close attempt is necessary...
        if (this.active.get() && this.closed.compareAndSet(false, true)) {
            if (logger.isDebugEnabled()) {
            logger.debug("Closing " + this);
            }
            // 注销所有的MBean
            LiveBeansView.unregisterApplicationContext(this);
    
            try {
                // Publish shutdown event.
                // 发布ContextClosedEvent关闭事件
                publishEvent(new ContextClosedEvent(this));
            }
            catch (Throwable ex) {
                logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
            }
    
            // Stop all Lifecycle beans, to avoid delays during individual destruction.
            // 调用所有lifecycle子类bean的关闭方法
            if (this.lifecycleProcessor != null) {
                try {
                    this.lifecycleProcessor.onClose();
                }
                catch (Throwable ex) {
                    logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
                }
            }
    
            // Destroy all cached singletons in the context's BeanFactory.
            // 清空上下文缓存的各类singleton bean
            destroyBeans();
    
            // Close the state of this context itself.
            // 将DefaultListableBeanFactory 置为null
            closeBeanFactory();
    
            // Let subclasses do some final clean-up if they wish...
            // 调用子类扩展的onClose
            onClose();
    
            // Reset local application listeners to pre-refresh state.
            // 重置applicationListeners 为预刷新状态
            if (this.earlyApplicationListeners != null) {
                this.applicationListeners.clear();
                this.applicationListeners.addAll(this.earlyApplicationListeners);
            }
    
            // Switch to inactive.
            // 设置运行标示为false
            this.active.set(false);
        }
    }
    
    1. 调用unregisterApplicationContext注销所有的MBean
    2. 发布ContextClosedEvent事件
    3. 清空上下文缓存的各类singleton bean。
    4. 将beanFactory 置为 null
    5. 调用子类的onClose方法,onClose为上下文子类提供一个实现自我扩展的机制。
    6. 重置applicationListeners
    7. 设置active标示为false

    其中发布ContextClosedEvent事件是非常关键的一步,所有希望完成清场操作的业务或组件都可以监听此事件来完成触发。

    整个回收过程还是比较简单的。

    相关文章

      网友评论

          本文标题:Spring 源码分析 —— 服务优雅关闭

          本文链接:https://www.haomeiwen.com/subject/xcinzktx.html