Python 脚本与服务共存的架构设计

1次阅读

用 os.setsid() 脱离进程组,systemd 配置 Delegate=yes 和 killmode=process,文件系统(json+os.replace)共享数据,pid 文件或健康端点判断服务状态,日志加前缀区分。

Python 脚本与服务共存的架构设计

python 脚本怎么不被服务进程 kill 掉

服务进程(比如用 systemdsupervisord 管理的后台服务)默认会把子进程设为同一进程组,脚本一启动就被父服务收编,服务重启/停止时连带干掉——这不是脚本写得有问题,是进程生命周期没切开。

  • os.setsid() 在脚本开头新建会话,脱离父进程组:
    import os os.setsid()  # 必须在 fork 后、exec 前调用,单进程脚本可直接用
  • 避免使用 subprocess.run(..., shell=True) 启动长期任务,它默认继承父环境;改用 start_new_session=True 参数
  • 如果脚本由 systemd 启动,加配置项 Delegate=yesKillMode=process,否则 systemd 会递归杀光所有子进程

脚本和服务共享数据但不共享内存怎么办

Python 多进程间不能直接传对象引用,尤其服务是常驻进程、脚本是临时任务时,靠全局变量或模块级变量根本不可靠。

  • 优先走文件系统:用 json + 原子写入(os.replace())做状态快照,服务轮询读取,脚本写完就退出
  • 避免用 sqlite3 的 WAL 模式做高并发读写,小规模没问题,但脚本频繁启停容易留下 -wal 文件卡住服务
  • 别碰 multiprocessing.Manager —— 它依赖本地 socket,脚本和服务不在同一 Python 进程树里时根本连不上,报错是 ConnectionRefusedError

如何让脚本感知服务是否还在运行

脚本需要判断服务状态再决定是否执行,但不能靠 ps aux | grep 这种脆弱方式——进程名可能重复,权限可能不够,还容易漏判。

  • 服务启动时写一个带时间戳的 pid 文件,路径统一设为 /var/run/myapp/service.pid,脚本读它后用 os.kill(pid, 0) 检查进程是否存在
  • 更稳的是走服务暴露的健康端点:比如服务开了 http://127.0.0.1:8000/health,脚本用 requests.get() 加超时(timeout=2),失败就当服务离线
  • 注意不要在脚本里用 time.sleep(5) 等服务启动,systemd 有 After=,supervisord 有 startsecs,交给进程管理器处理依赖

日志混在一起看不清谁干了什么

服务和脚本都往 stdout 打日志,systemd journal 里全搅成一团,grep ERROR 时分不清是服务崩溃还是脚本参数错了。

立即学习Python免费学习笔记(深入)”;

  • 脚本必须加前缀:用 Logging.basicConfig(format='[script] %(message)s'),服务端同理用 [service]
  • 别依赖 print(),它不经过 logging handler,systemd 里可能缓冲延迟输出;一律走 logging.info()
  • 如果脚本执行很快(StandardOutput=append:/var/log/myapp/script.log 单独落盘更可控

事情说清了就结束。最常被忽略的是进程组隔离和日志前缀——其他都能绕,这两点一漏,排查成本直接翻倍。

text=ZqhQzanResources