文章目录
- 前言
- kill -9 pid的危害
- 如何优雅的停机
- 理论步骤
- 优雅方式
- 1、kill -15 pid 命令停机
- 2、ApplicationContext close停机
- 3、actuator shutdown 停机
- 4、ApplicationListener 监听延时停机
前言
相信很多同学都会用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();
}
}
}
}
先请求带有睡眠代码的接口,然后停机,查看日志: