java优雅停机说明及springboot2版本后的单体优雅停机方案

之前搭建过jenkins发版项目,启动脚本中的判断进程是否存在,然后杀死进程的方式是kill -9的,这样的做法是很不优雅的,执行复杂的业务的时候突然中断可能导致一系列的问题。哎,包括我目前所在的公司处理生产的重启也是这样,我想到了之前无意中看到的优雅停机的方式

项目已上传gitee

https://gitee.com/gangye/springboot_mutiDemos/tree/shutdown-gentle-branch/

常规的java项目优雅停机

Java语言本身是支持优雅停机的,先写一个demo来看看普通的java项目是如何优雅停机的。搭建一个项目,为了下面继续使用springboot项目展示,我就不创建普通的java项目而是一个maven项目

<dependencies>
    <!--引入另一个项目工程-->
    <dependency>
        <groupId>com.csrcb</groupId>
        <artifactId>common_demo</artifactId>
        <version>1.0.0</version>
    </dependency>

    <!--若使用了spring-boot-starter-web依赖,则无需引入此依赖,以免冲突,否则引入此日志依赖-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.2</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <includeSystemScope>true</includeSystemScope>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <skipTests>true</skipTests>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <!--<mainClass>com.csrcb.ShutdownGentleNativeTest</mainClass>-->
                <mainClass>com.csrcb.SpringAppShutdown</mainClass>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

然后配置log4j.properties配置文件

#此配置适用于非springboot项目引入slf4j配置,首先引入slf4j-log4j12依赖

#也适用于springboot项目(前提springboot是1.3及之前的版本,1.3之后版本有所区别,
#   我使用的2.1.6版本不再支持properties格式文件支持,支持xml格式的,当然之前的logback-spring.xml是毫无疑问支持的并且无需在applications.yml中指定配置
#   为了兼容之前写的模拟优雅停机的非spring项目而引入的日志配置log.properties,我做了些调整,
#   将springboot-web-start中去除了spring-boot-starter-logging依赖,然后额外引入slf4j-log4j12依赖,接着在application.yml配置文件中配置好要使用的日志配置文件路径logging.config: classpath:log4j.properties)
#config root logger
log4j.rootLogger = INFO,system.out
log4j.appender.system.out=org.apache.log4j.ConsoleAppender
log4j.appender.system.out.layout=org.apache.log4j.PatternLayout
log4j.appender.system.out.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%p] [%t] %c - %m%n

#config this Project.file logger
#log4j.logger.thisProject.file=INFO,thisProject.file.out
#log4j.appender.thisProject.file.out=org.apache.log4j.DailyRollingFileAppender
#log4j.appender.thisProject.file.out.File=logContentFile.log
#log4j.appender.thisProject.file.out.layout=org.apache.log4j.PatternLayout
#log4j.appender.thisProject.file.out.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%c]-[%p] %m%n

在application.yml中指定log4j.properties配置文件

 测试Runtime.getRuntime().addShutdownHook();功能

@Slf4j
public class ShutdownHook extends Thread{
    private Thread mainThread;
    private boolean shutdownFlag;

    public ShutdownHook(Thread mainThread) {
        this.mainThread = mainThread;
        this.shutdownFlag = false;
        Runtime.getRuntime().addShutdownHook(this);
    }

    @Override
    public void run() {
        log.info("Shut down signal received.");
        this.shutdownFlag = true;
        mainThread.interrupt();
        try {
            mainThread.join();//当收到停止信号时,等待mainThread的执行完成
        } catch (InterruptedException e) {
            log.error("中断后join异常",e);
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            log.error("睡眠中断异常");
        }
        log.info("Shut down excute all complete.");
    }

    public boolean getShutownFlag(){
        return shutdownFlag;
    }
}

编写启动类,测试优雅停机功能是否实现

@Slf4j
public class ShutdownGentleNativeTest {
    private ShutdownHook shutdownHook;

    public ShutdownGentleNativeTest(){
        this.shutdownHook = new ShutdownHook(Thread.currentThread());
    }

    public static void main(String[] args) {
        ShutdownGentleNativeTest shutdownTest = new ShutdownGentleNativeTest();
        log.info("app start...");
        shutdownTest.businessDemoExcute();
        log.info("main Thread end...");
    }

    public void businessDemoExcute(){
        while (!shutdownHook.getShutownFlag()){
            log.info("do something start...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                log.error("睡眠中断异常捕获:",e);
            }
            log.info("do something end...");
        }
        log.info("end of businessExcute...");
    }
}

 idea启动然后停机有点类似断点的效果,无法模拟,然后我就打包部署发版,然后测试使用Ctrl+C(与kill -2没什么区别)还有kill -15,复合预期的优雅停机案例

Springboot项目的优雅停机

 由于大部分web项目都是使用的tomcat做容器,而且springboot自带的默认容器也是tomcat,所以就以常规的springboot项目做案例,我这使用的是springboot2.1的版本没有2.3以后的版本,没有引入spring-boot-starter-actuator组件

 若引入了spring-boot-starter-actuator组件,优雅停机就简单多了

以2.3以后的版本为例,配置这样即可

server.shutdown: graceful
spring.lifecycle.timeout-per-shutdown-phase: 30s

下面回归正题

import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;

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

/**
 * @Classname GracefulShutdownTomcat
 * @Description tomcat容器的优雅停机
 * @Date 2021/9/3 16:39
 * @Created by gangye
 */
@Slf4j
@Component
public class GracefulShutdownTomcat implements TomcatConnectorCustomizer , ApplicationListener<ContextClosedEvent> {
    private volatile Connector connector;
    //设置收到中断(终结)信号后等待时间,超出时间将强制关机
    private final int waitTime = 30;
    @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;
                threadPoolExecutor.shutdown();
                if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
                    log.warn("Tomcat thread pool did not shut down gracefully within " + waitTime + " seconds. Proceeding with forceful shutdown");
                }
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

在启动类中配编写

import com.csrcb.config.GracefulShutdownTomcat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;

/**
 * @Classname SpringAppShutdown
 * @Description SpringBoot的优雅停机(单体应用)
 * @Date 2021/8/31 17:06
 * @Created by gangye
 */
@SpringBootApplication
public class SpringAppShutdown {
    public static void main(String[] args) {
        SpringApplication.run(SpringAppShutdown.class,args);
    }

    @Autowired
    private GracefulShutdownTomcat gracefulShutdownTomcat;

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

同样,打包部署mvn clean install,注意将pom文件的mainClass里面的类改下,然后将打包的jar包,使用java -jar XXX.jar启动,注意不要使用nohup后台启动,不然kill -2关闭无效(不会关闭),但是kill -15还是正常有优雅停机的效果

请求一个,然后中断,再请求一个

 查看日志 

 复合优雅停机效果,但是此应用还是单体的,在微服务上,复杂应用中,消息队列,服务下线,定时任务关闭,等等一系列都要进行处理

 

参考文章:

https://www.jianshu.com/p/0c49eb23c627

http://www.spring4all.com/article/1022

https://www.jianshu.com/p/62574e09acbf 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值