最近我真是气笑了。一个线上服务挂了,日志啥都没有,监控也没捕到异常,最后发现是有人直接一手kill -9给干掉的。兄弟啊,这都2025年了,用kill -9关服务还不打招呼,这操作放在生产环境,简直是自杀式部署。
为什么不能随便 kill -9
你看啊,kill -9干的事很简单粗暴:直接向进程发送SIGKILL 信号,让系统内核强制把进程从内存里清理掉。听起来挺爽的对吧?但问题是——应用压根没机会“收尾”。
一般我们优雅关停服务的时候,比如用kill -15(也就是SIGTERM),程序还能捕获到这个信号,做一些收尾动作,比如:
把还没写完的日志落盘;
把数据库连接、MQ通道、线程池这些资源释放掉;
把内存缓存持久化;
通知注册中心“我下线啦”。
但kill -9就像是拔电源关电脑,啥清理都没做。日志写一半,缓存还在内存里,数据库连接没断干净,ZooKeeper节点还挂着。你再启动程序,分分钟报错:“端口被占用”、“连接池异常”、“文件锁未释放”……
有时候你以为“重启服务”就能解决问题,但其实你只是把烂摊子藏进了地毯下面。
正确姿势:优雅停机
咱写服务端程序,尤其是 Spring Boot 的那种,优雅停机是必须要做的。要在 JVM 收到退出信号的时候,把清理逻辑跑完再退。
比如下面这段 Java 代码,能优雅地捕获信号,做点收尾工作:
public class GracefulShutdownDemo {
public static void main(String[] args) {
System.out.println("服务启动成功... PID: " + ProcessHandle.current().pid());
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("收到关闭信号,开始清理资源...");
try {
Thread.sleep(2000); // 模拟资源清理
System.out.println("资源清理完毕,程序安全退出。");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}));
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
}
}
你可以这样测试:
# 启动程序
java GracefulShutdownDemo
# 优雅关闭
kill -15 <pid>
# 或者
ctrl + c
日志里你会看到 “资源清理完毕” 的输出。
但如果你用kill -9 <pid>,程序根本来不及执行ShutdownHook,直接消失,连个告别都没有。
那为啥有人非要用 kill -9?
大部分情况都是因为——程序卡死了。
要么是死锁、要么是线程池爆满、要么是某个 while(true) 忘了 sleep。开发者一看不动了,就直接kill -9一刀切。问题解决得快,但代价也大。
更常见的还有一种情况,就是在 CI/CD 脚本里偷懒,写了:
ps -ef | grep my-app | grep -v grep | awk '{print $2}' | xargs kill -9
然后再nohup java -jar my-app.jar &好家伙,连是否停干净都不检查,这脚本一跑就是线上“惊喜”。
正确的做法其实很简单,先发SIGTERM:
ps -ef | grep my-app | grep -v grep | awk '{print $2}' | xargs kill -15
然后等5秒看看服务是否自动退出。 如果还卡着,再考虑kill -9,那是最后一根稻草。
Spring Boot 自带优雅停机功能
其实 Spring Boot 从 2.3 版本开始就支持优雅停机了。只要你在application.yml里加上:
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
当服务收到 SIGTERM 信号时,它会:
拒绝新请求;
等待正在处理的请求完成;
再执行@PreDestroy注解的方法;
最后退出。
比如这样:
@Component
public class ResourceCleaner {
@PreDestroy
public void onDestroy() {
System.out.println("Spring Boot 正在优雅停机,释放资源...");
closeDbConnections();
shutdownThreadPools();
}
private void closeDbConnections() {
// 模拟关闭数据库连接
System.out.println("数据库连接已关闭");
}
private void shutdownThreadPools() {
System.out.println("线程池已销毁");
}
}
你再执行:
kill -15 <pid>
控制台输出:
Spring Boot 正在优雅停机,释放资源...
数据库连接已关闭
线程池已销毁
很干净,是吧。
实战中“kill -9”造成的灾难
我见过几个特别惨的例子:
某支付系统有人手动kill -9杀服务,结果 Redis 的分布式锁没释放,后续订单全卡死;
某业务用 MyBatis 批量写数据库,刚写到一半被kill -9,事务没提交也没回滚,数据半拉子;
某微服务在注册中心没下线,流量还在打过去,结果调用方疯狂报Connection refused;
还有人kill -9后立刻拉起服务,端口还没释放干净,直接启动失败。
所以那句话真不是玩笑话:
“发现谁用 kill -9 关闭程序就开除” ——不是因为脾气大,是因为真的贵。
最后
kill -9是最后的核按钮,不是常规操作。 生产上关停服务,正确顺序应该是:
发SIGTERM(kill -15);
确认服务停止;
清理残留资源;
再重新启动。
这点看似小事,实际上体现了一个工程师的职业素养。你可以写再多的业务逻辑,但只要线上一个kill -9下去,日志没了、数据坏了、锁卡了,别人只会说一句——“这是谁写的程序?”
所以啊,除非你是在救火,否则别碰 kill -9。
真要杀,也得知道自己在干啥。