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

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


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

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

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

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

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


本文你将得到什么

  1. 为什么 nohup 和 screen 不是生产级方案
  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 证书。


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

相关推荐
独码侠18 小时前
第 04 篇·DeepSeek Harness 沙箱实测:AI 替代测试开发 3.4→4.6,差距只在一个权限模式
人工智能
java资料站18 小时前
十四、SpringAi-RAG《知识库AI问答系统》实战项目介绍
人工智能
当下新鲜事18 小时前
皮尔磁纸板进料安全方案怎么选:myPNOZ与O300组合使用经验分享
网络·人工智能·安全
染指111018 小时前
139.Agent-多Agent框架-Skills渐进式加载文档
数据库·人工智能·langchain·agents
SimonKing18 小时前
Qoder中Qwen3.8-Flash 限时免费使用
java·后端·程序员
知几蜗牛18 小时前
Java 17实现Agent工具调用的白名单、预算与审计门禁
人工智能
橘和柠18 小时前
vLLM高吞吐推理引擎:本地高并发推理服务
人工智能
小林ixn18 小时前
别再手写 Agent 基建了:用 deepagents 中间件,30 行代码搞定文件系统 / 记忆 / 上下文压缩
人工智能·langchain
云和数据.ChenGuang18 小时前
langchain4j InMemoryEmbeddingStore常用的方法
java·人工智能·windows·java-ee·fastapi·springai
小天源18 小时前
GEO 品牌监测系统实战:基于 Node.js + Playwright 实现多平台采集与报告导出
人工智能·node.js·geo·品牌检测·ai诊断