本文目录导读:

- 核心概念:信号处理 (Signal Handling)
- 方案一:通用 Bash 进程管理脚本(最常用)
- 方案二:使用
trap命令 + 后台运行的守护进程 - 方案三:针对 Java 应用的
systemctl服务文件(推荐生产环境) - 方案四:针对容器化环境(Docker/Kubernetes)
- 如何选择与优化
实现进程的优雅启停(Graceful Start/Stop)是生产环境中保证服务可靠性的关键,核心思路是:启动时确保依赖就绪,关闭时给进程足够时间完成当前任务(而非直接 kill -9)。
以下是几种常见场景下的优雅启停脚本实现方案:
核心概念:信号处理 (Signal Handling)
- SIGTERM (15): 请求进程终止,进程可以捕获并执行清理逻辑(保存状态、关闭连接),这是默认的
kill和kill -15。 - SIGQUIT (3): 类似SIGTERM,但通常还会生成core dump。
- SIGKILL (9): 强制立即终止,进程无法捕获或忽略。应避免在常规停服中使用。
- SIGHUP (1): 常用于重载配置文件。
优雅停服的原则:
- 发送
SIGTERM(或SIGQUIT)。 - 等待一段时间(如
graceful_timeout)。 - 若进程仍在运行,再发送
SIGTERM或SIGQUIT作为警告。 - 最终超时后,才使用
SIGKILL。
通用 Bash 进程管理脚本(最常用)
这个脚本适用于大多数Java、Python、Node.js、Go应用,它通过PID文件管理进程,并实现优雅关闭。
#!/bin/bash
# 优雅启停脚本: graceful.sh
# 用法: ./graceful.sh {start|stop|restart|status}
APP_NAME="myapp"
APP_PATH="/opt/$APP_NAME"
JAR_NAME="$APP_NAME.jar" # 或者 Python main.py, Node app.js 等
PID_FILE="/var/run/${APP_NAME}.pid"
LOG_FILE="/var/log/${APP_NAME}.log"
# 优雅停机等待时间(秒)
GRACEFUL_TIMEOUT=30
start() {
if [ -f "$PID_FILE" ]; then
pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
echo "$APP_NAME is already running (PID: $pid)"
exit 1
else
# 僵尸PID,清理
rm -f "$PID_FILE"
fi
fi
echo "Starting $APP_NAME..."
# ❗ 关键:启动命令,根据实际情况修改
# 确保命令在后台运行并输出PID
cd "$APP_PATH" || exit 1
nohup java -jar "$JAR_NAME" >> "$LOG_FILE" 2>&1 &
# 或: nohup python main.py >> "$LOG_FILE" 2>&1 &
pid=$!
echo "$pid" > "$PID_FILE"
echo "$APP_NAME started (PID: $pid)"
}
stop() {
if [ ! -f "$PID_FILE" ]; then
echo "$APP_NAME is not running (PID file not found)"
return 0
fi
pid=$(cat "$PID_FILE")
if ! kill -0 "$pid" 2>/dev/null; then
echo "$APP_NAME is not running (stale PID file)"
rm -f "$PID_FILE"
return 0
fi
echo "Stopping $APP_NAME gracefully (PID: $pid)..."
# 第一步:发送SIGTERM(优雅终止信号)
kill -15 "$pid"
# 第二步:等待一段时间
waited=0
while [ $waited -lt $GRACEFUL_TIMEOUT ]; do
if ! kill -0 "$pid" 2>/dev/null; then
echo "$APP_NAME stopped gracefully."
rm -f "$PID_FILE"
return 0
fi
sleep 1
waited=$((waited + 1))
done
# 第三步:超时,再次尝试终止,然后强制终止
echo "Timeout reached ($GRACEFUL_TIMEOUT sec). Sending force kill..."
kill -9 "$pid" 2>/dev/null
wait "$pid" 2>/dev/null # 等待回收子进程
rm -f "$PID_FILE"
echo "$APP_NAME forcefully stopped (PID: $pid)."
}
restart() {
echo "Restarting $APP_NAME..."
stop
sleep 2 # 给系统释放端口等资源的时间
start
}
status() {
if [ -f "$PID_FILE" ]; then
pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
echo "$APP_NAME is running (PID: $pid)"
else
echo "$APP_NAME PID file exists but process dead"
fi
else
echo "$APP_NAME is not running"
fi
}
case "$1" in
start) start ;;
stop) stop ;;
restart) restart ;;
status) status ;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
如何让应用“优雅”?
上述脚本中,kill -15 只是发送信号,真正优雅的关键是应用本身要注册信号处理函数。
- Java (Spring Boot): 默认支持优雅关闭(
server.shutdown=graceful)。 - Python:
signal.signal(signal.SIGTERM, handler)。 - Node.js:
process.on('SIGTERM', callback)。 - Go:
signal.Notify(ch, syscall.SIGTERM)。
使用 trap 命令 + 后台运行的守护进程
如果你希望脚本本身能捕获退出信号,并将其传递给子进程。
#!/bin/bash
# 另一种优雅启停方式:脚本作为守护进程的父进程
pid=""
cleanup() {
echo "Received stop signal. Shutting down gracefully..."
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
kill -15 "$pid"
wait "$pid" # 等待子进程处理完SIGTERM
fi
exit 0
}
trap cleanup SIGTERM SIGINT SIGQUIT
echo "Starting service..."
# 启动后台进程
java -jar myapp.jar &
pid=$!
echo "Service started with PID: $pid"
# 等待子进程结束(阻塞)
wait
echo "Service stopped."
用法:
./daemon.sh &:在后台运行此脚本。kill -15 <daemon_pid>:触发trap,再优雅停止子进程。
针对 Java 应用的 systemctl 服务文件(推荐生产环境)
对于生产环境,使用 systemd 是最佳实践,它原生支持优雅关闭。
创建服务文件 /etc/systemd/system/myapp.service:
[Unit] Description=My Java App After=network.target [Service] Type=simple User=appuser WorkingDirectory=/opt/myapp ExecStart=/usr/bin/java -jar /opt/myapp/myapp.jar ExecStop=/bin/kill -15 $MAINPID RestartSec=10 Restart=on-failure TimeoutStopSec=30 # ⚠️ 关键参数:等待SIGTERM的最长时间,超时后发送SIGKILL [Install] WantedBy=multi-user.target
操作命令:
systemctl daemon-reload systemctl start myapp systemctl stop myapp # 会自动发送SIGTERM,等待TimeoutStopSec后发送SIGKILL systemctl restart myapp
优点:
- systemd 自动处理信号发送和超时逻辑。
- 进程监控、日志收集(
journalctl)更完善。
针对容器化环境(Docker/Kubernetes)
容器化环境下,优雅启停依赖容器运行时(Docker Engine / kubelet)的信号传递。
Docker 脚本示例(需在应用进程内实现):
#!/bin/bash # 容器内启动脚本 entrypoint.sh # 启动主进程,建议使用 exec,保证 PID 1 是应用进程 exec java -jar myapp.jar & # 等待任何信号(SIGTERM 由 docker stop 发送) trap 'kill -15 $child; wait' SIGTERM SIGINT wait
关键点:
exec: 在容器内,exec让 Java 进程直接成为 PID 1,确保它直接接收 Docker Engine 发出的 SIGTERM。STOPSIGNAL: 在 Dockerfile 中可设置STOPSIGNAL SIGTERM(默认就是SIGTERM)。Dockerfile示例:FROM openjdk:17-jre-slim COPY myapp.jar /app/ COPY entrypoint.sh /app/ RUN chmod +x /app/entrypoint.sh STOPSIGNAL SIGTERM ENTRYPOINT ["/app/entrypoint.sh"]
- Kubernetes: Pod 的
terminationGracePeriodSeconds字段对应等待 SIGTERM 的超时时间(默认30秒),Kubelet 会先发送 SIGTERM,超时后发送 SIGKILL。
如何选择与优化
| 场景 | 推荐方案 | 核心逻辑 |
|---|---|---|
| 简单 Shell 管理 | 方案一(通用 Bash 脚本) | PID文件 + kill -15 + 超时检测 + kill -9 |
| 生产服务器 | 方案三(systemd 服务) | TimeoutStopSec + ExecStop 配置 |
| Docker 单容器 | 方案四(Docker+exec) |
主进程PID 1 + 信号传递 + STOPSIGNAL |
| Kubernetes 集群 | 方案四(K8s + 容器) | terminationGracePeriodSeconds + preStop(可选) |
实现优雅启停的三个必要条件:
- 应用本身支持: 注册 SIGTERM 处理函数,做清理工作(如 Spring Boot
server.shutdown=graceful)。 - 正确的信号传递: 确保 PID 1 是实际应用进程(而非 shell 包装器)。
- 合理的超时机制: 给进程足够的时间完成当前请求(10-60 秒),超时后再强制终止。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播