优雅!通过编程方式重启 Spring Boot 应用的 3 种方案

发布于:2025-07-25 ⋅ 阅读:(16) ⋅ 点赞:(0)

优雅!通过编程方式重启 Spring Boot 应用的 3 种方案

场景:

  1. 动态刷新配置后希望热重启
  2. 升级 JAR 后无停机替换
  3. 运行中检测到致命错误,需要自恢复

本文给出三种经过生产验证的方案,全部 零外部依赖仅依赖 Actuator,可按场景自由取舍。


方案总览

方案 触发位置 是否关闭旧进程 是否零停机 依赖
SpringApplication.restart() 同 JVM Spring Cloud Context
② Actuator restart 端点 HTTP/SSH Actuator
③ 自托管脚本 + 优雅关闭 新 JVM

方案 1:编程式 RestartEndpoint(最轻量)

需要 spring-cloud-starter 或单独引入 spring-cloud-context

1.1 依赖

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

1.2 一行代码即可重启

@RestController
@RequiredArgsConstructor
public class RestartController {
    private final RestartEndpoint restartEndpoint;   // 来自 spring-cloud

    @PostMapping("/restart")
    public String restart() {
        Executors.newSingleThreadExecutor()
                 .submit(() -> restartEndpoint.restart()); // 异步,防止阻塞当前线程
        return "restarting...";
    }
}

1.3 特点

  • 无进程切换,同 JVM 热刷新
  • 会重新执行整个 SpringApplication.run,但 不会退出 JVM
  • 适用于配置热更新代码热替换(DevTools)

方案 2:Actuator /actuator/restart(运维最爱)

2.1 引入 Actuator & 暴露端点

management:
  endpoints:
    web:
      exposure:
        include: restart,health,info
  endpoint:
    restart:
      enabled: true

2.2 一键重启

curl -X POST http://localhost:8080/actuator/restart

2.3 进阶用法

  • 脚本化

    #!/bin/bash
    PID=$(pgrep -f myapp.jar)
    curl -s -X POST http://localhost:${MGMT_PORT:-8080}/actuator/restart
    # 等待健康 UP
    while [ "$(curl -s http://localhost:${MGMT_PORT:-8080}/actuator/health | jq -r .status)" != "UP" ]; do
      sleep 1
    done
    echo "restart done"
    
  • 灰度重启
    在负载均衡摘除节点 → 调 /restart → 健康检查 → 重新挂载。


方案 3:自托管脚本优雅重启(生产终极方案)

适用于 升级 JAR、JDK 参数变化 等需要真正 进程替换 的场景。

3.1 关闭钩子优雅停机

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(App.class);
        app.addListeners((ApplicationListener<ContextClosedEvent>) e -> {
            // 关闭线程池、MQ、连接池
            GracefulShutdown.destroy();
        });
        app.run(args);
    }
}

3.2 自动重启脚本(restart.sh)

#!/bin/bash
APP_NAME=myapp
JAR_PATH=/opt/apps/${APP_NAME}.jar
PID_FILE=/var/run/${APP_NAME}.pid
JAVA_OPTS="-Xms512m -Xmx512m"

# 1. 优雅关闭
if [ -f $PID_FILE ]; then
  PID=$(cat $PID_FILE)
  kill -15 $PID
  for i in {1..30}; do
    kill -0 $PID 2>/dev/null || break
    sleep 1
  done
  kill -9 $PID 2>/dev/null || true
fi

# 2. 启动新进程
nohup java $JAVA_OPTS -jar $JAR_PATH > /dev/null 2>&1 &
echo $! > $PID_FILE

3.3 在应用内触发脚本

@RestController
public class UpgradeController {

    @PostMapping("/upgrade")
    public void upgrade() throws IOException {
        Runtime.getRuntime().exec("./restart.sh");
    }
}

选型与最佳实践

场景 推荐方案
仅刷新配置 方案 1
运维/CI 一键重启 方案 2
升级 JAR、JDK 参数变化 方案 3
高可用集群 方案 2 + 方案 3 结合,逐台灰度

小结

  • 方案 1 最轻量,一行代码搞定同 JVM 热重启。
  • 方案 2 最友好,运维通过 HTTP 即可重启,零登录服务器。
  • 方案 3 最通用,任何升级、回滚都可优雅完成。

网站公告

今日签到

点亮在社区的每一天
去签到