SpringBoot2.X优雅停机

优雅停机主要应用在版本更新的时候,为了等待正在工作的线程全部执行完毕,然后再停止。
这就涉及到了springboot的核心机制之一——Actuator。

Actuator

概念:actuator是制造术语,是指用于移动或控制某些物体的机械设备。 actuators可以通过很小的变化产生大量的运动。
这是官方给出的actuator的定义,正常人一看,根本看不懂嘛,总的来说,actuator就是个监控神器。

Endpoints

actuator的核心,它能够对应用程序进行监控及交互,springboot提供了大量的内置的endpoints(我们也可以自己扩展endpoint)。
下面列举了一些内置的endpoints:

ID Description Enabled by default
auditevents 显示当前应用程序的审计事件信息。 Yes,需要一个AuditEventRepository Bean
beans 显示应用程序中所有Spring bean的完整列表。 Yes
caches 显示可用的缓存 Yes
conditions 显示在配置和自动配置类上被评估的条件,以及它们匹配或不匹配的原因。 Yes
configprops 显示所有@ConfigurationProperties的排序列表。 Yes
env 从Spring的ConfigurableEnvironment中公开属性。 Yes
flyway 显示已应用的所有Flyway数据库迁移。 Yes
health 显示应用程序的健康信息。 Yes
httptrace 显示HTTP跟踪信息(默认情况下,最后100个HTTP请求-响应交换)。 Yes,需要一个AuditEventRepository Bean
info 显示任意应用程序信息。 Yes
integrationgraph 显示Spring集成图。 Yes
loggers 显示和修改应用程序中的记录器配置。 Yes
liquibase 显示已应用的所有Liquibase数据库迁移。 Yes
metrics 显示当前应用程序的“度量”信息。 Yes
mappings 显示所有@RequestMapping路径的列表。 Yes
scheduledtasks 显示应用程序中计划的任务。 Yes
sessions 允许从Spring会话支持的会话存储中检索和删除用户会话。当使用Spring Session对反应性web应用程序的支持时不可用。 Yes
shutdown 让应用程序优雅地关闭。 No
threaddump 执行线程转储。 Yes

当你的应用是一个web应用的时候,诸如Spring MVC、Spring WebFlux或者Jersey等,可以使用下面的附加endpoint:

ID Description Enabled by default
heapdump 返回一个hprof堆转储文件。 Yes
jolokia 通过HTTP公开JMX bean(当Jolokia位于类路径上时,WebFlux不能使用它)。 Yes
logfile 返回日志文件的内容(如果是log .file.name或log .file)。已经设置了路径属性)。支持使用HTTP范围头来检索日志文件的部分内容。 Yes
prometheus 以可以被Prometheus服务器抓取的格式公开度量数据。 Yes
pom.xml中引入actuator依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置文件中开启shutdown endpoint

上面的表格可以看到,只有shutdown endpoint默认是关闭的,所以要先启用它。
application.yml:

management:
  endpoint:
    shutdown:
      enabled: true

application.properties:

#启用shutdown
management.endpoint.shutdown.enabled=true
完整代码

配置类:

package com.example.demo.Configuration;

import org.apache.catalina.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName ActuratorConfig
 * @Description garceful shutdown
 * @Author domi
 * @Date 2019/11/01
 **/
@Configuration
public class ActuratorConfig {

    private static final int waitTime = 20;  //ms

    @Bean
    public GracefulShutdown gracefulShutdown() {
        return new GracefulShutdown();
    }

    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
        tomcatServletWebServerFactory.addConnectorCustomizers(gracefulShutdown());
        return tomcatServletWebServerFactory;
    }

    private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {

        private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);

        private volatile Connector connector;


        @Override
        public void customize(Connector connector) {
            this.connector = connector;
        }

        @Override
        public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
            this.connector.pause();

            Executor executor = this.connector.getProtocolHandler().getExecutor();
            if (executor instanceof ThreadPoolExecutor) {
                try {
                    ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                    log.info("shutdown has started!");
                    threadPoolExecutor.shutdown();
                    log.info("shutdown has ended!");
                    if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
                        log.warn("Tomcat 进程在" + waitTime + "秒内无法结束,尝试强制结束ing...");
                    }
                    log.info("shutdown is success!");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }
    }

}

application.yml:

info:
  app:
    name: spring-boot-actuator
    version: V1.0.0
    test: test

management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: "*"
      base-path: /monitor
  server:
    port: 8081

server:
  port: 8080
测试代码

Controller:

package com.example.demo.Contorller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassName HomeController
 * @Description 模拟正在处理的多个线程
 * @Author domi
 * @Date 2019/11/01
 **/
@RestController
@RequestMapping("/hello")
public class HomeController {

    //计数器
    public AtomicInteger started = new AtomicInteger();
    public AtomicInteger ended = new AtomicInteger();

    @RequestMapping(value = "/world", method = RequestMethod.GET)
    public String testThread() {
        System.out.println("[" + Thread.currentThread().getName() + "]{" + this + "}started:" + started.addAndGet(1));
        try {
            Thread.sleep(1000 * 30);

        } catch (Exception ex) {
            ex.printStackTrace();
        }

        System.out.println("[" + Thread.currentThread().getName() + "]{" + this + "}finished:" + ended.addAndGet(1));

        return "hello";

    }

}

启动类:

package com.example.demo;

import com.example.demo.Contorller.HomeController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}

测试方法:启动项目,浏览器访问http://localhost:8080/hello/world,多次刷新页面,控制台打出如下log:
SpringBoot2.X优雅停机_第1张图片
然后通过在curl输入

curl -X POST http://localhost:8081/monitor/shutdown

curl工具下载链接

关闭项目。控制台打出如下log:
SpringBoot2.X优雅停机_第2张图片
如果在awaitTermination设定的时间内,还是有线程没有关闭的话,将会强制结束线程。
参考链接:
Spring Boot官方文档:https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/production-ready-features.html
Spring Boot 1.X和2.X优雅重启实战:https://www.cnblogs.com/bigbigwood/p/9977069.html
Shut down embedded servlet container gracefully:https://github.com/spring-projects/spring-boot/issues/4657

你可能感兴趣的:(springboot)