美文网首页Spring Cloud 程序员我爱编程
Spring Cloud: 使用kill命令优雅关闭微服务, 解

Spring Cloud: 使用kill命令优雅关闭微服务, 解

作者: 司青玄 | 来源:发表于2018-05-23 11:37 被阅读285次

    关于Spring Cloud服务优雅关闭的方案有很多种了,这里介绍一下使用kill {pid}命令优雅关闭的方案,并解决会出现的问题。

    所谓的优雅指两方面,一是程序在退出时要主动向Eureka取消注册自己,二是完成资源清理工作。比如我的程序里用到了线程池来异步执行一些任务,如果退出时不做清理,那么就有异步任务被异常中断导致业务数据不一致的风险。首先我们不能使用kill -9。如果加了-9,那么系统就不会给JVM调用 shutdown hook 的机会,也就无法完成资源清理了。

    退出时取消Euerka注册隐藏的坑

    Spring Cloud默认的EurekaClientAutoConfiguration这个自动配置类已经为我们做好了相应的工作,但是却不够完美。在程序收到kill信号时,JVM会调用 shutdown hook, 虽然在此hook中就有取消注册的逻辑,但我在实践中经常会遇到取消注册耗时特别长,导致 hook 线程block, 进程长时间等待而不能退出。这就会有一个致命的问题,因为kill命令并不会等待目标进程退出才会返回,而是立刻返回,这就意味着kill执行完后你的JVM进程还在。如果出现 hook 线程卡住的情况,那就极有可能当你再次启动服务的时候,上一次服务还没有关闭,从而导致新服务启动失败(往往是因为端口被占用)。

    通过追踪源代码,我发现取消注册的逻辑是在EurekaAutoServiceRegistration#onApplicationEvent()方法中实现的,此方法响应Spring容器的ContextCloseEvent,然后调用stop()方法取消注册,耗时的也是这个stop()方法。因此我们可以自己编写一个类继承此类,覆盖stop()方法,添加超时逻辑:

    @Slf4j
        public static class EngineEurekaAutoServiceRegistration extends EurekaAutoServiceRegistration {
            public EngineEurekaAutoServiceRegistration(ApplicationContext context,
                                                       EurekaServiceRegistry serviceRegistry,
                                                       EurekaRegistration registration) {
                super(context, serviceRegistry, registration);
            }
    
            /**
             * 上下文关闭时会调用此方法, 在另一个线程中取消注册, 防止超时
             */
            @Override
            public void stop() {
                log.info("unregsiter eureka in another thread");
    
                Thread stopThread = new Thread(() -> super.stop());
                stopThread.start();
                try {
                    stopThread.join(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                log.info("unregister done");
            }
        }
    

    在这里我们在新的线程中调用父类的stop(), 然后通过join()方法控制超时时间,设置为2s。
    要想让自己的类起效,我们还要做一些工作。首先我们要编写一个配置类注册自己的bean:

    @Configuration
    public class EngineEurekaConfig extends EurekaClientAutoConfiguration {
        public EngineEurekaConfig(ConfigurableEnvironment env) {
            super(env);
        }
    
    
        @Bean
        @ConditionalOnBean(AutoServiceRegistrationProperties.class)
        @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
        @Override
        public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(ApplicationContext context,
                                                                           EurekaServiceRegistry registry,
                                                                           EurekaRegistration registration) {
            return new EngineEurekaAutoServiceRegistration(context, registry, registration);
        }
    }
    

    然后在@SpringBootApplication注解中排除EurekaClientAutoConfiguration.class:

    @SpringBootApplication( exclude = {
                EurekaClientAutoConfiguration.class
                })
    

    这样Spring就使用我们的类来响应关闭事件了,当unregister过程超时后会直接忽略,进程退出。

    线程池清理

    这部分就比较简单了,写一个@PreDestroy方法,在里面依次调用线程池的pool.shutdown()pool.awaitTermination()即可。

    相关文章

      网友评论

        本文标题:Spring Cloud: 使用kill命令优雅关闭微服务, 解

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