从原理到实战的完整指南
文章导读目录
守护进程的核心概念与必要性
什么是守护进程?
在Linux/Unix系统中,守护进程(daemon)是一种在后台运行、不受终端控制、生命周期贯穿整个系统运行周期的特殊进程,例如Web服务器Nginx、容器引擎Docker的运行守护进程。

为什么需要脚本守护?
开发者在测试环境或生产部署中,常遇到以下痛点:
- 脚本因意外崩溃(如内存溢出、网络中断)后无法自动重启
- 进程未正确处理
SIGHUP信号导致终端关闭后中断 - 缺乏监控机制,故障发现滞后
通过编写守护脚本,可解决:
- 自动重启:进程异常退出时0.5秒内拉起新进程
- 电平控制:精确管控进程启动/停止状态
- 资源限制:监控CPU/内存使用超阈值时主动干预
脚本守护进程的常见技术方案
方案选择需根据环境复杂度与性能要求:
简单循环检测(Bash基础方案)
#!/bin/bash
while true; do
/path/to/your-script.sh
sleep 2
done
适用场景:快速验证、临时任务
缺陷:无法处理僵尸进程、无日志记录
Systemd服务单元(linux原生方案)
[Unit] Description=My Daemon Script After=network.target [Service] ExecStart=/usr/local/bin/my-script.sh Restart=always RestartSec=3 User=myuser [Install] WantedBy=multi-user.target
优势:自动依赖管理、标准化状态跟踪
配置要点:Restart=always确保任何退出均重启,RestartSec防止频繁重启
独立监控脚本(生产级方案)
通过子进程管理实现:
- 主进程仅执行监控逻辑
- 子进程执行实际业务
- 信号转发与资源回收
手把手实现一个可靠的守护脚本
以下是一个符合生产标准的Python守护脚本(兼容Python3.6+):
#!/usr/bin/env python3
import os
import sys
import time
import signal
import subprocess
class DaemonManager:
def __init__(self, command, max_restarts=10, restart_interval=5):
self.command = command
self.max_restarts = max_restarts
self.restart_interval = restart_interval
self.child_pid = None
self.restart_count = 0
self.running = True
def start_child(self):
"""以子进程形式启动目标脚本"""
process = subprocess.Popen(self.command, shell=True)
self.child_pid = process.pid
print(f"[INFO] 启动子进程 PID: {self.child_pid}")
def monitor(self):
"""核心监控循环"""
signal.signal(signal.SIGINT, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler)
self.start_child()
while self.running:
# 检查子进程存活状态
try:
pid, status = os.waitpid(self.child_pid, os.WNOHANG)
if pid != 0: # 子进程已退出
self.restart_count += 1
if self.restart_count > self.max_restarts:
print("[ERROR] 达到最大重启次数,停止守护")
break
print(f"[WARN] 进程退出,第{self.restart_count}次重启")
time.sleep(self.restart_interval)
self.start_child()
except OSError as e:
print(f"[ERROR] 监控异常:{e}")
time.sleep(0.5)
def signal_handler(self, signum, frame):
"""优雅处理终止信号"""
print(f"[INFO] 收到信号 {signum},停止守护...")
self.running = False
if self.child_pid:
os.kill(self.child_pid, signal.SIGTERM)
if __name__ == "__main__":
# 使用示例:守护 your-script.py
manager = DaemonManager(
command="python3 your-script.py",
max_restarts=5,
restart_interval=3
)
manager.monitor()
关键设计点:
- 使用
subprocess.Popen替代os.system避免shell注入 os.waitpid非阻塞检查避免僵尸进程- 最大重启次数防止无限循环崩溃
- 信号转发确保子进程能正常退出
高级技巧:应对进程崩溃与资源泄漏
1 内存泄漏自动保护
import psutil
def check_resource():
process = psutil.Process(child_pid)
if process.memory_percent() > 80.0: # 内存超80%
process.kill()
print("[ALARM] 内存泄漏风险,强制终止进程")
注意:需安装psutil库(pip install psutil)
2 日志回滚机制
import logging
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler('daemon.log', maxBytes=10*1024*1024, backupCount=3)
logging.basicConfig(handlers=[handler], level=logging.INFO)
3 健康检查接口(RESTful方式)
定期用cURL检查脚本的HTTP接口:
*/5 * * * * curl -f http://127.0.0.1:8080/health || systemctl restart myscript
常见问题问答(FAQ)
Q1:为什么我的守护脚本在终端关闭后就会停止?
A:这是因为脚本没有和终端进程组脱离,解决方案:
- 使用
nohup启动(nohup python daemon.py &) - 或在脚本开头调用
os.setsid()创建新会话
Q2:如何实现每天定时重启守护进程?
A:使用crontab结合计划任务:
# 每天凌晨3点重启脚本 0 3 * * * pkill -f 'your-script'; sleep 5; /usr/local/bin/start-daemon.sh
更规范的做法是systemd的OnCalendar定时单元。
Q3:守护进程日志占用磁盘过大怎么办?
A:必须配置日志轮转,推荐方案:
- 使用
logrotate管理系统日志 - 代码中实现
RotatingFileHandler(参考4.2节) - 限制日志保留天数(
maxDays=7)
Q4:多进程的脚本如何统一守护?
A:使用进程组管理,例如通过pgrep -P $PPID获取所有子进程,统一发送信号。
简化方案:改用supervisor作为监控容器(支持多个子进程配置)。
Q5:守护脚本自身崩溃怎么办?
A:有两个可靠方案:
- 使用systemd的
Restart=always管理守护脚本本身 - 编写二级守护:两个脚本互相监控对方存活状态(高可用集群思想)
守护进程的核心逻辑在于:监控→检测异常→自动恢复,根据环境选择适当的轮询间隔(生产环境建议1-3秒),结合资源限制与日志管理,即可实现高可用的脚本运行环境,推荐直接使用systemd(linux)或Windows Service Manager编写正式服务,但在需要灵活控制的自建场景中,掌握文中的Python守护脚本框架足以应对90%的需求。
本文由搜索引擎资料综合实践而成,参考了Linux Systemd文档及Stackoverflow社区的最佳实践讨论。