关于优雅停止spring Boot项目的两种方式:

关于优雅停止spring Boot项目的两种方式:

方法一:

首先我们不能使用kill-9。如果加了-9,那么系统就不会给JVM调用 shutdown hook 的机会,也就无法完成资源清理了。也不能使用kill -6 否则无法调用到移除eureka的方法。

退出取消Euerka注册

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

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

public class EngineEurekaAutoServiceRegistration extends EurekaAutoServiceRegistration {

	public EngineEurekaAutoServiceRegistration(ApplicationContext context, EurekaServiceRegistry serviceRegistry,
			EurekaRegistration registration) {
		super(context, serviceRegistry, registration);
		// TODO Auto-generated constructor stub
	}

	/**
     * 上下文关闭时会调用此方法, 在另一个线程中取消注册, 防止超时
     */
    @Override
    public void stop() {
    	System.out.println("取消注册:unregsiter eureka in another thread");
        Thread stopThread = new Thread(() -> super.stop());
        stopThread.start();
        try {
            stopThread.join(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("unregister done");
    }
}

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

@Configuration
public class EngineEurekaConfig extends EurekaClientAutoConfiguration{

	public EngineEurekaConfig(ConfigurableEnvironment env) {
		super(env);
		// TODO Auto-generated constructor stub
	}

	@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就使用我们的类来响应关闭事件了,直接kill 进程号 就可以优雅停止了

方法二(目前网关项目正在使用的方式):

spring-boot-starter-actuator中提供了/shutdown的方式来优雅的停止服务

pom.xml配置增加:

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.properties配置:

# 启用shutdown
endpoints.shutdown.enabled=true

# 禁用密码验证
endpoints.shutdown.sensitive=false

服务启动后,可以通过linux的curl命令发送POST请求的方式优雅的停止服务。编写脚本,可命名为:shutdown.sh,内容:

curl -X POST host(ip):port(端口)/shutdown

执行结果:

{"message":"Shutting down, bye..."}

服务停止log

c.n.e.EurekaDiscoveryClientConfiguration : Unregistering application eureka-server with eureka with status DOWN
com.netflix.discovery.DiscoveryClient    : Shutting down DiscoveryClient ...
com.netflix.discovery.DiscoveryClient    : Completed shut down of DiscoveryClient
o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase 0
o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans
c.n.eureka.DefaultEurekaServerContext    : Shutting down ...
c.n.eureka.DefaultEurekaServerContext    : Shut down
o.apache.catalina.core.StandardService   : Stopping service Tomcat
o.a.c.c.C.[Tomcat].[localhost].[/]       : Destroying Spring FrameworkServlet 'dispatcherServlet'

猜你喜欢

转载自blog.csdn.net/nihao12323432/article/details/81205288