优雅!通过编程方式重启 Spring Boot 应用的 3 种方案
场景:
- 动态刷新配置后希望热重启;
- 升级 JAR 后无停机替换;
- 运行中检测到致命错误,需要自恢复。
本文给出三种经过生产验证的方案,全部 零外部依赖 或 仅依赖 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 最通用,任何升级、回滚都可优雅完成。