kill
kill 可将指定的信息送至程序。
预设的信息为 SIGTERM(15),可将指定程序终止。就是默认 kill 传送的信息。
1
| kill [-s <信息名称或编号>][程序] 或 kill [-l <信息编号>]
|
- -l <信息编号> 若不加<信息编号>选项,则 -l 参数会列出全部的信息名称。
- -s <信息名称或编号> 指定要送出的信息。
- [程序] [程序]可以是程序的PID或是PGID,也可以是工作编号。
最常用的信号是:
- 1 (HUP):重新加载进程。
- 9 (KILL):杀死一个进程。
- 15 (TERM):正常停止一个进程。
有的文章说:kill -9 进程ID
是从内核级别把进程杀死。会使你的程序里执行到哪处于一种不知的状态。所以除非必要时刻不要使用kill -9 进程ID。
kill 进程ID
正常停止一个进程。是发送一个信号给你的程序告诉他,他要停止,他会走关闭流程。
什么情况会引发kill不掉要使用Kill -9
例如进程陷入无限循环、资源泄漏、线程死锁等。在这种情况下,标准的kill命令可能无法将进程正确地终止。
判断一个进程是否进入无限循环、资源泄漏、线程死锁等问题是一个复杂的任务,通常需要运行时监控和诊断工具来帮助我们进行分析。下面是一些常见的工具和方法:
- 监控进程的系统资源使用情况:使用操作系统的监控工具(如top、ps等)来观察进程的CPU使用率、内存占用情况等。如果发现一个进程占用过高的CPU或内存资源而无法恢复正常状态,那可能是进入了无限循环或资源泄漏的情况。
- 分析进程的日志文件:进程的日志文件可能包含关于异常、错误信息、死锁等的记录。通过仔细分析日志文件,可以定位到具体的代码段或操作可能导致进程陷入无限循环或死锁。
- 使用诊断工具:Java提供了一些用于诊断的工具,如jps、jstack、jmap、jconsole等。这些工具可以提供线程堆栈信息、内存快照、垃圾收集信息等,有助于分析进程中的问题。
- 执行代码审查:检查进程中的代码,特别是涉及到循环、锁、资源关闭等的地方。查看是否存在潜在的无限循环、不正确的锁使用、未及时释放资源等问题。
spring boot 优雅关闭
什么叫优雅结束?
- 第一步:停止接收请求和内部线程。
- 第二步:判断是否有线程正在执行。
- 第三步:等待正在执行的线程执行完毕。
- 第四步:停止容器。
优雅关闭
测试代码:
1 2 3 4 5 6 7 8 9 10 11
| @GetMapping(value = "/test") public String test(){ log.info("test --- start"); try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("test --- end"); return "test"; }
|
注意这里是补获取异常没有抛出异常。
kill -15 pid
启动spring boot项目
访问测试接口
使用 kill -15 pid(kill pid) 来结束这个进程
这时观察日志(console): 发现test — start 和 test — end 还有打断异常输出。
所以在生产环境中,使用kill -15 是基本安全的,程序正常执行至完毕。
ConfigurableApplicationContext colse
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j public class TestController implements ApplicationContextAware { private ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } @GetMapping(value = "/test") public String test(){ log.info("test --- start"); try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("test --- end"); return "test"; }
@PostMapping(value = "shutdown") public void shutdown(){ ConfigurableApplicationContext cyx = (ConfigurableApplicationContext) context; cyx.close(); } }
|
程序在启动的时候向 JVM 注册了一个关闭钩子,我们在执行 colse 方法的时候会删除这个关闭钩子,JVM 就会知道这是需要停止服务。
访问shutdown接口。观察同kill -15。
actuator
这种方式是通过引入依赖的方式停止服务,actuator 提供了很多接口,比如健康检查,基本信息等等,我们也可以使用他来优雅的停机。
引入依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
|
application.yml:
1 2 3 4 5 6 7 8 9 10
| management: endpoints: web: exposure: include: shutdown endpoint: shutdown: enabled: true server: port: 8888
|
访问:http://127.0.0.1:8888/actuator/shutdown
10 秒以后再停止服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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 java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ElegantShutdownConfig implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> { private volatile Connector connector; private final int waitTime = 10; @Override public void customize(Connector connector) { this.connector = connector; } @Override public void onApplicationEvent(ContextClosedEvent event) { connector.pause(); Executor executor = connector.getProtocolHandler().getExecutor(); if (executor instanceof ThreadPoolExecutor) { try { ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor; threadPoolExecutor.shutdown(); if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) { System.out.println("请尝试暴力关闭"); } } catch (InterruptedException ex) { System.out.println("异常了"); Thread.currentThread().interrupt(); } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import com.ymy.config.ElegantShutdownConfig; import lombok.extern.slf4j.Slf4j; import org.apache.catalina.connector.Connector; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; 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.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.event.ContextClosedEvent; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @SpringBootApplication public class ShutdownServerApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(ShutdownServerApplication.class, args); run.registerShutdownHook(); } @Bean public ElegantShutdownConfig elegantShutdownConfig() { return new ElegantShutdownConfig(); } @Bean public ServletWebServerFactory servletContainer() { TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(); tomcat.addConnectorCustomizers(elegantShutdownConfig()); return tomcat; } }
|
启动项目,再访问接口test接口,因为test也是10秒,10秒后再停止服务,操作需要一定的时间,所以test不会被打断,不会出现打断日志。
销毁前数据备份操作
如果我想在服务停止的时候做点备份操作啥的,应该怎么做呢?其实很简单在你要执行的方法上添加一个注解即可:@PreDestroy。
所以合在一起的意思就是在容器停止之前执行一次,你可以在这里面做备份操作,也可以做记录停机时间等。
新增服务停止备份工具类:DataBackupConfig.java。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import org.springframework.context.annotation.Configuration; import javax.annotation.PreDestroy; @Configuration public class DataBackupConfig { @PreDestroy public void backData(){ System.out.println("正在备份数据。。。。。。。。。。。"); } }
|
用上面的优雅关闭,可以看到执行打印日志。
结合nginx已连接的请求去掉负载后是否成功返回文章
可以发现,重启流程为修改ningx负载,重新加载nginx配置文件,等一会儿,使用kill (kill -15)等优雅关闭spring boot 服务可以基本保证程序可控。
其它的风险
正常情况按照上面的操作步骤就可以了。
但是还要看代码编写,比如如果在sleep的地方不是只打印日志或者异常出现在事务中,代码就会被中断,如果没有事务的保护,数据就会出错,代码要有可重入性、强壮性。
还有一些其他情况比如服务器断电等情况,还是未知情况。