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

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

场景:

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

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


方案总览

方案触发位置是否关闭旧进程是否零停机依赖
SpringApplication.restart()同 JVMSpring Cloud Context
② Actuator restart 端点HTTP/SSHActuator
③ 自托管脚本 + 优雅关闭新 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 最通用,任何升级、回滚都可优雅完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半部论语

如果觉得有帮助,打赏鼓励一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值