【Python量化系统工程化 #08】关了 SSH 就停?systemd 让脚本开机自启 + 异常自动拉起

系列定位:让"写一个能稳定跑半年的量化脚本"这件事,从散落的经验贴变成可复制的流水线。


痛点:脚本能跑了,但离开电脑就停

上一篇我们给脚本加上了定时调度。现在它能每天自动执行数据拉取和归档,看起来万事大吉。

但还有一个隐藏风险:运行环境不稳定

  • 笔记本合盖休眠 → Python 进程被系统挂起
  • SSH 窗口关闭 → nohup 也保不住某些场景
  • 云服务器重启 → 任务彻底消失,要手动登录重新启动
  • 脚本偶发异常崩溃 → 没人知道它停了,直到发现数据断更

定时调度解决的是"什么时候跑",后台守护解决的是"跑了之后怎么一直活着"。


本文你将得到什么

  1. 为什么 nohupscreen 不是生产级方案
  2. 用 Python 模拟守护进程:主进程监控 + 子进程崩溃自动重启(跨平台演示)
  3. Linux 生产环境标准方案:systemd unit 配置详解
  4. 日志查看与排错:journalctl 常用命令

先做一个跨平台演示:Python 进程守护

在深入 systemd 之前,先用一段纯 Python 代码理解"守护"的本质:

一个进程(daemon)持续监控另一个进程(worker),发现 worker 挂了立刻重新启动。

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
进程守护 demo:主进程监控 worker,异常退出时自动重启
"""
import os
import sys
import time
import subprocess
from datetime import datetime

def now():
    return datetime.now().strftime("%H:%M:%S")

def worker():
    """模拟工作进程:执行量化任务,12 秒后模拟异常退出"""
    pid = os.getpid()
    print(f"[{now()}] [worker pid={pid}] 启动,开始执行任务...")
    time.sleep(12)
    print(f"[{now()}] [worker pid={pid}] 模拟异常崩溃!")
    sys.exit(1)

def daemon(duration_sec=35):
    """守护进程:持续监控 worker,发现退出立即重启"""
    print(f"[{now()}] [daemon] 启动,策略:worker 异常退出 -> 立即重启")
    start = time.time()
    worker_proc = None
    restart_count = 0

    while time.time() - start < duration_sec:
        if worker_proc is None or worker_proc.poll() is not None:
            if worker_proc is not None:
                rc = worker_proc.returncode
                print(f"[{now()}] [daemon] 检测到 worker 退出 (rc={rc}),执行第 {restart_count + 1} 次重启...")
            worker_proc = subprocess.Popen([sys.executable, __file__, "--worker"])
            restart_count += 1
            print(f"[{now()}] [daemon] worker 已重启,新 pid={worker_proc.pid}")
        time.sleep(1)

    # 清理
    if worker_proc and worker_proc.poll() is None:
        worker_proc.terminate()
        worker_proc.wait(timeout=3)
    print(f"[{now()}] [daemon] 演示结束。期间 worker 共启动 {restart_count} 次")

if __name__ == "__main__":
    if "--worker" in sys.argv:
        worker()
    else:
        daemon(duration_sec=35)

运行效果:

复制代码
[10:27:07] [daemon] 启动,策略:worker 异常退出 -> 立即重启
[10:27:07] [daemon] worker 已重启,新 pid=26988
[10:27:19] [worker pid=26988] 模拟异常崩溃!
[10:27:20] [daemon] 检测到 worker 退出 (rc=1),执行第 2 次重启...
[10:27:20] [daemon] worker 已重启,新 pid=32628

核心逻辑只有三行

  1. subprocess.Popen() 启动 worker
  2. worker_proc.poll() 检查是否还在运行
  3. 如果退出了,回到第 1 步重新启动

这就是所有进程守护的本质------包括 systemd。


生产环境方案:systemd(Linux)

上面的 Python 模拟只在"理解原理"时有用。生产环境不需要自己造轮子,Linux 的 systemd 已经提供了完整、健壮的进程管理能力。

1. 创建 systemd unit 文件

/etc/systemd/system/quant-data.service

ini 复制代码
[Unit]
Description=量化数据采集服务
After=network.target

[Service]
Type=simple
User=quant
WorkingDirectory=/home/quant/app
Environment=MAIRUI_LICENCE=LICENCE-66D8-9F96-0C7F0FBCD073
ExecStart=/home/quant/venv/bin/python /home/quant/app/run.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

2. 关键配置项解释

配置项 作用
Type=simple 前台运行(Python 脚本无需 daemon 化)
Restart=always 无论退出码是什么,都自动重启
Restart=on-failure 仅异常退出(非零码)时重启
RestartSec=5 崩溃后等待 5 秒再重启,避免刷爆 CPU
Environment= 注入环境变量,证书不用写进代码

量化场景推荐 Restart=on-failure。如果脚本正常退出(比如跑完了当日全部任务),不需要强制重启;只有异常崩溃时才重启。

3. 启动与查看状态

bash 复制代码
# 重载配置
sudo systemctl daemon-reload

# 开机自启 + 立即启动
sudo systemctl enable --now quant-data

# 查看运行状态
sudo systemctl status quant-data

# 实时查看日志
sudo journalctl -u quant-data -f

# 查看今日全部日志
sudo journalctl -u quant-data --since today

Windows 用户怎么办?

如果你的开发机是 Windows,有两条路径:

路径 A:继续开发调试

  • 用上面的 Python daemon() 演示方案临时守护
  • 或写一个简单的 .bat 脚本放在"启动文件夹"

路径 B:部署到 Linux 服务器

  • 量化脚本最终还是要 7×24 运行,笔记本不是长久之计
  • 一台最低配云服务器(2核2G)足够跑个人量化任务
  • 部署后用 systemd 管理,配合 cron 做定时调度

推荐:开发在 Windows,部署用 Linux + systemd。


常见坑

现象 解决
Restart=always 把正常退出也重启 脚本执行完当日任务后正常退出,systemd 又把它拉起来 改用 Restart=on-failure,或在脚本末尾循环等待而不是直接退出
环境变量没传进去 脚本读不到 MAIRUI_LICENCE 写在 [Service]Environment= 里,不要用 ~/.bashrc
日志找不到 不知道脚本输出了什么 journalctl -u quant-data -f 实时查看
权限不足 systemctl start 失败 检查 User= 是否有权限访问工作目录和 Python 环境
虚拟环境路径不对 ImportError ExecStart 写虚拟环境的完整 python 路径,如 /home/quant/venv/bin/python

小结

  • 守护的本质:一个进程监控另一个进程,死了就重启
  • 开发调试 :上面的 Python daemon() 代码足够理解原理
  • Linux 生产环境 :systemd 是标准答案,Restart=on-failure + journalctl 排错
  • Windows:建议最终部署到 Linux 服务器,本地仅做开发

下篇预告:脚本稳定跑了,但日志文件越写越大、出错时大海捞针怎么办?#09 日志轮转与结构化日志,让排错不再头疼


代码与文档

本文配套代码已开源在 GitHub:https://github.com/MaiRuiApi

安装 SDK:

bash 复制代码
pip install mairui

证书获取:访问麦蕊官网申请免费版或付费版 API 证书。


免责声明:本文仅供技术学习交流,不构成任何投资建议。股市有风险,入市需谨慎。

相关推荐
西柚研究生1234561 小时前
论文分析17:YOLOv11_UAVNet:无人机航拍图像专用目标检测算法
人工智能·python·深度学习·算法·目标检测
m4Rk_1 小时前
【论文阅读】Agent 记忆机制(74):CompassMem——从相似度检索走向事件图上的记忆导航
论文阅读·人工智能·学习·开源·github
镭封1 小时前
短剧多人对白怎么配?一人完成多角色配音的办法
人工智能·音视频·语音识别·媒体
电商API_180079052471 小时前
拍照找同款是怎么实现的?淘宝以图搜图接口 item_search_img 对接实战
大数据·爬虫·数据挖掘·数据分析·代采api
天若有情6731 小时前
C++花式魔改!手写头文件复刻Java语法,彻底打破编码思维定式
java·c++·语言做语言·jac++
昇腾知识体系1 小时前
昇腾 torchrec_npu Docker 镜像选择:CANN/PyTorch/torchrec 版本矩阵与启动参数
人工智能·华为·知识图谱
VIP_CQCRE1 小时前
用 Ace Data Cloud 一次接入 AI 视频生成:HappyHorse Videos API 实战指南
人工智能·api·ai视频·acedatacloud
去伪存真2 小时前
大模型的参数量为什么那么大?
前端·人工智能