springboot项目如何优雅停机

前言

相信很多同学都会用Kill -9 PID来杀死进程,如果用在我们微服务项目里面,突然杀死进程会有什么后果?有没有其他的方式优雅的停机呢?今天,我们就来讨论下kill -9 pid 这个命令对微服务项目的影响,以及如何优雅的停机的问题。

kill -9 pid的危害

kill -9 属于暴利删除,直接杀死进程。对于微服务系统而言,直接判了死刑,直接从系统层面对进程进行了结束。微服务中的线程,根本没有反应的机会直接香消玉损。

比如前端发起的订单请求,后端springboot项目中的线程正在处理订单数据的保存。此时如果kill -9 pid 将微服务进程杀死,后端将不会有任何响应请求的机会,页面请求会立即中断出现异常情况。虽然一般对于后端事务数据库都会回滚,但是,页面出现异常毕竟会让用户体验度下降。

如何优雅的停机

理论步骤

1、停止接收请求和内部线程
2、判断是否有线程正在执行
3、等待正在执行的线程执行完毕
4、停止tomcat容器

优雅方式

1、kill -15 pid 命令停机

项目增加测试入口:

/**
 * 停机 测试
 * kill -15
 * kill -9
 * @return a
 * @author senfel
 * @date 2023/5/13 17:37
 */
@GetMapping("test")
public void test(){
    
    
    log.error("测试方法进入开始===========");
    try {
    
    
        Thread.sleep(100000);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    log.error("测试方法执行结束===========");
}

服务启动项目

[root@devops-01 tmp]# nohup java -jar demo.jar &
[11] 43451
[root@devops-01 tmp]# nohup: ignoring input and appending output to ‘nohup.out’

页面访问地址后服务器用kill -15 PID结束进程

[root@devops-01 tmp]# kill -15 43451

查看日志:
在这里插入图片描述

居然报错了,但是测试日志都打印出来了。为什么会报错呢?这就和sleep这个方法有关了,在线程休眠期间,当调用线程的interrupt方法的时候会导致sleep抛出异常,这里很明显就是kill -15 这个命令会让程序马上调用线程的interrupt方法,目的是为了让线程停止,虽然让线程停止,但线程什么时候停止还是线程自己决定。

kill -15 pid 会等待线程执行完并拒绝新的请求,如果有线程睡眠会抛出java.lang.InterruptedException异常。

2、ApplicationContext close停机

项目增加测试代码:

/**
 * shutdown
 * @author senfel
 * @version 1.0
 * @date 2023/5/13 18:04
 */
@RestController
@Slf4j
public class ShutDownController implements ApplicationContextAware {
    
    

    private ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        this.context = applicationContext;
    }


    /**
     * 停机 ConfigurableApplicationContext方式
     * @return a
     * @author senfel
     * @date 2023/5/13 17:36
     */
    @PostMapping(value = "shutdown")
    public void shutdown(){
    
    
        ConfigurableApplicationContext cyx = (ConfigurableApplicationContext) context;
        cyx.close();
    }
}

启动项目

[root@devops-01 tmp]# nohup java -jar demo.jar &
[15] 44001
[root@devops-01 tmp]# nohup: ignoring input and appending output to ‘nohup.out’

调用测试方法后,调用上下文主动停机接口/shutdown
查看服务日志:
在这里插入图片描述

同样的日志信息,表示调用上下文停机后会等待线程执行完并拒绝新的请求,如果有线程睡眠会抛出java.lang.InterruptedException异常。

3、actuator shutdown 停机

引入依赖

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

配置yml

management:
  endpoints:
    web:
      exposure:
        include: health,shutdown
  endpoint:
    shutdown:
      enabled: true

这种方式是通过引入依赖的方式停止服务,actuator提供了很多接口,比如健康检查,基本信息等等,
我们也可以使用他来优雅的停机。

调用接口测试//actuator/shutdown
查看日志:
在这里插入图片描述

查看日志同样是停机后会等待线程执行完并拒绝新的请求,如果有线程睡眠会抛出java.lang.InterruptedException异常。只不过页面会有提示,更加的友好。

在这里插入图片描述

4、ApplicationListener 监听延时停机

以上三种都是直接打断睡眠线程,那么有没有一种方式在阻断新请求的同时,让睡眠线程睡眠完成优雅停机呢?
有点,我们的ApplicationListener 可以监听到服务关闭事件,覆写事件并延时即可。

增加监听代码:

/**
 * GracefulShutdown
 * @author senfel
 * @version 1.0
 * @date 2023/5/13 19:40
 */
@Slf4j
@Component
public class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
    
    
    private volatile Connector connector;
    @Override
    public void customize(Connector connector) {
    
    
        this.connector = connector;
    }
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
    
    
        this.connector.pause();
        Executor executor = this.connector.getProtocolHandler().getExecutor();
        if (executor instanceof ThreadPoolExecutor) {
    
    
            try {
    
    
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                threadPoolExecutor.shutdown();
                if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
    
    
                    log.error("Tomcat thread pool did not shut down gracefully within 30 seconds. Proceeding with forceful shutdown");
                }
            } catch (InterruptedException ex) {
    
    
                Thread.currentThread().interrupt();
            }
        }
    }
}

先请求带有睡眠代码的接口,然后停机,查看日志:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_39970883/article/details/130661951