Python 脚本与长期运行服务的本质区别

2次阅读

服务需持续运行并响应请求,不同于单次执行脚本;须用专业服务器(如uvicorn)、正确处理信号、规范日志、管理全局资源与依赖生命周期。

Python 脚本与长期运行服务的本质区别

python 脚本执行完就退出,服务必须持续响应请求

脚本是单次任务:读文件、算数据、写结果、sys.exit() —— 进程结束,资源全释放。服务不是这样:它启动后得一直占着端口、监听连接、处理并发请求,哪怕暂时没流量也不能退。

常见错误现象:ConnectionRefusedError: [errno 111](连不上)、Address already in use(端口被占)、进程启动后立刻消失(没加阻塞逻辑)。

  • while True: + time.sleep(1) 模拟“不退出”?不行 —— 它不响应网络请求,只是空转,CPU 白耗,且无法优雅停止
  • 真正服务要依赖框架的事件循环或 WSGI/ASGI 服务器(如 uvicorngunicorn),不是靠自己轮询
  • 脚本里调 requests.get("http://localhost:8000") 测试服务?得先确认服务已启动并完成绑定,否则容易因竞态失败

信号处理和进程生命周期管理完全不同

脚本收到 SIGINT(Ctrl+C)通常直接终止;服务必须能捕获 SIGHUPSIGTERM,做清理(关数据库连接、刷缓存、保存状态),再退出。

使用场景:用 systemd 管理 Python 服务时,若没处理 SIGTERMsystemctl stop 会超时后强制 kill,导致数据丢失。

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

  • signal.signal(signal.SIGTERM, cleanup_handler) 注册清理函数,但注意:线程下信号只发给主线程
  • 异步服务(如 asyncio)要用 asyncio.create_task() 启动清理协程,不能直接在信号回调里 await
  • atexit.register() 在服务中基本无效 —— 它只在正常退出时触发,而 SIGKILL 或 OOM killer 下完全不执行

日志、错误、标准输出的行为差异极大

脚本的 print() 直接输出到终端;服务跑在后台时,stdoutstderr 若没重定向,内容会丢失或混入系统日志,排查问题时根本找不到。

性能影响:频繁 print() 在高并发服务里会成为 I/O 瓶颈,尤其当 stdout 是行缓冲但实际未 flush 时,日志延迟严重。

  • 服务必须用 Logging 模块,配置 handlers 写入文件或 syslog,禁用裸 print()
  • 日志级别别全开 DEBUG —— 在生产环境,一次请求打 50 行日志会让磁盘 I/O 拉满
  • 未捕获异常在脚本里会打印 traceback 并退出;在服务里可能只静默失败,请求卡住或返回 500,但进程还在跑 —— 必须配全局异常处理器(如 flask@app.errorhandler(Exception)

环境隔离与依赖加载时机决定稳定性

脚本每次运行都重新 import 全部模块,出错就停;服务启动时加载一次依赖,之后所有请求共享同一份内存中的对象 —— 全局变量、单例、数据库连接池都是“活”的,改了代码不重启就无效。

容易踩的坑:import 时做了耗时操作(如读大文件、连远程 API),会导致服务启动慢甚至超时失败;或者模块内初始化了不可复用的对象(如未设 timeout 的 requests.session)。

  • 把重操作移到请求处理函数里(如按需建 HTTP client),或用懒加载模式(if not hasattr(g, 'db')
  • 避免在模块顶层执行 os.chdir()os.environ.update() —— 会影响后续所有请求的路径和环境变量
  • venv 隔离依赖是基础,但服务还要注意:升级包后必须重启进程,热重载(如 watchdog)在生产环境极不稳定

最常被忽略的是:服务不是“脚本加个 while 循环”,而是对资源生命周期、错误传播路径、外部交互节奏的重新建模。一个 time.sleep() 看似简单,背后牵扯的是信号安全、线程模型、监控可观测性 —— 这些不厘清,服务上线后的问题只会更隐蔽。

text=ZqhQzanResources