首页
学习
活动
专区
圈层
工具
发布

领导:发现谁用 kill -9 关闭程序就开除!

kill -9 24831

命令一敲,Java 进程瞬间没了。

干净是真干净,连 JVM 跟你说遗言的机会都没有。线上要是谁把kill -9当正常停机命令用,我看到一般也会拦一下,这玩意更适合“进程已经没救了”的时候,不适合拿来日常发布。

有次我看到一段停机脚本,写得相当豪爽:

pid=$(cat /data/run/order-api.pid)

kill -9 "$pid"

rm -f /data/run/order-api.pid

我第一眼就不太信这个脚本。

不是担心它杀不掉,而是担心它杀得太彻底。

Java 服务停机时,后面可能还挂着线程池任务、数据库事务、MQ 消费、日志缓冲区,甚至还有正在处理一半的 HTTP 请求。你直接一个-9,操作系统确实省事了,业务可不一定省事。

正常情况下,我更愿意这样停:

kill 24831

别看就少了个-9,差别挺大。

Linux 下普通kill默认发送的是SIGTERM,也就是 15 号信号。它表达的意思更像:

“准备关门了,把手上的活收一下。”

而:

kill -9 24831

发送的是SIGKILL。

这个信号不会交给 JVM 处理,操作系统直接把进程干掉。Java 代码捕获不了,也不能忽略。

这地方最容易坑人的就是 JVM Shutdown Hook。

比如服务里有个内存队列,停机前我希望把还没处理的数据记录下来:

public final class PendingTaskRecorder {

  private final BlockingQueue<Long> pendingIds;

  public PendingTaskRecorder(BlockingQueue<Long> pendingIds) {

      this.pendingIds = pendingIds;

  }

  public void registerShutdownTask() {

      Runtime.getRuntime().addShutdownHook(

          new Thread(() -> {

              System.out.println("service stopping, pending=" + pendingIds.size());

              while (!pendingIds.isEmpty()) {

                  Long taskId = pendingIds.poll();

                  if (taskId != null) {

                      System.out.println("unfinished task: " + taskId);

                  }

              }

          }, "shutdown-recorder")

      );

  }

}

正常kill PID,JVM 收到SIGTERM,Shutdown Hook 有机会执行。

kill -9?

这段代码跟没写差不多。

Spring 项目也一样。

很多服务停机时,会在 Bean 销毁阶段关闭资源:

@Component

public class ReportWorker {

  private final ExecutorService pool =

          Executors.newFixedThreadPool(4);

  @PreDestroy

  public void close() {

      pool.shutdown();

      try {

          if (!pool.awaitTermination(8, TimeUnit.SECONDS)) {

              pool.shutdownNow();

          }

      } catch (InterruptedException e) {

          Thread.currentThread().interrupt();

          pool.shutdownNow();

      }

  }

}

这种代码我在线上项目里是愿意留的。

因为正常停机时,Spring 容器关闭,@PreDestroy会有执行机会,线程池至少能把已经接进来的任务处理一下。

但你要是直接kill -9,Spring 容器的生命周期也别谈了。

线程池没收尾,连接没主动释放,业务清理逻辑没执行。

数据库连接最终会因为 TCP 断开被清掉,但“连接最后会被清掉”和“业务正确完成”完全是两回事。

比如代码正跑到这里:

@Transactional

public void settle(Long orderId) {

  orderRepository.markPaid(orderId);

  ledgerService.writeLedger(orderId);

  eventPublisher.publish(orderId);

}

如果都在一个数据库事务里,进程突然死亡,数据库通常还能依靠事务机制回滚。

我反而不太怕这种。

我更怕这种:

public void finishOrder(Long orderId) {

  orderRepository.markFinished(orderId);

  remoteCouponClient.release(orderId);

  messageSender.sendFinished(orderId);

}

数据库改完了,远程接口刚调出去,消息还没发送,进程被kill -9了。

重启以后到底应该从哪一步继续?

这就不是 Linux 命令的问题了,而是业务一致性问题。

还有 MQ 消费。

消费者刚把消息取下来,业务执行到一半,进程直接消失。如果 ACK 时机、事务边界、幂等没设计好,重启以后很容易遇到重复消费,严重一点就是业务状态对不上。

所以我平时停 Java 服务,顺序比较固定。

先发SIGTERM:

kill "$pid"

然后看进程有没有正常退出:

for i in {1..20}; do

  if ! kill -0 "$pid" 2>/dev/null; then

      echo "process stopped"

      exit 0

  fi

  sleep 1

done

20 秒还不退,我才会继续查。

先jstack看线程卡在哪:

jstack "$pid" > /tmp/java-stop-$pid.log

如果看到大量线程堵在数据库连接、HTTP 调用或者某把锁上,这时候处理的是“为什么停不下来”,不是上来就给它一发-9。

真到了服务僵死、Full GC 卡住、Shutdown Hook 本身挂死,或者进程已经失去响应,那kill -9当然可以用。

我从来不反对这个命令。

我反对的是把它写成:

stop() {

  kill -9 "$1"

}

然后堂而皇之叫“优雅停机”。

这跟关电脑不点关机,天天直接拔插头没多大区别。

Java 服务能不能安全退出,看的不是进程最后有没有消失,而是它消失之前,该停的流量停了没有,该处理完的任务处理了没有,该提交或者回滚的数据有没有落稳。

kill -9应该是最后那把锤子。

天天拿锤子关服务,领导看了想开除人,我能理解。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OD-_chsHhnMhlBZeDwkaht0Q0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券